@phonghq/go-chat 1.0.9 → 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.
- package/README.md +1 -1
- package/dist/chat/App.vue.d.ts +3 -3
- package/dist/components/chat/call/Calling.vue.d.ts +6 -2
- package/dist/components/common/drawer/DrawerBaseCustom.vue.d.ts +30 -0
- package/dist/composable/TestSound.d.ts +64 -0
- package/dist/composable/useCallHelper.d.ts +26 -8
- package/dist/constant/datetime.d.ts +1 -0
- package/dist/plugins/websocket.d.ts +1 -0
- package/dist/test/chat/App.vue.js +173 -108
- package/dist/test/chat/page/home/ChatList.vue.js +41 -6
- package/dist/test/chat/page/home/Home.vue.js +8 -2
- package/dist/test/chat/page/home/InputChat.vue.js +3 -2
- package/dist/test/components/chat/call/Calling.vue.js +101 -69
- package/dist/test/components/common/drawer/DrawerBaseCustom.vue.js +128 -0
- package/dist/test/components/ui/drawer/DrawerOverlay.vue.js +2 -2
- package/dist/test/composable/TestSound.js +196 -0
- package/dist/test/composable/useCallHelper.js +198 -142
- package/dist/test/constant/datetime.js +1 -0
- package/dist/test/plugins/websocket.js +10 -13
- package/dist/test/utils/chat/call.js +37 -0
- package/dist/types/chat/global.d.ts +1 -0
- package/dist/utils/chat/call.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,170 +1,226 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
164
|
+
async function scheduleNext() {
|
|
152
165
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
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.
|
|
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
|
-
|
|
168
|
-
|
|
221
|
+
sendOfferOk,
|
|
222
|
+
handleMedia,
|
|
223
|
+
userRemoter,
|
|
224
|
+
startIncomingCall
|
|
169
225
|
};
|
|
170
226
|
}
|
|
@@ -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
|
-
|
|
6
|
-
socket =
|
|
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
|
-
|
|
33
|
-
|
|
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,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>;
|