@poolse/react 2.13.0 → 2.14.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/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ 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.0] — 2026-08-23
8
+
9
+ ### Added
10
+
11
+ - **`useCallRoom().devices` / `.activeDevices` / `.selectDevice`** —
12
+ camera, microphone and audio-output selection, refreshed as devices
13
+ come and go. Enumeration is deferred until the call connects, because
14
+ a browser withholds device labels from a page that does not yet hold
15
+ the matching permission.
16
+
17
+ ## [2.13.1] — 2026-08-23
18
+
19
+ Republished in lockstep with `@poolse/react-native@2.13.1`. No changes
20
+ to this package.
21
+
7
22
  ## [2.13.0] — 2026-08-23
8
23
 
9
24
  ### Fixed
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,17 @@ function useCallRoom(conversationId, opts) {
1168
1192
  await room.switchCamera();
1169
1193
  setFacing(room.getFacingMode());
1170
1194
  }, [room]);
1195
+ const selectDevice = react.useCallback(
1196
+ async (kind, deviceId) => {
1197
+ if (!room) return;
1198
+ await room.setActiveDevice(kind, deviceId);
1199
+ setActiveDevices((current) => ({
1200
+ ...current,
1201
+ [kind]: room.getActiveDevice(kind) ?? deviceId
1202
+ }));
1203
+ },
1204
+ [room]
1205
+ );
1171
1206
  return {
1172
1207
  status,
1173
1208
  participants,
@@ -1175,6 +1210,8 @@ function useCallRoom(conversationId, opts) {
1175
1210
  micEnabled,
1176
1211
  facingMode,
1177
1212
  alone,
1213
+ devices,
1214
+ activeDevices,
1178
1215
  error,
1179
1216
  sfuAvailable,
1180
1217
  join,
@@ -1182,6 +1219,7 @@ function useCallRoom(conversationId, opts) {
1182
1219
  setCameraEnabled,
1183
1220
  toggleCamera,
1184
1221
  switchCamera,
1222
+ selectDevice,
1185
1223
  setMicrophoneEnabled,
1186
1224
  toggleMic
1187
1225
  };