kasunk99-livestream-core 0.1.1 → 0.1.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useViewerSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewerSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"useViewerSocket.d.ts","sourceRoot":"","sources":["../../src/hooks/useViewerSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7D,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,6EAA6E;IAC7E,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA+cpG"}
|
|
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { io } from 'socket.io-client';
|
|
3
3
|
import { getApiKey, getDisplayName } from '../config';
|
|
4
4
|
import { ensureMediasoupGlobals } from '../services/mediasoup-init';
|
|
5
|
-
import { getSignalingUrl } from '../services/livestream.service';
|
|
5
|
+
import { getSignalingUrl, mintViewerToken } from '../services/livestream.service';
|
|
6
6
|
/**
|
|
7
7
|
* Connects to the livestream signaling server and joins a room as viewer.
|
|
8
8
|
* When roomId changes, leaves the previous room and joins the new one (TikTok-style).
|
|
@@ -102,80 +102,105 @@ export function useViewerSocket(roomId) {
|
|
|
102
102
|
};
|
|
103
103
|
});
|
|
104
104
|
}, JOIN_TIMEOUT_MS);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
timeout: 10000,
|
|
112
|
-
...(apiKey
|
|
113
|
-
? {
|
|
114
|
-
auth: { apiKey },
|
|
115
|
-
extraHeaders: { 'x-api-key': apiKey },
|
|
116
|
-
}
|
|
117
|
-
: {}),
|
|
118
|
-
});
|
|
119
|
-
socketRef.current = socket;
|
|
120
|
-
socket.on('connect', () => {
|
|
121
|
-
socket.emit('join-room', {
|
|
122
|
-
roomId,
|
|
123
|
-
role: 'viewer',
|
|
124
|
-
displayName,
|
|
125
|
-
}, (res) => {
|
|
105
|
+
let socket = null;
|
|
106
|
+
(async () => {
|
|
107
|
+
// Server requires viewerToken for viewers (not merchant apiKey).
|
|
108
|
+
// We mint a short-lived viewer token via HTTP using the apiKey.
|
|
109
|
+
const apiKey = getApiKey();
|
|
110
|
+
if (!apiKey) {
|
|
126
111
|
clearTimeout(joinTimeout);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
112
|
+
setState((prev) => ({ ...prev, joining: false, joined: false, error: 'Livestream API key not configured' }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const tokenRes = await mintViewerToken({ roomId });
|
|
116
|
+
if (roomIdRef.current !== roomId)
|
|
117
|
+
return;
|
|
118
|
+
if ('error' in tokenRes) {
|
|
119
|
+
clearTimeout(joinTimeout);
|
|
120
|
+
setState((prev) => ({
|
|
121
|
+
...prev,
|
|
122
|
+
joining: false,
|
|
123
|
+
joined: false,
|
|
124
|
+
error: tokenRes.error || 'Failed to mint viewer token',
|
|
125
|
+
}));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Match web client (Client/src/streamService.js): websocket first, then polling.
|
|
129
|
+
socket = io(signalingUrl, {
|
|
130
|
+
transports: ['websocket', 'polling'],
|
|
131
|
+
reconnection: true,
|
|
132
|
+
autoConnect: true,
|
|
133
|
+
timeout: 10000,
|
|
134
|
+
auth: { viewerToken: tokenRes.token },
|
|
135
|
+
});
|
|
136
|
+
socketRef.current = socket;
|
|
137
|
+
socket.on('connect', () => {
|
|
138
|
+
socket?.emit('join-room', {
|
|
139
|
+
roomId,
|
|
140
|
+
role: 'viewer',
|
|
141
|
+
displayName,
|
|
142
|
+
}, (res) => {
|
|
143
|
+
clearTimeout(joinTimeout);
|
|
144
|
+
if (roomIdRef.current !== roomId)
|
|
145
|
+
return;
|
|
146
|
+
if (res?.error) {
|
|
147
|
+
setState((prev) => ({
|
|
148
|
+
...prev,
|
|
149
|
+
joining: false,
|
|
150
|
+
joined: false,
|
|
151
|
+
error: res.error ?? 'Failed to join',
|
|
152
|
+
}));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const iceFromJoin = res.iceServers;
|
|
156
|
+
if (Array.isArray(iceFromJoin) && iceFromJoin.length > 0) {
|
|
157
|
+
iceServersRef.current = iceFromJoin;
|
|
158
|
+
}
|
|
130
159
|
setState((prev) => ({
|
|
131
160
|
...prev,
|
|
132
161
|
joining: false,
|
|
133
|
-
joined:
|
|
134
|
-
|
|
162
|
+
joined: true,
|
|
163
|
+
producerList: res.producerList ?? [],
|
|
164
|
+
roomState: res.roomState ?? null,
|
|
165
|
+
rtpCapabilities: res.rtpCapabilities ?? null,
|
|
166
|
+
error: null,
|
|
135
167
|
}));
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
iceServersRef.current = iceFromJoin;
|
|
141
|
-
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
socket.on('connect_error', (err) => {
|
|
171
|
+
clearTimeout(joinTimeout);
|
|
142
172
|
setState((prev) => ({
|
|
143
173
|
...prev,
|
|
144
174
|
joining: false,
|
|
145
|
-
joined:
|
|
146
|
-
|
|
147
|
-
roomState: res.roomState ?? null,
|
|
148
|
-
rtpCapabilities: res.rtpCapabilities ?? null,
|
|
149
|
-
error: null,
|
|
175
|
+
joined: false,
|
|
176
|
+
error: err?.message ?? 'Connection failed',
|
|
150
177
|
}));
|
|
151
178
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
joined: false,
|
|
159
|
-
error: err?.message ?? 'Connection failed',
|
|
160
|
-
}));
|
|
161
|
-
});
|
|
162
|
-
socket.on('ice-servers', (servers) => {
|
|
163
|
-
iceServersRef.current = Array.isArray(servers) ? servers : [];
|
|
164
|
-
});
|
|
165
|
-
socket.on('room-updated', (payload) => {
|
|
166
|
-
if (payload?.producerList) {
|
|
167
|
-
setState((prev) => ({ ...prev, producerList: payload.producerList ?? [] }));
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
socket.on('new-producer', () => {
|
|
171
|
-
socket.emit('get-producers', null, (resp) => {
|
|
172
|
-
if (resp?.producerList) {
|
|
173
|
-
setState((prev) => ({ ...prev, producerList: resp.producerList ?? [] }));
|
|
179
|
+
socket.on('ice-servers', (servers) => {
|
|
180
|
+
iceServersRef.current = Array.isArray(servers) ? servers : [];
|
|
181
|
+
});
|
|
182
|
+
socket.on('room-updated', (payload) => {
|
|
183
|
+
if (payload?.producerList) {
|
|
184
|
+
setState((prev) => ({ ...prev, producerList: payload.producerList ?? [] }));
|
|
174
185
|
}
|
|
175
186
|
});
|
|
176
|
-
|
|
187
|
+
socket.on('new-producer', () => {
|
|
188
|
+
socket?.emit('get-producers', null, (resp) => {
|
|
189
|
+
if (resp?.producerList) {
|
|
190
|
+
setState((prev) => ({ ...prev, producerList: resp.producerList ?? [] }));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
})();
|
|
177
195
|
return () => {
|
|
178
196
|
clearTimeout(joinTimeout);
|
|
197
|
+
try {
|
|
198
|
+
socket?.removeAllListeners();
|
|
199
|
+
socket?.disconnect();
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// ignore
|
|
203
|
+
}
|
|
179
204
|
disconnect();
|
|
180
205
|
};
|
|
181
206
|
}, [roomId, displayName, disconnect]);
|
|
@@ -9,7 +9,18 @@ export type FetchStreamsResult = {
|
|
|
9
9
|
streams: [];
|
|
10
10
|
error: string;
|
|
11
11
|
};
|
|
12
|
+
export type MintViewerTokenResult = {
|
|
13
|
+
token: string;
|
|
14
|
+
error?: never;
|
|
15
|
+
} | {
|
|
16
|
+
token?: never;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
12
19
|
export declare function fetchActiveStreams(): Promise<FetchStreamsResult>;
|
|
20
|
+
export declare function mintViewerToken(params: {
|
|
21
|
+
roomId: string;
|
|
22
|
+
viewerId?: string | null;
|
|
23
|
+
}): Promise<MintViewerTokenResult>;
|
|
13
24
|
export declare function getSignalingUrl(): string;
|
|
14
25
|
export declare function getSignalingWsUrl(): string;
|
|
15
26
|
//# sourceMappingURL=livestream.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"livestream.service.d.ts","sourceRoot":"","sources":["../../src/services/livestream.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC5C;IAAE,OAAO,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA6BtE;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
1
|
+
{"version":3,"file":"livestream.service.d.ts","sourceRoot":"","sources":["../../src/services/livestream.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAC1B;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAC5C;IAAE,OAAO,EAAE,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnC,MAAM,MAAM,qBAAqB,GAC7B;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAChC;IAAE,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA6BtE;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyBjC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAGD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
|
|
@@ -33,6 +33,39 @@ export async function fetchActiveStreams() {
|
|
|
33
33
|
return { streams: [], error: message };
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
export async function mintViewerToken(params) {
|
|
37
|
+
const base = getBaseUrl();
|
|
38
|
+
if (!base)
|
|
39
|
+
return { error: 'Livestream server not configured' };
|
|
40
|
+
const apiKey = getApiKey();
|
|
41
|
+
if (!apiKey)
|
|
42
|
+
return { error: 'Livestream API key not configured' };
|
|
43
|
+
if (!params.roomId)
|
|
44
|
+
return { error: 'roomId required' };
|
|
45
|
+
try {
|
|
46
|
+
const url = `${base.replace(/\/$/, '')}/api/viewer-token`;
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'content-type': 'application/json',
|
|
51
|
+
'x-api-key': apiKey,
|
|
52
|
+
},
|
|
53
|
+
body: JSON.stringify({ roomId: params.roomId, viewerId: params.viewerId ?? null }),
|
|
54
|
+
});
|
|
55
|
+
if (!res.ok)
|
|
56
|
+
return { error: `HTTP ${res.status}` };
|
|
57
|
+
const data = (await res.json());
|
|
58
|
+
if (data?.error)
|
|
59
|
+
return { error: data.error };
|
|
60
|
+
if (!data?.token)
|
|
61
|
+
return { error: 'Missing token in response' };
|
|
62
|
+
return { token: data.token };
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
const message = e instanceof Error ? e.message : 'Unknown error';
|
|
66
|
+
return { error: message };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
36
69
|
export function getSignalingUrl() {
|
|
37
70
|
return getUrlFromConfig();
|
|
38
71
|
}
|
package/package.json
CHANGED