UNPKG

@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
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