@poolse/react 2.13.1 → 2.14.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to `@poolse/react` are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [semver](https://semver.org).
6
6
 
7
+ ## [2.14.1] — 2026-08-23
8
+
9
+ ### Added
10
+
11
+ - **`useCallRoom().refreshDevices`** — re-read the device list on
12
+ demand. Devices are already re-read on connect and on hardware
13
+ changes; this covers the case neither does, which is that a browser
14
+ only reveals device ids and labels once the page holds a matching
15
+ permission, and that moment is not subscribable. Call it when opening
16
+ a picker.
17
+
18
+ ### Fixed
19
+
20
+ - Device lists inherited the `MediaDeviceInfo` spread bug from
21
+ `@poolse/sdk`. See its 2.14.1 entry.
22
+
23
+ ## [2.14.0] — 2026-08-23
24
+
25
+ ### Added
26
+
27
+ - **`useCallRoom().devices` / `.activeDevices` / `.selectDevice`** —
28
+ camera, microphone and audio-output selection, refreshed as devices
29
+ come and go. Enumeration is deferred until the call connects, because
30
+ a browser withholds device labels from a page that does not yet hold
31
+ the matching permission.
32
+
7
33
  ## [2.13.1] — 2026-08-23
8
34
 
9
35
  Republished in lockstep with `@poolse/react-native@2.13.1`. No changes
package/dist/index.cjs CHANGED
@@ -1071,6 +1071,12 @@ function useCallRoom(conversationId, opts) {
1071
1071
  const [micEnabled, setMic] = react.useState(false);
1072
1072
  const [facingMode, setFacing] = react.useState(opts.facing ?? "user");
1073
1073
  const [alone, setAlone] = react.useState(false);
1074
+ const [devices, setDevices] = react.useState({
1075
+ videoinput: [],
1076
+ audioinput: [],
1077
+ audiooutput: []
1078
+ });
1079
+ const [activeDevices, setActiveDevices] = react.useState({});
1074
1080
  const [error, setError] = react.useState(null);
1075
1081
  const [sfuAvailable, setSfuAvailable] = react.useState(null);
1076
1082
  const optsRef = react.useRef(opts);
@@ -1086,6 +1092,21 @@ function useCallRoom(conversationId, opts) {
1086
1092
  ...optsRef.current.aloneGraceMs !== void 0 ? { aloneGraceMs: optsRef.current.aloneGraceMs } : {}
1087
1093
  });
1088
1094
  }, [poolse, conversationId]);
1095
+ const refreshDevices = react.useCallback((target) => {
1096
+ void Promise.all([
1097
+ target.listDevices("videoinput"),
1098
+ target.listDevices("audioinput"),
1099
+ target.listDevices("audiooutput")
1100
+ ]).then(([videoinput, audioinput, audiooutput]) => {
1101
+ setDevices({ videoinput, audioinput, audiooutput });
1102
+ const active = {};
1103
+ for (const kind of ["videoinput", "audioinput", "audiooutput"]) {
1104
+ const id = target.getActiveDevice(kind);
1105
+ if (id) active[kind] = id;
1106
+ }
1107
+ setActiveDevices(active);
1108
+ });
1109
+ }, []);
1089
1110
  const sync = react.useCallback((target) => {
1090
1111
  if (!target) return;
1091
1112
  setCamera(target.isCameraEnabled());
@@ -1107,18 +1128,20 @@ function useCallRoom(conversationId, opts) {
1107
1128
  sync(room);
1108
1129
  });
1109
1130
  const offAlone = room.onAlone(() => setAlone(true));
1131
+ const offDevices = room.onDevices(() => refreshDevices(room));
1110
1132
  const offError = room.onError(setError);
1111
1133
  return () => {
1112
1134
  offStatus();
1113
1135
  offRoster();
1114
1136
  offAlone();
1137
+ offDevices();
1115
1138
  offError();
1116
1139
  if (joined.current) {
1117
1140
  joined.current = false;
1118
1141
  room.leave();
1119
1142
  }
1120
1143
  };
1121
- }, [room, sync]);
1144
+ }, [room, sync, refreshDevices]);
1122
1145
  const join = react.useCallback(async () => {
1123
1146
  if (!room) return false;
1124
1147
  setError(null);
@@ -1127,13 +1150,14 @@ function useCallRoom(conversationId, opts) {
1127
1150
  joined.current = connected;
1128
1151
  setSfuAvailable(connected);
1129
1152
  sync(room);
1153
+ if (connected) refreshDevices(room);
1130
1154
  return connected;
1131
1155
  } catch (err) {
1132
1156
  setError(err instanceof Error ? err : new Error(String(err)));
1133
1157
  setSfuAvailable(true);
1134
1158
  return false;
1135
1159
  }
1136
- }, [room, sync]);
1160
+ }, [room, sync, refreshDevices]);
1137
1161
  const leave = react.useCallback(() => {
1138
1162
  if (!joined.current) return;
1139
1163
  joined.current = false;
@@ -1168,6 +1192,20 @@ function useCallRoom(conversationId, opts) {
1168
1192
  await room.switchCamera();
1169
1193
  setFacing(room.getFacingMode());
1170
1194
  }, [room]);
1195
+ const refresh = react.useCallback(() => {
1196
+ if (room) refreshDevices(room);
1197
+ }, [room, refreshDevices]);
1198
+ const selectDevice = react.useCallback(
1199
+ async (kind, deviceId) => {
1200
+ if (!room) return;
1201
+ await room.setActiveDevice(kind, deviceId);
1202
+ setActiveDevices((current) => ({
1203
+ ...current,
1204
+ [kind]: room.getActiveDevice(kind) ?? deviceId
1205
+ }));
1206
+ },
1207
+ [room]
1208
+ );
1171
1209
  return {
1172
1210
  status,
1173
1211
  participants,
@@ -1175,6 +1213,9 @@ function useCallRoom(conversationId, opts) {
1175
1213
  micEnabled,
1176
1214
  facingMode,
1177
1215
  alone,
1216
+ devices,
1217
+ activeDevices,
1218
+ refreshDevices: refresh,
1178
1219
  error,
1179
1220
  sfuAvailable,
1180
1221
  join,
@@ -1182,6 +1223,7 @@ function useCallRoom(conversationId, opts) {
1182
1223
  setCameraEnabled,
1183
1224
  toggleCamera,
1184
1225
  switchCamera,
1226
+ selectDevice,
1185
1227
  setMicrophoneEnabled,
1186
1228
  toggleMic
1187
1229
  };