@phonghq/go-chat 1.0.8 → 1.0.10

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.
Files changed (121) hide show
  1. package/README.md +1 -1
  2. package/dist/chat/App.vue.d.ts +3 -2
  3. package/dist/components/chat/call/Calling.vue.d.ts +6 -2
  4. package/dist/components/common/drawer/DrawerBaseCustom.vue.d.ts +30 -0
  5. package/dist/composable/TestSound.d.ts +64 -0
  6. package/dist/composable/useCallHelper.d.ts +26 -8
  7. package/dist/constant/datetime.d.ts +1 -0
  8. package/dist/go-chat.es.js +143 -143
  9. package/dist/go-chat.umd.js +6 -6
  10. package/dist/plugins/websocket.d.ts +1 -0
  11. package/dist/style.css +1 -1
  12. package/dist/test/assets/icons/IconArrowLeft.vue.js +1 -1
  13. package/dist/test/assets/icons/IconPhone.vue.js +1 -1
  14. package/dist/test/assets/icons/IconPlan.vue.js +1 -1
  15. package/dist/test/assets/icons/IconPlus.vue.js +1 -1
  16. package/dist/test/assets/icons/IconSearch.vue.js +1 -1
  17. package/dist/test/assets/icons/call/IconMic.vue.js +1 -1
  18. package/dist/test/assets/icons/call/IconPhone.vue.js +1 -1
  19. package/dist/test/assets/icons/call/IconPhoneCancel.vue.js +1 -1
  20. package/dist/test/assets/icons/call/IconSpeaker.vue.js +1 -1
  21. package/dist/test/assets/icons/customer-appointment/IconFilter.vue.js +1 -1
  22. package/dist/test/assets/icons/customer-detail/IconArrow.vue.js +1 -1
  23. package/dist/test/assets/icons/customer-detail/IconCheck.vue.js +1 -1
  24. package/dist/test/assets/icons/customer-detail/IconDate.vue.js +1 -1
  25. package/dist/test/assets/icons/customer-detail/IconGroup.vue.js +1 -1
  26. package/dist/test/assets/icons/customer-detail/IconMessage.vue.js +1 -1
  27. package/dist/test/assets/icons/customer-detail/IconNote.vue.js +1 -1
  28. package/dist/test/assets/icons/customer-detail/IconPhone.vue.js +1 -1
  29. package/dist/test/assets/icons/customer-detail/IconPin.vue.js +1 -1
  30. package/dist/test/assets/icons/customer-detail/IconSearch.vue.js +1 -1
  31. package/dist/test/chat/App.vue.js +174 -108
  32. package/dist/test/chat/page/customer-appointment/CustomerAppointment.vue.js +1 -1
  33. package/dist/test/chat/page/customer-check-in/CollapseCheckIn.vue.js +1 -1
  34. package/dist/test/chat/page/customer-check-in/CustomerCheckIn.vue.js +1 -1
  35. package/dist/test/chat/page/customer-detail/CustomerDetail.vue.js +1 -1
  36. package/dist/test/chat/page/customer-detail/SubInformation.vue.js +1 -1
  37. package/dist/test/chat/page/error/Error.vue.js +1 -1
  38. package/dist/test/chat/page/home/ChatList.vue.js +40 -6
  39. package/dist/test/chat/page/home/ChatMessage.vue.js +1 -1
  40. package/dist/test/chat/page/home/Home.vue.js +9 -3
  41. package/dist/test/chat/page/home/InputChat.vue.js +3 -2
  42. package/dist/test/chat/page/home/NewCustomer.vue.js +1 -1
  43. package/dist/test/components/ListenEvent.vue.js +1 -1
  44. package/dist/test/components/chat/ScrollEvent/ScrollEvent.vue.js +1 -1
  45. package/dist/test/components/chat/call/Calling.vue.js +102 -70
  46. package/dist/test/components/chat/card/CardCustomerDetail.vue.js +1 -1
  47. package/dist/test/components/chat/common/collapse/CollapseBase.vue.js +1 -1
  48. package/dist/test/components/chat/common/input/InputSearch.vue.js +1 -1
  49. package/dist/test/components/chat/common/popover/PopoverBase.vue.js +1 -1
  50. package/dist/test/components/chat/common/spin/BaseSpin.vue.js +1 -1
  51. package/dist/test/components/chat/customer/Avatar.vue.js +1 -1
  52. package/dist/test/components/chat/layout/mobile/Footer.vue.js +1 -1
  53. package/dist/test/components/common/CustomLoading.vue.js +1 -1
  54. package/dist/test/components/common/Notification/NotificationDescription.vue.js +1 -1
  55. package/dist/test/components/common/button/ButtonBase.vue.js +1 -1
  56. package/dist/test/components/common/button/ButtonToggle.vue.js +1 -1
  57. package/dist/test/components/common/checkbox/CCheckboxNumber.vue.js +1 -1
  58. package/dist/test/components/common/collapse/BaseCollapse.vue.js +1 -1
  59. package/dist/test/components/common/collapse/BaseCollapseItem.vue.js +1 -1
  60. package/dist/test/components/common/drawer/DrawerBase.vue.js +1 -1
  61. package/dist/test/components/common/drawer/DrawerBaseCustom.vue.js +128 -0
  62. package/dist/test/components/common/dropdown/DropdownBase.vue.js +1 -1
  63. package/dist/test/components/common/input/CInputSearch.vue.js +1 -1
  64. package/dist/test/components/common/modal/ModalBase.vue.js +1 -1
  65. package/dist/test/components/common/popover/PopoverBase.vue.js +1 -1
  66. package/dist/test/components/common/slider/BaseSlider.vue.js +1 -1
  67. package/dist/test/components/common/spin/CSpin.vue.js +1 -1
  68. package/dist/test/components/common/tooltip/TooltipBase.vue.js +1 -1
  69. package/dist/test/components/layout/Blank.vue.js +1 -1
  70. package/dist/test/components/layout/Default.vue.js +1 -1
  71. package/dist/test/components/modal/Confirm.vue.js +1 -1
  72. package/dist/test/components/ui/button/CButton.vue.js +1 -1
  73. package/dist/test/components/ui/checkbox/Checkbox.vue.js +1 -1
  74. package/dist/test/components/ui/collapsible/Collapsible.vue.js +1 -1
  75. package/dist/test/components/ui/collapsible/CollapsibleContent.vue.js +1 -1
  76. package/dist/test/components/ui/collapsible/CollapsibleTrigger.vue.js +1 -1
  77. package/dist/test/components/ui/dialog/Dialog.vue.js +1 -1
  78. package/dist/test/components/ui/dialog/DialogClose.vue.js +1 -1
  79. package/dist/test/components/ui/dialog/DialogContent.vue.js +1 -1
  80. package/dist/test/components/ui/dialog/DialogDescription.vue.js +1 -1
  81. package/dist/test/components/ui/dialog/DialogFooter.vue.js +1 -1
  82. package/dist/test/components/ui/dialog/DialogHeader.vue.js +1 -1
  83. package/dist/test/components/ui/dialog/DialogScrollContent.vue.js +1 -1
  84. package/dist/test/components/ui/dialog/DialogTitle.vue.js +1 -1
  85. package/dist/test/components/ui/dialog/DialogTrigger.vue.js +1 -1
  86. package/dist/test/components/ui/drawer/Drawer.vue.js +1 -1
  87. package/dist/test/components/ui/drawer/DrawerContent.vue.js +1 -1
  88. package/dist/test/components/ui/drawer/DrawerDescription.vue.js +1 -1
  89. package/dist/test/components/ui/drawer/DrawerFooter.vue.js +1 -1
  90. package/dist/test/components/ui/drawer/DrawerHeader.vue.js +1 -1
  91. package/dist/test/components/ui/drawer/DrawerOverlay.vue.js +3 -3
  92. package/dist/test/components/ui/drawer/DrawerTitle.vue.js +1 -1
  93. package/dist/test/components/ui/dropdown-menu/DropdownMenu.vue.js +1 -1
  94. package/dist/test/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue.js +1 -1
  95. package/dist/test/components/ui/dropdown-menu/DropdownMenuContent.vue.js +1 -1
  96. package/dist/test/components/ui/dropdown-menu/DropdownMenuGroup.vue.js +1 -1
  97. package/dist/test/components/ui/dropdown-menu/DropdownMenuItem.vue.js +1 -1
  98. package/dist/test/components/ui/dropdown-menu/DropdownMenuLabel.vue.js +1 -1
  99. package/dist/test/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue.js +1 -1
  100. package/dist/test/components/ui/dropdown-menu/DropdownMenuRadioItem.vue.js +1 -1
  101. package/dist/test/components/ui/dropdown-menu/DropdownMenuSeparator.vue.js +1 -1
  102. package/dist/test/components/ui/dropdown-menu/DropdownMenuShortcut.vue.js +1 -1
  103. package/dist/test/components/ui/dropdown-menu/DropdownMenuSub.vue.js +1 -1
  104. package/dist/test/components/ui/dropdown-menu/DropdownMenuSubContent.vue.js +1 -1
  105. package/dist/test/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue.js +1 -1
  106. package/dist/test/components/ui/dropdown-menu/DropdownMenuTrigger.vue.js +1 -1
  107. package/dist/test/components/ui/popover/Popover.vue.js +1 -1
  108. package/dist/test/components/ui/popover/PopoverContent.vue.js +1 -1
  109. package/dist/test/components/ui/popover/PopoverTrigger.vue.js +1 -1
  110. package/dist/test/components/ui/radio-group/RadioGroup.vue.js +1 -1
  111. package/dist/test/components/ui/radio-group/RadioGroupItem.vue.js +1 -1
  112. package/dist/test/components/ui/slider/Slider.vue.js +1 -1
  113. package/dist/test/components/ui/switch/Switch.vue.js +1 -1
  114. package/dist/test/composable/TestSound.js +196 -0
  115. package/dist/test/composable/useCallHelper.js +198 -142
  116. package/dist/test/constant/datetime.js +1 -0
  117. package/dist/test/plugins/websocket.js +10 -13
  118. package/dist/test/utils/chat/call.js +37 -0
  119. package/dist/types/chat/global.d.ts +1 -0
  120. package/dist/utils/chat/call.d.ts +2 -0
  121. package/package.json +1 -1
@@ -0,0 +1,196 @@
1
+ import { ref, onBeforeUnmount } from 'vue';
2
+ export function useAudioStream(wsUrl) {
3
+ const SAMPLE_RATE = 24000;
4
+ const CHUNK_SIZE = 480;
5
+ const PREBUFFER_SEC = 0.4;
6
+ let audioCtx;
7
+ let processor;
8
+ let input;
9
+ let stream;
10
+ const ws = ref(null);
11
+ const statusText = ref('Tap to Speak with Vico');
12
+ // Speaker queue
13
+ let speakerQueue = [];
14
+ let nextPlayTime = 0;
15
+ // UI state
16
+ const recording = ref(false);
17
+ const micLevel = ref(0);
18
+ // Status management
19
+ const STATUS = {
20
+ IDLE: 'Tap to Speak with Vico',
21
+ CONNECTING: 'Connecting...',
22
+ LISTENING: 'Listening...',
23
+ SPEAKING: 'Speaking...'
24
+ };
25
+ let currentStatus = STATUS.IDLE;
26
+ function setStatus(newStatus) {
27
+ if (currentStatus !== newStatus) {
28
+ currentStatus = newStatus;
29
+ statusText.value = newStatus;
30
+ }
31
+ }
32
+ // 🎤 Float32 → PCM16
33
+ function floatTo16BitPCM(float32Array) {
34
+ const buffer = new ArrayBuffer(float32Array.length * 2);
35
+ const view = new DataView(buffer);
36
+ for (let i = 0; i < float32Array.length; i++) {
37
+ let s = Math.max(-1, Math.min(1, float32Array[i]));
38
+ view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
39
+ }
40
+ return buffer;
41
+ }
42
+ // 🔊 PCM16 → Float32
43
+ function int16ToFloat32(int16Array) {
44
+ const float32 = new Float32Array(int16Array.length);
45
+ for (let i = 0; i < int16Array.length; i++) {
46
+ float32[i] = int16Array[i] / 32768;
47
+ }
48
+ return float32;
49
+ }
50
+ // 📥 enqueue speaker chunk
51
+ async function enqueueSpeakerChunk(arrayBuffer) {
52
+ const int16View = new Int16Array(arrayBuffer);
53
+ const float32Data = int16ToFloat32(int16View);
54
+ speakerQueue.push(float32Data);
55
+ }
56
+ // 🔊 process queue
57
+ function processSpeakerQueue() {
58
+ try {
59
+ if (speakerQueue.length > 0) {
60
+ const chunk = speakerQueue.shift();
61
+ if (chunk) {
62
+ const audioBuffer = audioCtx.createBuffer(1, chunk.length, SAMPLE_RATE);
63
+ audioBuffer.getChannelData(0).set(chunk);
64
+ const source = audioCtx.createBufferSource();
65
+ source.buffer = audioBuffer;
66
+ source.connect(audioCtx.destination);
67
+ if (nextPlayTime < audioCtx.currentTime + 0.05) {
68
+ nextPlayTime = audioCtx.currentTime + PREBUFFER_SEC;
69
+ }
70
+ source.start();
71
+ nextPlayTime += audioBuffer.duration;
72
+ setStatus(STATUS.SPEAKING);
73
+ }
74
+ }
75
+ else if (recording.value) {
76
+ setStatus(STATUS.LISTENING);
77
+ }
78
+ }
79
+ catch (e) {
80
+ console.log(e);
81
+ }
82
+ requestAnimationFrame(processSpeakerQueue);
83
+ }
84
+ // 🎤 start mic
85
+ async function startRecording() {
86
+ audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
87
+ return;
88
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
89
+ input = audioCtx.createMediaStreamSource(stream);
90
+ processor = audioCtx.createScriptProcessor(1024, 1, 1);
91
+ processor.onaudioprocess = (e) => {
92
+ if (!ws.value || ws.value.readyState !== WebSocket.OPEN)
93
+ return;
94
+ const inputData = e.inputBuffer.getChannelData(0);
95
+ // calculate mic level
96
+ let sum = 0;
97
+ for (let i = 0; i < inputData.length; i++)
98
+ sum += inputData[i] ** 2;
99
+ micLevel.value = Math.sqrt(sum / inputData.length);
100
+ // chunking & send
101
+ for (let i = 0; i < inputData.length; i += CHUNK_SIZE) {
102
+ const slice = inputData.slice(i, i + CHUNK_SIZE);
103
+ const binaryChunk = floatTo16BitPCM(slice);
104
+ ws.value.send(binaryChunk);
105
+ }
106
+ };
107
+ input.connect(processor);
108
+ processor.connect(audioCtx.destination);
109
+ recording.value = true;
110
+ setStatus(STATUS.LISTENING);
111
+ }
112
+ // ⏹ stop mic
113
+ function stopRecording() {
114
+ recording.value = false;
115
+ processor?.disconnect();
116
+ input?.disconnect();
117
+ stream?.getTracks().forEach((t) => t.stop());
118
+ if (audioCtx && audioCtx.state !== 'closed') {
119
+ audioCtx
120
+ .close()
121
+ .then(() => console.log('AudioContext closed successfully.'))
122
+ .catch((err) => console.error('Error closing AudioContext:', err))
123
+ .finally(() => (micLevel.value = 0));
124
+ }
125
+ setStatus(STATUS.IDLE);
126
+ }
127
+ const getAudioContext = () => {
128
+ if (!audioCtx || audioCtx.state === 'closed') {
129
+ audioCtx = new AudioContext({ sampleRate: SAMPLE_RATE });
130
+ }
131
+ return audioCtx;
132
+ };
133
+ const safeResumeAudio = () => {
134
+ const ctx = getAudioContext();
135
+ if (ctx.state === 'suspended') {
136
+ ctx.resume().catch((err) => console.error('Error resuming AudioContext:', err));
137
+ }
138
+ };
139
+ function connect() {
140
+ if (ws.value && ws.value.readyState === WebSocket.OPEN)
141
+ return;
142
+ setStatus(STATUS.CONNECTING);
143
+ ws.value = new WebSocket(wsUrl);
144
+ ws.value.binaryType = 'arraybuffer';
145
+ ws.value.onopen = () => {
146
+ console.log('✅ WS connected');
147
+ startRecording();
148
+ processSpeakerQueue();
149
+ };
150
+ ws.value.onmessage = (event) => {
151
+ if (event.data instanceof ArrayBuffer) {
152
+ enqueueSpeakerChunk(event.data);
153
+ return;
154
+ }
155
+ if (typeof event.data === 'string') {
156
+ try {
157
+ const msg = JSON.parse(event.data);
158
+ if (msg.type === 'AudioStop' || msg.code === 'UserStartedSpeaking') {
159
+ // speakerQueue.length = 0
160
+ nextPlayTime = 0;
161
+ setStatus(STATUS.LISTENING);
162
+ return;
163
+ }
164
+ }
165
+ catch (err) {
166
+ console.warn('⚠️ Parse JSON error:', err, event.data);
167
+ }
168
+ }
169
+ console.log('⚠️ Unknown WS message, closing...');
170
+ disconnect();
171
+ };
172
+ ws.value.onclose = () => {
173
+ console.log('❌ WS closed');
174
+ stopRecording();
175
+ };
176
+ }
177
+ function disconnect() {
178
+ ws.value?.close();
179
+ stopRecording();
180
+ }
181
+ onBeforeUnmount(() => {
182
+ disconnect();
183
+ });
184
+ return {
185
+ ws,
186
+ statusText,
187
+ micLevel,
188
+ recording,
189
+ connect,
190
+ disconnect,
191
+ resumeAudio: safeResumeAudio,
192
+ enqueueSpeakerChunk,
193
+ processSpeakerQueue,
194
+ startRecording
195
+ };
196
+ }
@@ -1,170 +1,226 @@
1
- import { publishMessage } from '../plugins/mqtt';
2
- import { TOPIC_DETAIL_CALL } from '../constant/mqtt';
3
- import { getIceService } from '../utils/chat/call';
4
- import { dataProfile } from '../utils/chat/auth';
1
+ import { plivoCall, plivoEndCall } from '../utils/chat/call';
5
2
  import { ref } from 'vue';
3
+ import { socketSend } from '../plugins/websocket';
6
4
  export function useCallHelper() {
7
- let pc;
8
- let iceStunServiceLink = '';
9
- let iceServer = null;
10
- let answer;
11
- let offer = null;
12
- const remoteAudio = () => {
13
- return document.getElementById('go-chat-remote-audio');
14
- };
15
- const localAudio = () => {
16
- return document.getElementById('go-chat-local-audio');
17
- };
18
- const userRemoter = ref(null);
19
- const call = async (user) => {
20
- userRemoter.value = user;
21
- await getIceServiceLink();
22
- startPeerConnection();
23
- const offer = await pc?.createOffer();
24
- await pc?.setLocalDescription(offer);
25
- publishMessage(TOPIC_DETAIL_CALL + (userRemoter.value?.id ?? ''), {
26
- type: 'offer',
27
- offer,
28
- link: iceStunServiceLink,
29
- server: iceServer,
30
- user: dataProfile.value
5
+ function _getAudioContext() {
6
+ return new (window.AudioContext || window.webkitAudioContext)({
7
+ sampleRate: SAMPLE_RATE
31
8
  });
32
- };
33
- const end = (link) => {
34
- endPc(link);
35
- publishMessage(TOPIC_DETAIL_CALL + (userRemoter.value?.id ?? ''), {
36
- type: 'end-call',
37
- link: iceStunServiceLink
38
- });
39
- iceStunServiceLink = '';
40
- };
41
- const endPc = (link) => {
42
- if (!link || link === iceStunServiceLink) {
43
- if (pc) {
44
- pc?.close();
45
- pc = null;
46
- }
47
- const remote = remoteAudio();
48
- const local = localAudio();
49
- if (remote)
50
- remote.srcObject = null;
51
- if (local)
52
- local.srcObject = null;
9
+ }
10
+ const SAMPLE_RATE = 8000;
11
+ let uuid = '';
12
+ let audioCtxListen = _getAudioContext();
13
+ let audioCtxCall = _getAudioContext();
14
+ let prebuffer = [];
15
+ let nextPlayTime = 0;
16
+ let processorCall;
17
+ let sourceCall;
18
+ let loopTimer = null;
19
+ let running = false;
20
+ const userRemoter = ref(null);
21
+ function _int16ToFloat32(int16Array) {
22
+ const float32 = new Float32Array(int16Array.length);
23
+ for (let i = 0; i < int16Array.length; i++) {
24
+ const int = int16Array[i];
25
+ float32[i] = int < 0 ? int / 32768 : int / 32767;
53
26
  }
54
- };
55
- const test = async (data) => {
56
- const pcs = new RTCPeerConnection({
57
- iceServers: data
58
- });
59
- console.log('www', data);
60
- pcs.onicecandidate = (e) => console.log('Candidate:', e.candidate);
61
- pcs.onicegatheringstatechange = () => console.log('Gathering:', pc.iceGatheringState);
62
- const offer = await pcs.createOffer();
63
- await pcs.setLocalDescription(offer);
64
- };
65
- const startPeerConnection = async () => {
66
- // iceServer[1].credentialType = 'oauth'
67
- // test(iceServer)
68
- const data = { 'iceServers': [{ 'urls': 'stun:stun.cloudflare.com:3478' }] };
69
- pc = new RTCPeerConnection(data);
70
- // console.log(pc)
71
- // pc = new RTCPeerConnection({ iceServers: data })
72
- // console.log( "hung" , pc)
73
- pc.onicegatheringstatechange = () => {
74
- console.log('ICE gathering state:', pc.iceGatheringState);
75
- };
76
- pc.oniceconnectionstatechange = () => {
77
- console.log('ICE connection state:', pc.iceConnectionState);
78
- };
79
- pc.addEventListener('icecandidate', (event) => {
80
- console.log('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii');
81
- if (event.candidate) {
82
- console.log("New ICE candidate:", event.candidate);
27
+ return float32;
28
+ }
29
+ function _float32ToMuLaw8(float32Array) {
30
+ const MU_MAX = 0x1fff;
31
+ const BIAS = 0x84;
32
+ const muLawArray = new Uint8Array(float32Array.length);
33
+ for (let i = 0; i < float32Array.length; i++) {
34
+ let sample = float32Array[i];
35
+ // Clamp sample to [-1, 1]
36
+ sample = Math.max(-1, Math.min(1, sample));
37
+ // Convert to PCM value
38
+ let pcm_val = sample < 0 ? -sample * MU_MAX : sample * MU_MAX;
39
+ // Get sign
40
+ let sign = sample < 0 ? 0x80 : 0;
41
+ // Calculate exponent and mantissa
42
+ let exponent = 7;
43
+ for (let expMask = 0x4000; (pcm_val & expMask) === 0 && exponent > 0; expMask >>= 1) {
44
+ exponent--;
83
45
  }
84
- });
85
- pc.onicecandidate = (event) => {
86
- console.log('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii');
87
- if (event.candidate) {
88
- publishMessage(TOPIC_DETAIL_CALL + (userRemoter.value?.id ?? ''), {
89
- type: 'candidate',
90
- candidate: event.candidate,
91
- link: iceStunServiceLink
92
- });
93
- }
94
- };
95
- pc.ontrack = (event) => {
96
- console.log('aaaaaaa');
97
- const remote = remoteAudio();
98
- if (remote)
99
- remote.srcObject = event.streams[0];
100
- };
101
- navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
102
- const local = localAudio();
103
- if (local)
104
- local.srcObject = stream;
105
- stream.getTracks().forEach((track) => pc?.addTrack(track, stream));
106
- });
107
- };
108
- const getIceServiceLink = async () => {
109
- const res = await getIceService();
46
+ let mantissa = (pcm_val >> (exponent + 3)) & 0x0f;
47
+ // Compose µ-law byte
48
+ let muByte = ~(sign | (exponent << 4) | mantissa) & 0xff;
49
+ muLawArray[i] = muByte;
50
+ }
51
+ return muLawArray;
52
+ }
53
+ function float32To16Bit(float32Array) {
54
+ const buffer = new ArrayBuffer(float32Array.length * 2);
55
+ const view = new DataView(buffer);
56
+ for (let i = 0; i < float32Array.length; i++) {
57
+ let s = Math.max(-1, Math.min(1, float32Array[i]));
58
+ view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);
59
+ }
60
+ return buffer;
61
+ }
62
+ function floatToMuLawSample(sample) {
63
+ const MULAW_MAX = 0x1fff;
64
+ const MULAW_BIAS = 33;
65
+ const CLIP = 32635;
66
+ let sign = sample < 0 ? 0x80 : 0;
67
+ sample = Math.min(CLIP, Math.abs(sample * 32768));
68
+ let exponent = Math.floor(Math.log(sample / 256) / Math.log(2));
69
+ let mantissa = (sample >> (exponent + 3)) & 0x0f;
70
+ let muLawByte = ~(sign | (exponent << 4) | mantissa);
71
+ return muLawByte & 0xff;
72
+ }
73
+ function _float32ToUint8(float32Array) {
74
+ const uint8Array = new Uint8Array(float32Array.length);
75
+ for (let i = 0; i < float32Array.length; i++) {
76
+ // map từ [-1, 1] sang [0, 255]
77
+ let val = Math.max(-1, Math.min(1, float32Array[i])); // clamp
78
+ uint8Array[i] = Math.floor((val + 1) * 127.5); // chuyển về 0-255
79
+ }
80
+ return uint8Array;
81
+ }
82
+ function _encodeMuLaw(float32Array) {
83
+ const out = new Uint8Array(float32Array.length);
84
+ for (let i = 0; i < float32Array.length; i++) {
85
+ out[i] = floatToMuLawSample(float32Array[i]);
86
+ }
87
+ return out;
88
+ }
89
+ function _muLawDecode(muLawByte) {
90
+ const MULAW_MAX = 0x1fff;
91
+ const BIAS = 0x84;
92
+ muLawByte = ~muLawByte;
93
+ let sign = muLawByte & 0x80;
94
+ let exponent = (muLawByte >> 4) & 0x07;
95
+ let mantissa = muLawByte & 0x0f;
96
+ let magnitude = ((mantissa << 4) + 8) << (exponent + 3);
97
+ magnitude -= BIAS;
98
+ if (magnitude < 0)
99
+ magnitude = 0;
100
+ return sign ? -magnitude : magnitude;
101
+ }
102
+ function _muLawToFloat32(muLawArray) {
103
+ const pcm = new Float32Array(muLawArray.length);
104
+ for (let i = 0; i < muLawArray.length; i++) {
105
+ pcm[i] = _muLawDecode(muLawArray[i]) / 32768.0;
106
+ }
107
+ return pcm;
108
+ }
109
+ function _float32ToInt16(float32Array) {
110
+ const int16Array = new Int16Array(float32Array.length);
111
+ for (let i = 0; i < float32Array.length; i++) {
112
+ let s = Math.max(-1, Math.min(1, float32Array[i]));
113
+ int16Array[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
114
+ }
115
+ return int16Array;
116
+ }
117
+ const call = async (user) => {
118
+ userRemoter.value = user;
119
+ uuid = '';
120
+ const res = await plivoCall(user);
110
121
  console.log(res);
111
- iceStunServiceLink = res[0]?.urls[0] ?? '';
112
- iceServer = res;
113
- };
114
- const handleOffer = async (data) => {
115
- if (pc)
116
- return false;
117
- if (!data.link || !data.offer || !data.user || !data.server)
118
- return false;
119
- offer = data;
120
- return true;
122
+ uuid = res?.call?.requestUuid;
121
123
  };
122
- const sendOfferOk = async () => {
123
- if (!offer)
124
+ const end = async (link) => {
125
+ processorCall?.disconnect?.();
126
+ sourceCall?.disconnect?.(processorCall);
127
+ stopQueue();
128
+ console.log(uuid);
129
+ if (!uuid)
124
130
  return;
131
+ await plivoEndCall(uuid);
132
+ return;
133
+ };
134
+ const startPeerConnection = async () => {
125
135
  try {
126
- iceStunServiceLink = offer.link;
127
- iceServer = offer.server;
128
- userRemoter.value = offer.user;
129
- startPeerConnection();
130
- console.log(pc, 'aaaaaaa');
131
- await pc.setRemoteDescription(new RTCSessionDescription(offer.offer));
132
- answer = await pc?.createAnswer();
133
- await pc?.setLocalDescription(answer);
134
- publishMessage(TOPIC_DETAIL_CALL + (userRemoter.value?.id ?? ''), {
135
- type: 'offer-response',
136
- answer,
137
- link: iceStunServiceLink
138
- });
139
- return true;
136
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
137
+ sourceCall = audioCtxCall.createMediaStreamSource(stream);
138
+ processorCall: ScriptProcessorNode = audioCtxCall.createScriptProcessor(256, 1, 1);
139
+ processorCall.onaudioprocess = (e) => {
140
+ const input = e.inputBuffer.getChannelData(0);
141
+ for (let i = 0; i < input.length; i += 160) {
142
+ const slice = input.slice(i, i + 160);
143
+ const binaryChunk = _float32ToMuLaw8(slice);
144
+ socketSend(binaryChunk);
145
+ }
146
+ };
147
+ sourceCall.connect(processorCall);
148
+ processorCall.connect(audioCtxCall.destination);
140
149
  }
141
150
  catch (e) {
142
151
  console.log(e);
143
- return false;
144
152
  }
145
153
  };
146
- const handleOfferResponse = async (data) => {
147
- if (!data.link || !data.answer || data.link != iceStunServiceLink)
148
- return;
149
- await pc?.setRemoteDescription(new RTCSessionDescription(data.answer));
154
+ const handleOffer = async (data) => { };
155
+ const sendOfferOk = async () => { };
156
+ const handleOfferResponse = async (data) => { };
157
+ const addQueueListen = (arrayBuffer) => {
158
+ // const int16View = new Int16Array(arrayBuffer)
159
+ const ulawBytes = new Uint8Array(arrayBuffer);
160
+ // const float32Data = int16ToFloat32(int16View)
161
+ const floatData = _muLawToFloat32(ulawBytes);
162
+ prebuffer.push(floatData);
150
163
  };
151
- const addIceCandidate = async (data) => {
164
+ async function scheduleNext() {
152
165
  try {
153
- if (data.candidate && data.link == iceStunServiceLink) {
154
- await pc.addIceCandidate(data.candidate);
166
+ const chunk = prebuffer.shift();
167
+ if (chunk) {
168
+ const audioBuffer = audioCtxListen.createBuffer(1, chunk.length, 8000);
169
+ audioBuffer.getChannelData(0).set(chunk);
170
+ const source = audioCtxListen.createBufferSource();
171
+ source.buffer = audioBuffer;
172
+ source.connect(audioCtxListen.destination);
173
+ source.start(nextPlayTime);
174
+ nextPlayTime = audioCtxListen.currentTime + chunk.length / SAMPLE_RATE;
155
175
  }
156
176
  }
157
177
  catch (e) {
158
- console.error('Error adding received ICE candidate', e);
178
+ console.log(e);
179
+ }
180
+ }
181
+ const playQueueLoop = () => {
182
+ if (!running)
183
+ return;
184
+ while (prebuffer.length > 0 && nextPlayTime < audioCtxListen.currentTime + 0.1) {
185
+ scheduleNext();
186
+ }
187
+ loopTimer = setTimeout(playQueueLoop, 10);
188
+ };
189
+ function stopQueue() {
190
+ running = false;
191
+ if (loopTimer)
192
+ clearTimeout(loopTimer);
193
+ prebuffer.length = 0;
194
+ }
195
+ const handleMedia = async (message) => {
196
+ addQueueListen(message);
197
+ };
198
+ const startIncomingCall = async () => {
199
+ if (!audioCtxCall) {
200
+ audioCtxCall = _getAudioContext();
201
+ }
202
+ if (audioCtxCall.state === 'suspended')
203
+ await audioCtxCall.resume();
204
+ await startPeerConnection();
205
+ if (running)
206
+ return;
207
+ running = true;
208
+ if (!audioCtxListen) {
209
+ audioCtxListen = _getAudioContext();
159
210
  }
211
+ if (audioCtxListen.state === 'suspended')
212
+ await audioCtxListen.resume();
213
+ nextPlayTime = audioCtxListen.currentTime;
214
+ playQueueLoop();
160
215
  };
161
216
  return {
162
217
  call,
163
218
  end,
164
- endPc,
165
219
  handleOfferResponse,
166
220
  handleOffer,
167
- addIceCandidate,
168
- sendOfferOk
221
+ sendOfferOk,
222
+ handleMedia,
223
+ userRemoter,
224
+ startIncomingCall
169
225
  };
170
226
  }
@@ -3,3 +3,4 @@ export const DATE_FORMATS = {
3
3
  DATE_FORMAT_FULL: 'YYYY-MM-DD HH:mm:ss',
4
4
  TIME_12_FORMAT: 'hh:mm A'
5
5
  };
6
+ export const TIME_ZONE_UTC = 'Africa/Freetown';
@@ -2,16 +2,8 @@ import { tryParseJson } from '../utils/json';
2
2
  let socket = null;
3
3
  let dataCallBack = [];
4
4
  export function initWebSocket(server) {
5
- // Create WebSocket connection.
6
- socket = new WebSocket('wss://web-socket.dev01.dtsmart.dev');
7
- // socket.addEventListener('open', (event: any) => {
8
- // console.log('fffffff')
9
- // socket.send('Hello Server!')
10
- // })
11
- //
12
- // socket.addEventListener('message', (event: any) => {
13
- // console.log('Message from server ', event.data)
14
- // })
5
+ socket = new WebSocket('wss://web-socket.dev01.dtsmart.dev/web-stream');
6
+ socket.binaryType = "arraybuffer";
15
7
  socket.onopen = (event) => {
16
8
  console.log('Connected!');
17
9
  };
@@ -28,9 +20,14 @@ export function initWebSocket(server) {
28
20
  console.error('Socket error:', event);
29
21
  };
30
22
  }
31
- const socketSend = (data) => {
32
- let request = typeof data == 'string' ? data : JSON.stringify(data);
33
- socket.send(request);
23
+ export const socketSend = (data) => {
24
+ try {
25
+ let request = typeof data == 'string' ? data : JSON.stringify(data);
26
+ socket?.send(data);
27
+ }
28
+ catch (e) {
29
+ console.log(e);
30
+ }
34
31
  };
35
32
  export const getWebSocket = async () => {
36
33
  const url = 'https://web-socket.dev01.dtsmart.dev/ws/connect';
@@ -59,3 +59,40 @@ export const callOutBound = async (user) => {
59
59
  console.error(error.message);
60
60
  }
61
61
  };
62
+ export const plivoCall = async (user) => {
63
+ const url = BARE_WEBSOCKET_URL + '/ws/call';
64
+ const response = await fetch(url, {
65
+ method: 'POST',
66
+ body: JSON.stringify({
67
+ from: '18668259612' || dataProfile.value?.phone,
68
+ to: user?.phone || ''
69
+ }),
70
+ headers: {
71
+ ['Content-Type']: 'application/json'
72
+ }
73
+ });
74
+ if (!response.ok) {
75
+ throw new Error(`Response status: ${response.status}`);
76
+ }
77
+ console.log(response);
78
+ const result = await response.json();
79
+ console.log(result);
80
+ return result;
81
+ };
82
+ export const plivoEndCall = async (uuid) => {
83
+ const url = BARE_WEBSOCKET_URL + '/plivo/end-call';
84
+ const response = await fetch(url, {
85
+ method: 'POST',
86
+ body: JSON.stringify({
87
+ call_uuid: uuid
88
+ }),
89
+ headers: {
90
+ ['Content-Type']: 'application/json'
91
+ }
92
+ });
93
+ if (!response.ok) {
94
+ throw new Error(`Response status: ${response.status}`);
95
+ }
96
+ const result = await response.json();
97
+ console.log(result);
98
+ };
@@ -3,5 +3,6 @@ export type GoChatProps = {
3
3
  id?: string;
4
4
  domain?: string;
5
5
  response?: PAGE_RESPONSE;
6
+ isLib?: boolean;
6
7
  };
7
8
  export type PAGE_RESPONSE = 'mobile' | 'tablet';
@@ -3,3 +3,5 @@ import type { IResCall } from '../../types/call';
3
3
  export declare const getIceService: () => Promise<import("axios").AxiosResponse<any, any>>;
4
4
  export declare const callClient: (body: IResCall) => Promise<void>;
5
5
  export declare const callOutBound: (user: IResUser) => Promise<void>;
6
+ export declare const plivoCall: (user: IResUser) => Promise<any>;
7
+ export declare const plivoEndCall: (uuid: string) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phonghq/go-chat",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist"