@daily-co/daily-react
Version:
Daily React makes it easier to integrate [@daily-co/daily-js](https://www.npmjs.com/package/@daily-co/daily-js) in React applications.
1,269 lines (1,247 loc) • 134 kB
JavaScript
import { atomFamily, useAtomCallback } from 'jotai/utils';
import React, { createContext, useContext, useDebugValue, useState, useRef, useMemo, useEffect, useCallback, memo, forwardRef, useImperativeHandle } from 'react';
import { atom, useAtomValue, Provider, useAtom } from 'jotai';
import throttle from 'lodash.throttle';
import Daily from '@daily-co/daily-js';
import deepEqual from 'fast-deep-equal';
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelperLoose(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (it) return (it = it.call(o)).next.bind(it);
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
return function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var DailyContext = /*#__PURE__*/createContext(null);
/**
* Returns callObject instance passed to or created by closest <DailyProvider>.
*/
var useDaily = function useDaily() {
var daily = useContext(DailyContext);
useDebugValue(daily);
return daily;
};
var DailyEventContext = /*#__PURE__*/createContext({
on: function on() {},
off: function off() {}
});
var priorityCounter = -1;
var getPriorityUnique = function getPriorityUnique() {
return priorityCounter--;
};
var uniqueCounter = 1;
var getUnique = function getUnique() {
return uniqueCounter++;
};
/**
* Sets up a daily event listener using [on](https://docs.daily.co/reference/daily-js/instance-methods/on) method.
* When this hook is unmounted the event listener is unregistered using [off](https://docs.daily.co/reference/daily-js/instance-methods/off).
*
* Warning: callback has to be a memoized reference (e.g. via [useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)).
* Otherwise a console error might be thrown indicating a re-render loop issue.
*
* @param ev The DailyEvent to register.
* @param callback A memoized callback reference to run when the event is emitted.
*/
var useDailyEvent = function useDailyEvent(ev, callback, INTERNAL_priority) {
if (INTERNAL_priority === void 0) {
INTERNAL_priority = false;
}
var _useContext = useContext(DailyEventContext),
off = _useContext.off,
on = _useContext.on;
var _useState = useState(false),
isBlocked = _useState[0],
setIsBlocked = _useState[1];
var reassignCount = useRef(0);
var eventId = useMemo(function () {
return INTERNAL_priority ? getPriorityUnique() : getUnique();
}, [INTERNAL_priority]);
useEffect(function () {
if (!ev || isBlocked) return;
/**
* Check if callback has been reassigned often enough without hitting the 50ms timeout.
*/
if (reassignCount.current > 100000) {
console.error("useDailyEvent called with potentially non-memoized event callback or due to too many re-renders.\n Memoize using useCallback to avoid re-render loop or reduce the amount of state transitions the callback depends on.\n Passed callback for '" + ev + "' event is NOT registered.", callback);
setIsBlocked(true);
return;
}
reassignCount.current++;
var timeout = setTimeout(function () {
reassignCount.current = 0;
}, 50);
on(ev, callback, eventId);
return function () {
clearTimeout(timeout);
off(ev, eventId);
};
}, [callback, ev, eventId, isBlocked, off, on]);
useDebugValue({
event: ev,
eventId: eventId,
isBlocked: isBlocked,
callback: callback
});
};
/**
* Compares two variables for deep equality.
* Gracefully handles equality checks on MediaStreamTracks by comparing their ids.
*/
function customDeepEqual(a, b) {
if (a === b) return true;
// Handle arrays separately
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; i++) {
if (!customDeepEqual(a[i], b[i])) return false;
}
return true;
}
// Handle specific cases like MediaStream, MediaStreamTrack, Date, etc.
if (MediaStream) {
if (a instanceof MediaStream && b instanceof MediaStream) {
return a.id === b.id && a.active === b.active && a.getTracks().length === b.getTracks().length && a.getTracks().every(function (track, idx) {
return customDeepEqual(track, b.getTracks()[idx]);
});
}
}
// Handle special case for MediaStreamTrack
if (MediaStreamTrack) {
if (a instanceof MediaStreamTrack && b instanceof MediaStreamTrack) {
return a.id === b.id && a.kind === b.kind && a.readyState === b.readyState;
}
}
// Handle special case for Date
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
// Handle special case for RegExp
if (a instanceof RegExp && b instanceof RegExp) {
return a.source === b.source && a.flags === b.flags;
}
// Handle Set comparisons
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size) return false;
var arrA = Array.from(a).sort();
var arrB = Array.from(b).sort();
return arrA.every(function (val, idx) {
return customDeepEqual(val, arrB[idx]);
});
}
// Handle Map comparisons
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) return false;
for (var _iterator = _createForOfIteratorHelperLoose(a.entries()), _step; !(_step = _iterator()).done;) {
var _step$value = _step.value,
key = _step$value[0],
value = _step$value[1];
if (!b.has(key) || !customDeepEqual(value, b.get(key))) return false;
}
return true;
}
// Primitive types and null checks
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
return false;
}
// Generic object handling
var keysA = Object.keys(a);
var keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (var _i = 0; _i < keysA.length; _i++) {
var _key = keysA[_i];
if (!Object.prototype.hasOwnProperty.call(b, _key) || !customDeepEqual(a[_key], b[_key])) {
return false;
}
}
// All keys and values match -> the objects are deeply equal
return true;
}
/**
* Comparison function optimized for comparing arrays.
*/
function arraysDeepEqual(a, b) {
// Check for reference equality
if (a === b) return true;
// Check if both arrays are of the same length
if (a.length !== b.length) return false;
// Compare each element in the array
for (var i = 0; i < a.length; i++) {
var valueA = a[i];
var valueB = b[i];
var isComplexTypeA = valueA !== null && typeof valueA === 'object';
var isComplexTypeB = valueB !== null && typeof valueB === 'object';
// Use customDeepEqual only if either value is a complex type
if (isComplexTypeA || isComplexTypeB) {
if (!customDeepEqual(valueA, valueB)) return false;
} else if (valueA !== valueB) {
return false;
}
}
return true;
}
function jotaiDebugLabel(label) {
return 'daily-react-' + label;
}
function equalAtomFamily(options) {
var atomCache = new Map();
var priorValues = new Map();
return function (param) {
if (!atomCache.has(param)) {
var baseAtom = atom(function (get) {
var derivedValue = options.get(param)(get);
var prior = priorValues.get(param);
if (prior != null && options.equals(derivedValue, prior)) {
return prior;
}
priorValues.set(param, derivedValue);
return derivedValue;
});
atomCache.set(param, baseAtom);
}
return atomCache.get(param);
};
}
var DELIM = '::';
var PATHS_DELIM = ';';
var getPropertyParam = function getPropertyParam(id, property) {
return id + DELIM + property;
};
var getPropertiesParam = function getPropertiesParam(id, properties) {
return id + DELIM + properties.join(PATHS_DELIM);
};
var getParticipantPropertyAtom = function getParticipantPropertyAtom(id, property) {
return participantPropertyState(getPropertyParam(id, property));
};
/**
* Stores all property paths for a given participant.
*/
var participantPropertyPathsState = /*#__PURE__*/atomFamily(function (id) {
var participantPropertyPathsAtom = atom([]);
participantPropertyPathsAtom.debugLabel = jotaiDebugLabel("participant-property-paths-" + id);
return participantPropertyPathsAtom;
});
/**
* Stores resolved values for each participant and property path.
*/
var participantPropertyState = /*#__PURE__*/atomFamily(function (param) {
var participantPropertyAtom = atom(null);
participantPropertyAtom.debugLabel = jotaiDebugLabel("participant-property-" + param);
return participantPropertyAtom;
});
/**
* Stores resolved values for each participant and property path.
*/
var participantPropertiesState = /*#__PURE__*/equalAtomFamily({
equals: arraysDeepEqual,
get: function get(param) {
return function (get) {
var _param$split = param.split(DELIM),
id = _param$split[0],
paths = _param$split[1];
var properties = paths.split(PATHS_DELIM);
return properties.map(function (path) {
return get(getParticipantPropertyAtom(id, path));
});
};
}
});
/**
* Returns a participant's property that you subscribe to.
* @param participantId The participant's session_id.
* @param propertyPaths the array of participant property that you want to subscribe to.
*/
var useParticipantProperty = function useParticipantProperty(participantId, propertyPaths) {
var _ref;
var properties = useAtomValue(Array.isArray(propertyPaths) ? participantPropertiesState(getPropertiesParam(participantId, propertyPaths)) : participantPropertyState(getPropertyParam(participantId, propertyPaths)));
useDebugValue(Array.isArray(propertyPaths) ? propertyPaths.reduce(function (o, path, i) {
o[path] = properties[i];
return o;
}, {}) : (_ref = {}, _ref[propertyPaths] = properties, _ref));
return properties;
};
/**
* Sets up a throttled daily event listener using [on](https://docs.daily.co/reference/daily-js/instance-methods/on) method.
* When this hook is unmounted the event listener is unregistered using [off](https://docs.daily.co/reference/daily-js/instance-methods/off).
*
* In comparison to useDailyEvent the callback passed here will be called with an array of event objects.
*
* You can pass an array of DailyEvents to register multiple daily events with a single callback handler.
* The events returned in the callback parameter are guaranteed to be in the same order as they were emitted.
*
* @param ev The DailyEvent to register or an array of DailyEvent to register.
* @param callback A memoized callback reference to run when throttled events are emitted.
* @param throttleTimeout The minimum waiting time until the callback is called again. Default: 500
*/
var useThrottledDailyEvent = function useThrottledDailyEvent(ev, callback, throttleTimeout, INTERNAL_priority) {
if (throttleTimeout === void 0) {
throttleTimeout = 500;
}
if (INTERNAL_priority === void 0) {
INTERNAL_priority = false;
}
var _useContext = useContext(DailyEventContext),
off = _useContext.off,
on = _useContext.on;
var eventId = useMemo(function () {
var _ref;
if (Array.isArray(ev)) return ev.reduce(function (r, e) {
r[e] = INTERNAL_priority ? getPriorityUnique() : getUnique();
return r;
}, {});
return _ref = {}, _ref[ev] = INTERNAL_priority ? getPriorityUnique() : getUnique(), _ref;
}, [ev, INTERNAL_priority]);
var throttledEvents = useRef([]);
useDailyEvent('call-instance-destroyed', useCallback(function () {
throttledEvents.current.length = 0;
}, []));
var emitEvents = useMemo(function () {
return throttle(function () {
if (throttledEvents.current.length === 0) return;
callback(throttledEvents.current);
throttledEvents.current.length = 0;
}, throttleTimeout, {
trailing: true
});
}, [callback, throttleTimeout]);
useEffect(function () {
if (!ev) return;
var addEvent = function addEvent(ev) {
throttledEvents.current.push(ev);
emitEvents();
};
if (Array.isArray(ev)) {
ev.forEach(function (e) {
return on(e, addEvent, eventId[e]);
});
} else {
on(ev, addEvent, eventId[ev]);
}
return function () {
if (Array.isArray(ev)) {
ev.forEach(function (e) {
return off(e, eventId[e]);
});
} else {
off(ev, eventId[ev]);
}
};
}, [emitEvents, ev, eventId, off, on]);
useDebugValue({
event: ev,
eventId: eventId
});
};
/**
* Returns all property paths for an object.
*/
var getPaths = function getPaths(o, currentPath, visited) {
if (currentPath === void 0) {
currentPath = '';
}
if (visited === void 0) {
visited = new Set();
}
if (typeof o !== 'object' || o === null || visited.has(o)) {
return [currentPath];
}
visited.add(o);
var paths = [];
for (var key in o) {
if (Object.prototype.hasOwnProperty.call(o, key)) {
var newPath = currentPath ? currentPath + "." + key : key;
paths.push.apply(paths, [newPath].concat(getPaths(o[key], newPath, visited)));
}
}
visited["delete"](o);
return paths;
};
/**
* Returns all property paths for a given participant object.
*/
var getParticipantPaths = function getParticipantPaths(p) {
return getPaths(p);
};
var resolvePath = function resolvePath(participant, path) {
return String(path).split('.').filter(function (key) {
return key.length;
}).reduce(function (p, key) {
return p && p[key];
}, participant);
};
var resolveParticipantPaths = function resolveParticipantPaths(participant, paths) {
return paths.map(function (path) {
return resolvePath(participant, path);
});
};
/**
* Stores the most recent peerId as reported from [active-speaker-change](https://docs.daily.co/reference/daily-js/events/meeting-events#active-speaker-change) event.
*/
var activeIdState = /*#__PURE__*/atom(null);
activeIdState.debugLabel = /*#__PURE__*/jotaiDebugLabel('active-id');
var localIdState = /*#__PURE__*/atom('');
localIdState.debugLabel = /*#__PURE__*/jotaiDebugLabel('local-id');
var localJoinDateState = /*#__PURE__*/atom(null);
localJoinDateState.debugLabel = /*#__PURE__*/jotaiDebugLabel('local-join-date');
var participantIdsState = /*#__PURE__*/atom([]);
participantIdsState.debugLabel = /*#__PURE__*/jotaiDebugLabel('participant-ids');
var participantState = /*#__PURE__*/atomFamily(function (id) {
var participantAtom = atom(null);
participantAtom.debugLabel = jotaiDebugLabel("participant-" + id);
return participantAtom;
});
var waitingParticipantsState = /*#__PURE__*/atom([]);
waitingParticipantsState.debugLabel = /*#__PURE__*/jotaiDebugLabel('waiting-participants');
var waitingParticipantState = /*#__PURE__*/atomFamily(function (id) {
var waitingParticipantAtom = atom({
awaitingAccess: {
level: 'full'
},
id: id,
name: ''
});
waitingParticipantAtom.debugLabel = jotaiDebugLabel("waiting-participant-" + id);
return waitingParticipantAtom;
});
var allWaitingParticipantsSelector = /*#__PURE__*/equalAtomFamily({
equals: arraysDeepEqual,
get: function get() {
return function (get) {
var ids = get(waitingParticipantsState);
return ids.map(function (id) {
return get(waitingParticipantState(id));
});
};
}
});
var DailyParticipants = function DailyParticipants(_ref) {
var children = _ref.children;
var daily = useDaily();
var _useState = useState(false),
initialized = _useState[0],
setInitialized = _useState[1];
var initParticipants = useAtomCallback(useCallback(function (_get, set, participants) {
set(localIdState, participants.local.session_id);
var participantsArray = Object.values(participants);
var ids = participantsArray.map(function (p) {
return p.session_id;
});
set(participantIdsState, ids);
participantsArray.forEach(function (p) {
set(participantState(p.session_id), p);
var paths = getParticipantPaths(p);
set(participantPropertyPathsState(p.session_id), paths);
paths.forEach(function (property) {
var _resolveParticipantPa = resolveParticipantPaths(p, [property]),
value = _resolveParticipantPa[0];
set(getParticipantPropertyAtom(p.session_id, property), value);
});
});
setInitialized(true);
}, []));
/**
* Initialize participants state based on daily.participants().
* Retries every 100ms to initialize the state, until daily is ready.
*/
useEffect(function () {
if (!daily || initialized) return;
var interval = setInterval(function () {
var participants = daily.participants();
if (!('local' in participants)) return;
initParticipants(participants);
clearInterval(interval);
}, 100);
return function () {
clearInterval(interval);
};
}, [daily, initialized, initParticipants]);
var handleInitEvent = useCallback(function () {
if (!daily) return;
var participants = daily == null ? void 0 : daily.participants();
if (!participants.local) return;
initParticipants(participants);
}, [daily, initParticipants]);
useDailyEvent('started-camera', handleInitEvent, true);
useDailyEvent('access-state-updated', handleInitEvent, true);
useDailyEvent('joining-meeting', useAtomCallback(useCallback(function (_get, set) {
set(localJoinDateState, new Date());
handleInitEvent();
}, [handleInitEvent])), true);
useDailyEvent('joined-meeting', useCallback(function (ev) {
initParticipants(ev.participants);
}, [initParticipants]), true);
/**
* Reset stored participants, when meeting has ended.
*/
var handleCleanup = useAtomCallback(useCallback(function (get, set) {
set(localIdState, '');
set(activeIdState, null);
var ids = get(participantIdsState);
ids.forEach(function (id) {
return participantState.remove(id);
});
set(participantIdsState, []);
}, []));
useDailyEvent('call-instance-destroyed', handleCleanup, true);
useDailyEvent('left-meeting', handleCleanup, true);
useThrottledDailyEvent(['active-speaker-change', 'participant-joined', 'participant-updated', 'participant-left'], useAtomCallback(useCallback(function (get, set, evts) {
if (!evts.length) return;
evts.forEach(function (ev) {
switch (ev.action) {
case 'active-speaker-change':
{
set(activeIdState, ev.activeSpeaker.peerId);
set(participantState(ev.activeSpeaker.peerId), function (prev) {
if (!prev) return null;
return _extends({}, prev, {
last_active: new Date()
});
});
break;
}
case 'participant-joined':
{
// Update list of ids
set(participantIdsState, function (prevIds) {
return prevIds.includes(ev.participant.session_id) ? prevIds : [].concat(prevIds, [ev.participant.session_id]);
});
// Store entire object
set(participantState(ev.participant.session_id), ev.participant);
var paths = getParticipantPaths(ev.participant);
// Set list of property paths
set(participantPropertyPathsState(ev.participant.session_id), paths);
// Set all property path values
paths.forEach(function (property) {
var _resolveParticipantPa2 = resolveParticipantPaths(ev.participant, [property]),
value = _resolveParticipantPa2[0];
set(getParticipantPropertyAtom(ev.participant.session_id, property), value);
});
break;
}
case 'participant-updated':
{
// Update entire object
set(participantState(ev.participant.session_id), ev.participant);
// Update local session_id
if (ev.participant.local) {
set(localIdState, ev.participant.session_id);
}
var _paths = getParticipantPaths(ev.participant);
var oldPaths = get(participantPropertyPathsState(ev.participant.session_id));
var pathsChanged = _paths.length !== oldPaths.length || _paths.some(function (path) {
return !oldPaths.includes(path);
});
// Set list of property paths
if (pathsChanged) {
set(participantPropertyPathsState(ev.participant.session_id), _paths);
}
// Create a Set of oldPaths for quick lookup
var oldPathSet = new Set(oldPaths);
// Resolve all path values in one call
var resolvedValues = resolveParticipantPaths(ev.participant, _paths);
_paths.forEach(function (property, idx) {
var value = resolvedValues[idx];
// Remove property from oldPathSet to mark it as processed
oldPathSet["delete"](property);
// Only update if the new value differs from the current one
set(getParticipantPropertyAtom(ev.participant.session_id, property), function (prev) {
return customDeepEqual(prev, value) ? prev : value;
});
});
// Set any remaining paths in oldPathSet to null
oldPathSet.forEach(function (property) {
set(getParticipantPropertyAtom(ev.participant.session_id, property), null);
});
break;
}
case 'participant-left':
{
// Remove from list of ids
set(participantIdsState, function (prevIds) {
return prevIds.includes(ev.participant.session_id) ? prevIds.filter(function (id) {
return id !== ev.participant.session_id;
}) : prevIds;
});
// Remove entire object
participantState.remove(ev.participant.session_id);
var _oldPaths = get(participantPropertyPathsState(ev.participant.session_id));
// Remove property path values
_oldPaths.forEach(function (property) {
participantPropertyState.remove(getPropertyParam(ev.participant.session_id, property));
});
// Remove all property paths
participantPropertyPathsState.remove(ev.participant.session_id);
break;
}
}
});
}, [])), 100, true);
useThrottledDailyEvent(['waiting-participant-added', 'waiting-participant-updated', 'waiting-participant-removed'], useAtomCallback(useCallback(function (_get, set, evts) {
evts.forEach(function (ev) {
switch (ev.action) {
case 'waiting-participant-added':
set(waitingParticipantsState, function (wps) {
return wps.includes(ev.participant.id) ? wps : [].concat(wps, [ev.participant.id]);
});
set(waitingParticipantState(ev.participant.id), ev.participant);
break;
case 'waiting-participant-updated':
set(waitingParticipantState(ev.participant.id), ev.participant);
break;
case 'waiting-participant-removed':
set(waitingParticipantsState, function (wps) {
return wps.filter(function (wp) {
return wp !== ev.participant.id;
});
});
waitingParticipantState.remove(ev.participant.id);
break;
}
});
}, [])), 100, true);
return React.createElement(React.Fragment, null, children);
};
/**
* Returns the local participant's session_id or empty string '',
* if the local participant doesn't exist.
*/
var useLocalSessionId = function useLocalSessionId() {
var localId = useAtomValue(localIdState);
useDebugValue(localId);
return localId;
};
var noopFilter = function noopFilter() {
return true;
};
/**
* Returns the most recent speaker id mentioned in an [active-speaker-change](https://docs.daily.co/reference/daily-js/events/meeting-events#active-speaker-change) event.
*/
var useActiveSpeakerId = function useActiveSpeakerId(_temp) {
var _ref = _temp === void 0 ? {} : _temp,
_ref$filter = _ref.filter,
filter = _ref$filter === void 0 ? noopFilter : _ref$filter,
_ref$ignoreLocal = _ref.ignoreLocal,
ignoreLocal = _ref$ignoreLocal === void 0 ? false : _ref$ignoreLocal;
var localSessionId = useLocalSessionId();
var recentActiveId = useAtomValue(activeIdState);
var isIgnoredLocalId = ignoreLocal && recentActiveId === localSessionId;
var isFilteredOut = !(filter != null && filter(recentActiveId));
var isRecentIdRelevant = !isIgnoredLocalId && !isFilteredOut;
var _useState = useState(isRecentIdRelevant ? recentActiveId : null),
activeId = _useState[0],
setActiveId = _useState[1];
useEffect(function () {
if (isIgnoredLocalId || isFilteredOut) return;
setActiveId(recentActiveId);
}, [isFilteredOut, isIgnoredLocalId, recentActiveId]);
useDebugValue(activeId);
return activeId;
};
var isTrackOff = function isTrackOff(trackState) {
return ['blocked', 'off'].includes(trackState);
};
var SERIALIZABLE_DELIM = ';';
var getParticipantIdsFilterSortParam = function getParticipantIdsFilterSortParam(filter, sort) {
return "" + filter + SERIALIZABLE_DELIM + sort;
};
/**
* Short-cut state selector for useParticipantIds({ filter: 'local' })
*/
var participantIdsFilteredAndSortedState = /*#__PURE__*/equalAtomFamily({
equals: customDeepEqual,
get: function get(param) {
return function (get) {
var _param$split = param.split(SERIALIZABLE_DELIM),
filter = _param$split[0],
sort = _param$split[1];
var ids = get(participantIdsState);
return ids.filter(function (id) {
switch (filter) {
/**
* Simple boolean fields first.
*/
case 'local':
case 'owner':
case 'record':
{
return get(getParticipantPropertyAtom(id, filter));
}
case 'remote':
{
return !get(getParticipantPropertyAtom(id, 'local'));
}
case 'screen':
{
var screenAudioState = get(getParticipantPropertyAtom(id, 'tracks.screenAudio.state'));
var screenVideoState = get(getParticipantPropertyAtom(id, 'tracks.screenVideo.state'));
return !isTrackOff(screenAudioState) || !isTrackOff(screenVideoState);
}
default:
return true;
}
}).sort(function (idA, idB) {
switch (sort) {
case 'joined_at':
case 'session_id':
case 'user_id':
case 'user_name':
{
var aSort = get(getParticipantPropertyAtom(idA, sort));
var bSort = get(getParticipantPropertyAtom(idB, sort));
if (aSort !== undefined || bSort !== undefined) {
if (aSort === undefined) return -1;
if (bSort === undefined) return 1;
if (aSort > bSort) return 1;
if (aSort < bSort) return -1;
}
return 0;
}
default:
return 0;
}
});
};
}
});
/**
* Returns a list of participant ids (= session_id).
* The list can optionally be filtered and sorted, using the filter and sort options.
*/
var useParticipantIds = function useParticipantIds(_temp) {
var _ref = _temp === void 0 ? {} : _temp,
filter = _ref.filter,
onActiveSpeakerChange = _ref.onActiveSpeakerChange,
onParticipantJoined = _ref.onParticipantJoined,
onParticipantLeft = _ref.onParticipantLeft,
onParticipantUpdated = _ref.onParticipantUpdated,
sort = _ref.sort;
/**
* For instances of useParticipantIds with string-based filter and sort,
* we can immediately return the correct ids from Jotai's state.
*/
var preFilteredSortedIds = useAtomValue(participantIdsFilteredAndSortedState(getParticipantIdsFilterSortParam(typeof filter === 'string' ? filter : null, typeof sort === 'string' ? sort : null)));
var shouldUseCustomIds = typeof filter === 'function' || typeof sort === 'function';
var getCustomFilteredIds = useCallback(function (get) {
if (
// Ignore if both filter and sort are not functions.
typeof filter !== 'function' && typeof sort !== 'function') return [];
var participants = preFilteredSortedIds.map(function (id) {
return get(participantState(id));
});
return participants
// Make sure we don't accidentally try to filter/sort `null` participants
// This can happen when a participant's id is already present in store
// but the participant object is not stored, yet.
.filter(function (p) {
return Boolean(p);
})
// Run custom filter, if it's a function. Otherwise don't filter any participants.
.filter(typeof filter === 'function' ? filter : function () {
return true;
})
// Run custom sort, if it's a function. Otherwise don't sort.
.sort(typeof sort === 'function' ? sort : function () {
return 0;
})
// Map back to session_id.
.map(function (p) {
return p.session_id;
})
// Filter any potential null/undefined ids.
// This shouldn't really happen, but better safe than sorry.
.filter(Boolean);
}, [filter, preFilteredSortedIds, sort]);
var _useState = useState([]),
customIds = _useState[0],
setCustomIds = _useState[1];
var maybeUpdateCustomIds = useAtomCallback(useCallback(function (get) {
if (!shouldUseCustomIds) return;
var newIds = getCustomFilteredIds(get);
if (customDeepEqual(newIds, customIds)) return;
setCustomIds(newIds);
}, [customIds, getCustomFilteredIds, shouldUseCustomIds]));
useEffect(function () {
maybeUpdateCustomIds();
}, [maybeUpdateCustomIds]);
useThrottledDailyEvent(['participant-joined', 'participant-updated', 'active-speaker-change', 'participant-left'], useCallback(function (evts) {
if (!evts.length) return;
evts.forEach(function (ev) {
switch (ev.action) {
case 'participant-joined':
onParticipantJoined == null ? void 0 : onParticipantJoined(ev);
break;
case 'participant-updated':
onParticipantUpdated == null ? void 0 : onParticipantUpdated(ev);
break;
case 'active-speaker-change':
onActiveSpeakerChange == null ? void 0 : onActiveSpeakerChange(ev);
break;
case 'participant-left':
onParticipantLeft == null ? void 0 : onParticipantLeft(ev);
break;
}
});
maybeUpdateCustomIds();
}, [maybeUpdateCustomIds, onActiveSpeakerChange, onParticipantJoined, onParticipantLeft, onParticipantUpdated]));
var result = typeof filter === 'function' || typeof sort === 'function' ? customIds : preFilteredSortedIds;
useDebugValue(result);
return result;
};
var screenSharesState = /*#__PURE__*/equalAtomFamily({
equals: arraysDeepEqual,
get: function get() {
return function (get) {
var screenIds = get(participantIdsFilteredAndSortedState(getParticipantIdsFilterSortParam('screen', null)));
return screenIds.map(function (id) {
return {
local: get(getParticipantPropertyAtom(id, 'local')),
screenAudio: get(getParticipantPropertyAtom(id, 'tracks.screenAudio')),
screenVideo: get(getParticipantPropertyAtom(id, 'tracks.screenVideo')),
screenId: id + "-screen",
session_id: id
};
});
};
}
});
/**
* Allows access to information about shared screens, and methods to start or stop a local screen share.
*/
var useScreenShare = function useScreenShare(_temp) {
var _ref = _temp === void 0 ? {} : _temp,
onError = _ref.onError,
onLocalScreenShareStarted = _ref.onLocalScreenShareStarted,
onLocalScreenShareStopped = _ref.onLocalScreenShareStopped;
var daily = useDaily();
var startScreenShare = useCallback(function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
daily == null ? void 0 : daily.startScreenShare.apply(daily, args);
}, [daily]);
var stopScreenShare = useCallback(function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
daily == null ? void 0 : daily.stopScreenShare.apply(daily, args);
}, [daily]);
useDailyEvent('local-screen-share-started', useCallback(function () {
return onLocalScreenShareStarted == null ? void 0 : onLocalScreenShareStarted();
}, [onLocalScreenShareStarted]));
useDailyEvent('local-screen-share-stopped', useCallback(function () {
return onLocalScreenShareStopped == null ? void 0 : onLocalScreenShareStopped();
}, [onLocalScreenShareStopped]));
useDailyEvent('nonfatal-error', useCallback(function (ev) {
if (ev.type !== 'screen-share-error') return;
onError == null ? void 0 : onError(ev);
}, [onError]));
var screens = useAtomValue(screenSharesState(undefined));
var result = {
isSharingScreen: screens.some(function (s) {
return s.local;
}),
screens: screens,
startScreenShare: startScreenShare,
stopScreenShare: stopScreenShare
};
useDebugValue(result);
return result;
};
/**
* Returns a participant's track and state, based on the given MediaType.
*
* Equivalent to daily.participants()[participantId].tracks[type].
*
* @param participantId The participant's session_id.
* @param type The track type. Default: "video"
*/
var useMediaTrack = function useMediaTrack(participantId, type) {
if (type === void 0) {
type = 'video';
}
var trackState = useParticipantProperty(participantId, "tracks." + type);
var result = trackState ? _extends({}, trackState, {
isOff: isTrackOff(trackState.state)
}) : {
isOff: true,
persistentTrack: undefined,
state: 'off',
subscribed: false
};
useDebugValue(result);
return result;
};
/**
* Original source: https://github.com/jaredLunde/react-hook/blob/master/packages/merged-ref/src/index.tsx
* Original author: Jared Lunde (https://github.com/jaredLunde)
* Originally published under the MIT license: https://github.com/jaredLunde/react-hook/blob/master/LICENSE
*/
function useMergedRef() {
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
refs[_key] = arguments[_key];
}
return useCallback(function (element) {
for (var i = 0; i < refs.length; i++) {
var ref = refs[i];
if (typeof ref === 'function') ref(element);else if (ref && typeof ref === 'object') ref.current = element;
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
refs);
}
var _excluded = ["onPlayFailed", "sessionId", "type"];
var DailyAudioTrack = /*#__PURE__*/memo( /*#__PURE__*/forwardRef(function (_ref, ref) {
var onPlayFailed = _ref.onPlayFailed,
sessionId = _ref.sessionId,
_ref$type = _ref.type,
type = _ref$type === void 0 ? 'audio' : _ref$type,
props = _objectWithoutPropertiesLoose(_ref, _excluded);
var audioEl = useRef(null);
var audio = useMediaTrack(sessionId, type);
var audioRef = useMergedRef(audioEl, ref);
var subscribedState = audio == null ? void 0 : audio.subscribed;
/**
* Setup audio tag.
*/
useEffect(function () {
var audioTag = audioEl.current;
if (!audioTag || !(audio != null && audio.persistentTrack)) return;
var playTimeout;
var handleCanPlay = function handleCanPlay() {
audioTag.play()["catch"](function (e) {
onPlayFailed == null ? void 0 : onPlayFailed({
sessionId: sessionId,
target: audioTag,
type: type,
message: e.message,
name: e.name
});
});
};
var handlePlay = function handlePlay() {
clearTimeout(playTimeout);
};
if (!MediaStream) {
console.warn("MediaStream API not available. Can't setup " + type + " for " + sessionId);
onPlayFailed == null ? void 0 : onPlayFailed({
sessionId: sessionId,
target: audioTag,
type: type,
message: 'MediaStream API not available',
name: 'MediaStream API not available'
});
return;
}
audioTag.addEventListener('canplay', handleCanPlay);
audioTag.addEventListener('play', handlePlay);
audioTag.srcObject = new MediaStream([audio == null ? void 0 : audio.persistentTrack]);
return function () {
audioTag == null ? void 0 : audioTag.removeEventListener('canplay', handleCanPlay);
audioTag == null ? void 0 : audioTag.removeEventListener('play', handlePlay);
};
}, [audio == null ? void 0 : audio.persistentTrack, onPlayFailed, sessionId, type]);
return React.createElement("audio", Object.assign({
autoPlay: true,
ref: audioRef
}, props, {
"data-session-id": sessionId,
"data-audio-type": type,
"data-subscribed": subscribedState
}));
}));
DailyAudioTrack.displayName = 'DailyAudioTrack';
var DailyAudio = /*#__PURE__*/memo( /*#__PURE__*/forwardRef(function (_ref, ref) {
var _ref$autoSubscribeAct = _ref.autoSubscribeActiveSpeaker,
autoSubscribeActiveSpeaker = _ref$autoSubscribeAct === void 0 ? false : _ref$autoSubscribeAct,
_ref$maxSpeakers = _ref.maxSpeakers,
maxSpeakers = _ref$maxSpeakers === void 0 ? 5 : _ref$maxSpeakers,
onPlayFailed = _ref.onPlayFailed,
_ref$playLocalScreenA = _ref.playLocalScreenAudio,
playLocalScreenAudio = _ref$playLocalScreenA === void 0 ? false : _ref$playLocalScreenA;
var daily = useDaily();
var _useState = useState(new Array(maxSpeakers).fill('')),
speakers = _useState[0],
setSpeakers = _useState[1];
var _useScreenShare = useScreenShare(),
screens = _useScreenShare.screens;
var localSessionId = useLocalSessionId();
var activeSpeakerId = useActiveSpeakerId({
ignoreLocal: true
});
var containerRef = useRef(null);
useImperativeHandle(ref, function () {
return {
getActiveSpeakerAudio: function getActiveSpeakerAudio() {
var _containerRef$current, _containerRef$current2;
return (_containerRef$current = (_containerRef$current2 = containerRef.current) == null ? void 0 : _containerRef$current2.querySelector("audio[data-session-id=\"" + activeSpeakerId + "\"][data-audio-type=\"audio\"]")) != null ? _containerRef$current : null;
},
getAllAudio: function getAllAudio() {
var _containerRef$current3, _containerRef$current4;
return Array.from((_containerRef$current3 = (_containerRef$current4 = containerRef.current) == null ? void 0 : _containerRef$current4.querySelectorAll('audio')) != null ? _containerRef$current3 : []);
},
getAudioBySessionId: function getAudioBySessionId(id) {
var _containerRef$current5, _containerRef$current6;
return (_containerRef$current5 = (_containerRef$current6 = containerRef.current) == null ? void 0 : _containerRef$current6.querySelector("audio[data-session-id=\"" + id + "\"][data-audio-type=\"audio\"]")) != null ? _containerRef$current5 : null;
},
getRmpAudio: function getRmpAudio() {
var _containerRef$current7, _containerRef$current8;
return Array.from((_containerRef$current7 = (_containerRef$current8 = containerRef.current) == null ? void 0 : _containerRef$current8.querySelectorAll('audio[data-audio-type="rmpAudio"]')) != null ? _containerRef$current7 : []);
},
getScreenAudio: function getScreenAudio() {
var _containerRef$current9, _containerRef$current10;
return Array.from((_containerRef$current9 = (_containerRef$current10 = containerRef.current) == null ? void 0 : _containerRef$current10.querySelectorAll('audio[data-audio-type="screenAudio"]')) != null ? _containerRef$current9 : []);
},
getRmpAudioBySessionId: function getRmpAudioBySessionId(id) {
var _containerRef$current11, _containerRef$current12;
return (_containerRef$current11 = (_containerRef$current12 = containerRef.current) == null ? void 0 : _containerRef$current12.querySelector("audio[data-session-id=\"" + id + "\"][data-audio-type=\"rmpAudio\"]")) != null ? _containerRef$current11 : null;
},
getScreenAudioBySessionId: function getScreenAudioBySessionId(id) {
var _containerRef$current13, _containerRef$current14;
return (_containerRef$current13 = (_containerRef$current14 = containerRef.current) == null ? void 0 : _containerRef$current14.querySelector("audio[data-session-id=\"" + id + "\"][data-audio-type=\"screenAudio\"]")) != null ? _containerRef$current13 : null;
}
};
}, [activeSpeakerId]);
var assignSpeaker = useAtomCallback(useCallback( /*#__PURE__*/function () {
var _ref2 = _asyncToGenerator(function* (get, _set, sessionId) {
var _daily$participants;
/**
* Only consider remote participants with subscribed or staged audio.
*/
var subscribedParticipants = Object.values((_daily$participants = daily == null ? void 0 : daily.participants()) != null ? _daily$participants : {}).filter(function (p) {
return !p.local && Boolean(p.tracks.audio.subscribed);
});
var isSubscribed = function isSubscribed(id) {
return subscribedParticipants.some(function (p) {
return p.session_id === id;
});
};
if (!isSubscribed(sessionId)) {
if (daily && !daily.isDestroyed() && autoSubscribeActiveSpeaker && !daily.subscribeToTracksAutomatically()) {
daily.updateParticipant(sessionId, {
setSubscribedTracks: {
audio: true
}
});
} else {
return;
}
}
setSpeakers(function (prevSpeakers) {
var _speakerObjects$;
// New speaker is already present
if (prevSpeakers.includes(sessionId)) return prevSpeakers;
// Try to find a free slot: either unassigned or unsubscribed
var freeSlotCheck = function freeSlotCheck(id) {
return !id || !isSubscribed(id);
};
if (prevSpeakers.some(freeSlotCheck)) {
var idx = prevSpeakers.findIndex(freeSlotCheck);
prevSpeakers[idx] = sessionId;
return [].concat(prevSpeakers);
}
// From here on we can assume that all assigned audio tracks are subscribed.
// Try to find muted recent speaker
var mutedIdx = prevSpeakers.findIndex(function (id) {
return subscribedParticipants.some(function (p) {
return p.session_id === id && isTrackOff(p.tracks.audio.state);
});
});
if (mutedIdx >= 0) {
prevSpeakers[mutedIdx] = sessionId;
return [].concat(prevSpeakers);
}
// Find least recent non-active speaker and replace with new speaker
var speakerObjects = subscribedParticipants.filter(function (p) {
return (
// Only consider participants currently assigned to speaker slots
prevSpeakers.includes(p.session_id) &&
// Don't replace current active participant, to avoid audio drop-outs
p.session_id !== activeSpeakerId
);
}).sort(function (a, b) {
var _get, _get2;
var lastActiveA = (_get = get(getParticipantPropertyAtom(a.session_id, 'last_active'))) != null ? _get : new Date('1970-01-01');
var lastActiveB = (_get2 = get(getParticipantPropertyAtom(b.session_id, 'last_active'))) != null ? _get2 : new Date('1970-01-01');
if (lastActiveA > lastActiveB) return 1;
if (lastActiveA < lastActiveB) return -1;
return 0;
});
// No previous speaker in call anymore. Assign first free slot.
if (!speakerObjects.length) {
// Don't replace the active speaker. Instead find first non-active speaker slot.
var _replaceIdx = prevSpeakers.findIndex(function (id) {
return id !== activeSpeakerId;
});
prevSpeakers[_replaceIdx] = sessionId;
return [].concat(prevSpeakers);
}
// Replace least recent speaker with new speaker
var replaceIdx = prevSpeakers.indexOf((_speakerObjects$ = speakerObjects[0]) == null ? void 0 : _speakerObjects$.session_id);
prevSpeakers[replaceIdx] = sessionId;
return [].concat(prevSpeakers);
});
});
return function (_x, _x2, _x3) {
return _ref2.apply(this, arguments);
};
}(), [activeSpeakerId, autoSubscribeActiveSpeaker, daily]));
/**
* Unassigns speaker from speaker slot, e.g. because participant left the call.
*/
var removeSpeaker = useCallback(function (sessionId) {
setSpeakers(function (prevSpeakers) {
if (!prevSpeakers.includes(sessionId)) return prevSpeakers;
var newSpeakers = [].concat(prevSpeakers);
var idx = newSpeakers.indexOf(sessionId);
newSpeakers[idx] = '';
return newSpeakers;
});
}, []);
useThrottledDailyEvent(['active-speaker-change', 'track-started', 'participant-left'], useCallback(function (evts) {
evts.forEach(function (ev) {
switch (ev.action) {
case 'active-speaker-change':
if (ev.activeSpeaker.peerId === localSessionId) return;
assignSpeaker(ev.activeSpeaker.peerId);
break;
case 'track-started':
if (ev.track.kind === 'audio' && ev.participant && !ev.participant.local) {
assignSpeaker(ev.participant.session_id);
}
break;
case 'participant-left':
removeSpeaker(ev.participant.session_id);
break;
}
});
}, [assignSpeaker, localSessionId, removeSpeaker]), 200);
var rmpAudioIds = useParticipantIds({
filter: useCallback(function (p) {
var _p$tracks;
return Boolean(p == null ? void 0 : (_p$tracks = p.tracks) == null ? void 0 : _p$tracks.rmpAudio);
}, [])
});
return React.createElement("div", {
ref: containerRef
}, speakers.map(function (sessionId, idx) {
return React.createElement(DailyAudioTrack, {
key: "speaker-slot-" + idx,
onPlayFailed: onPlayFailed,
sessionId: sessionId,
type: "audio"
});
}), screens.filter(function (screen) {
return playLocalScreenAudio ? true : !screen.local;
}).map(function (screen) {
return React.createElement(DailyAudioTrack, {
key: screen.screenId,
onPlayFailed: onPlayFailed,
sessionId: screen.session_id,
type: "screenAudio"
});
}), rmpAudioIds.map(function (id) {
return React.createElement(DailyAudioTrack, {
key: id + "-rmp",
onPlayFailed: onPlayFailed,
sessionId: id,
type: "rmpAudio"
});
}));
}));
DailyAudio.displayName = 'DailyAudio';
var _excluded$1 = ["automirror", "fit", "mirror", "onResize", "playableStyle", "sessionId", "style", "type"];
var DailyVideo = /*#__PURE__*/forwardRef(function DailyVideo(_ref, ref) {
var automirror = _ref.automirror,
_ref$fit = _ref.fit,
fit = _ref$fit === void 0 ? 'contain' : _ref$fit,
mirror = _ref.mirror,
onResize = _ref.onResize,
_ref$playableStyle = _ref.playableStyle,
playableStyle = _ref$playableStyle === void 0 ? {} : _ref$playableStyle,
sessionId = _ref.sessionId,
_ref$style = _ref.style,
style = _ref$style === void 0 ? {} : _ref$style,
_ref$type = _ref.type,
type = _ref$type === void 0 ? 'video' : _ref$type,
props = _objectWithoutPropertiesLoose(_ref, _excluded$1);
var localSessionId = useLocalSessionId();
var isLocal = localSessionId === session