UNPKG

@livekit/components-core

Version:

This package is a wrapper around the [livekit/client-sdk-js](https://github.com/livekit/client-sdk-js) package. It transforms the event based logic into simple to use observable state and component level APIs. This package is the core for all framework sp

1,456 lines (1,423 loc) 78 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; var __forAwait = (obj, it, method) => (it = obj[__knownSymbol("asyncIterator")]) ? it.call(obj) : (obj = obj[__knownSymbol("iterator")](), it = {}, method = (key, fn) => (fn = obj[key]) && (it[key] = (arg) => new Promise((yes, no, done) => (arg = fn.call(obj, arg), done = arg.done, Promise.resolve(arg.value).then((value) => yes({ value, done }), no)))), method("next"), method("return"), it); // src/constants.ts var cssPrefix = "lk"; // src/track-reference/track-reference.types.ts function isTrackReference(trackReference) { if (typeof trackReference === "undefined") { return false; } return isTrackReferenceSubscribed(trackReference) || isTrackReferencePublished(trackReference); } function isTrackReferenceSubscribed(trackReference) { var _a; if (!trackReference) { return false; } return trackReference.hasOwnProperty("participant") && trackReference.hasOwnProperty("source") && trackReference.hasOwnProperty("track") && typeof ((_a = trackReference.publication) == null ? void 0 : _a.track) !== "undefined"; } function isTrackReferencePublished(trackReference) { if (!trackReference) { return false; } return trackReference.hasOwnProperty("participant") && trackReference.hasOwnProperty("source") && trackReference.hasOwnProperty("publication") && typeof trackReference.publication !== "undefined"; } function isTrackReferencePlaceholder(trackReference) { if (!trackReference) { return false; } return trackReference.hasOwnProperty("participant") && trackReference.hasOwnProperty("source") && typeof trackReference.publication === "undefined"; } // src/track-reference/track-reference.utils.ts function getTrackReferenceId(trackReference) { if (typeof trackReference === "string" || typeof trackReference === "number") { return `${trackReference}`; } else if (isTrackReferencePlaceholder(trackReference)) { return `${trackReference.participant.identity}_${trackReference.source}_placeholder`; } else if (isTrackReference(trackReference)) { return `${trackReference.participant.identity}_${trackReference.publication.source}_${trackReference.publication.trackSid}`; } else { throw new Error(`Can't generate a id for the given track reference: ${trackReference}`); } } function getTrackReferenceSource(trackReference) { if (isTrackReference(trackReference)) { return trackReference.publication.source; } else { return trackReference.source; } } function isEqualTrackRef(a, b) { if (a === void 0 || b === void 0) { return false; } if (isTrackReference(a) && isTrackReference(b)) { return a.publication.trackSid === b.publication.trackSid; } else { return getTrackReferenceId(a) === getTrackReferenceId(b); } } function isTrackReferencePinned(trackReference, pinState) { if (typeof pinState === "undefined") { return false; } if (isTrackReference(trackReference)) { return pinState.some( (pinnedTrackReference) => pinnedTrackReference.participant.identity === trackReference.participant.identity && isTrackReference(pinnedTrackReference) && pinnedTrackReference.publication.trackSid === trackReference.publication.trackSid ); } else if (isTrackReferencePlaceholder(trackReference)) { return pinState.some( (pinnedTrackReference) => pinnedTrackReference.participant.identity === trackReference.participant.identity && isTrackReferencePlaceholder(pinnedTrackReference) && pinnedTrackReference.source === trackReference.source ); } else { return false; } } function isPlaceholderReplacement(currentTrackRef, nextTrackRef) { return isTrackReferencePlaceholder(currentTrackRef) && isTrackReference(nextTrackRef) && nextTrackRef.participant.identity === currentTrackRef.participant.identity && nextTrackRef.source === currentTrackRef.source; } // src/utils.ts function isLocal(p) { return p.isLocal; } function isRemote(p) { return !p.isLocal; } var attachIfSubscribed = (publication, element) => { if (!publication) return; const { isSubscribed, track } = publication; if (element && track) { if (isSubscribed) { track.attach(element); } else { track.detach(element); } } }; function isParticipantTrackReferencePinned(trackRef, pinState) { if (pinState === void 0) { return false; } return pinState.some((pinnedTrackRef) => isEqualTrackRef(pinnedTrackRef, trackRef)); } function getScrollBarWidth() { const inner = document.createElement("p"); inner.style.width = "100%"; inner.style.height = "200px"; const outer = document.createElement("div"); outer.style.position = "absolute"; outer.style.top = "0px"; outer.style.left = "0px"; outer.style.visibility = "hidden"; outer.style.width = "200px"; outer.style.height = "150px"; outer.style.overflow = "hidden"; outer.appendChild(inner); document.body.appendChild(outer); const w1 = inner.offsetWidth; outer.style.overflow = "scroll"; let w2 = inner.offsetWidth; if (w1 === w2) { w2 = outer.clientWidth; } document.body.removeChild(outer); const scrollBarWidth = w1 - w2; return scrollBarWidth; } // src/helper/detectMobileBrowser.ts function isWeb() { return typeof document !== "undefined"; } function isMobileBrowser() { return isWeb() ? /Mobi/i.test(window.navigator.userAgent) : false; } // src/helper/url-regex.ts function createUrlRegExp(options) { options = __spreadValues({}, options); const protocol = `(?:(?:[a-z]+:)?//)?`; const auth = "(?:\\S+(?::\\S*)?@)?"; const ip = new RegExp( "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}", "g" ).source; const host = "(?:(?:[a-z\\u00a1-\\uffff0-9][-_]*)*[a-z\\u00a1-\\uffff0-9]+)"; const domain = "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*"; const tld = `(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))\\.?`; const port = "(?::\\d{2,5})?"; const path = '(?:[/?#][^\\s"]*)?'; const regex2 = `(?:${protocol}|www\\.)${auth}(?:localhost|${ip}|${host}${domain}${tld})${port}${path}`; return options.exact ? new RegExp(`(?:^${regex2}$)`, "i") : new RegExp(regex2, "ig"); } // src/helper/emailRegex.ts var regex = "[^\\.\\s@:](?:[^\\s@:]*[^\\s@:\\.])?@[^\\.\\s@]+(?:\\.[^\\.\\s@]+)*"; function createEmailRegExp({ exact } = {}) { return exact ? new RegExp(`^${regex}$`) : new RegExp(regex, "g"); } // src/helper/floating-menu.ts import { autoUpdate, computePosition, flip, offset, shift } from "@floating-ui/dom"; function computeMenuPosition(button, menu, onUpdate) { const cleanup = autoUpdate(button, menu, () => __async(this, null, function* () { const { x, y } = yield computePosition(button, menu, { placement: "top", middleware: [offset(6), flip(), shift({ padding: 5 })] }); onUpdate == null ? void 0 : onUpdate(x, y); })); return cleanup; } function wasClickOutside(insideElement, event) { const isOutside = !insideElement.contains(event.target); return isOutside; } // src/helper/tokenizer.ts var createDefaultGrammar = () => { return { email: createEmailRegExp(), url: createUrlRegExp({}) }; }; function tokenize(input, grammar) { const matches = Object.entries(grammar).map( ([type, rx], weight) => Array.from(input.matchAll(rx)).map(({ index, 0: content }) => ({ type, weight, content, index: index != null ? index : 0 })) ).flat().sort((a, b) => { const d = a.index - b.index; return d !== 0 ? d : a.weight - b.weight; }).filter(({ index }, i, arr) => { if (i === 0) return true; const prev = arr[i - 1]; return prev.index + prev.content.length <= index; }); const tokens = []; let pos = 0; for (const { type, content, index } of matches) { if (index > pos) tokens.push(input.substring(pos, index)); tokens.push({ type, content }); pos = index + content.length; } if (input.length > pos) tokens.push(input.substring(pos)); return tokens; } // src/helper/eventGroups.ts import { ParticipantEvent, RoomEvent } from "livekit-client"; var allRemoteParticipantRoomEvents = [ RoomEvent.ConnectionStateChanged, RoomEvent.RoomMetadataChanged, RoomEvent.ActiveSpeakersChanged, RoomEvent.ConnectionQualityChanged, RoomEvent.ParticipantConnected, RoomEvent.ParticipantDisconnected, RoomEvent.ParticipantPermissionsChanged, RoomEvent.ParticipantMetadataChanged, RoomEvent.ParticipantNameChanged, RoomEvent.ParticipantAttributesChanged, RoomEvent.TrackMuted, RoomEvent.TrackUnmuted, RoomEvent.TrackPublished, RoomEvent.TrackUnpublished, RoomEvent.TrackStreamStateChanged, RoomEvent.TrackSubscriptionFailed, RoomEvent.TrackSubscriptionPermissionChanged, RoomEvent.TrackSubscriptionStatusChanged ]; var allParticipantRoomEvents = [ ...allRemoteParticipantRoomEvents, RoomEvent.LocalTrackPublished, RoomEvent.LocalTrackUnpublished ]; var participantTrackEvents = [ ParticipantEvent.TrackPublished, ParticipantEvent.TrackUnpublished, ParticipantEvent.TrackMuted, ParticipantEvent.TrackUnmuted, ParticipantEvent.TrackStreamStateChanged, ParticipantEvent.TrackSubscribed, ParticipantEvent.TrackUnsubscribed, ParticipantEvent.TrackSubscriptionPermissionChanged, ParticipantEvent.TrackSubscriptionFailed, ParticipantEvent.LocalTrackPublished, ParticipantEvent.LocalTrackUnpublished ]; var allRemoteParticipantEvents = [ ParticipantEvent.ConnectionQualityChanged, ParticipantEvent.IsSpeakingChanged, ParticipantEvent.ParticipantMetadataChanged, ParticipantEvent.ParticipantPermissionsChanged, ParticipantEvent.TrackMuted, ParticipantEvent.TrackUnmuted, ParticipantEvent.TrackPublished, ParticipantEvent.TrackUnpublished, ParticipantEvent.TrackStreamStateChanged, ParticipantEvent.TrackSubscriptionFailed, ParticipantEvent.TrackSubscriptionPermissionChanged, ParticipantEvent.TrackSubscriptionStatusChanged ]; var allParticipantEvents = [ ...allRemoteParticipantEvents, ParticipantEvent.LocalTrackPublished, ParticipantEvent.LocalTrackUnpublished ]; // src/logger.ts import { setLogLevel as setClientSdkLogLevel, setLogExtension as setClientSdkLogExtension, LogLevel as LogLevelEnum } from "livekit-client"; import loglevel from "loglevel"; var log = loglevel.getLogger("lk-components-js"); log.setDefaultLevel("WARN"); function setLogLevel(level, options = {}) { var _a; log.setLevel(level); setClientSdkLogLevel((_a = options.liveKitClientLogLevel) != null ? _a : level); } function setLogExtension(extension, options = {}) { var _a; const originalFactory = log.methodFactory; log.methodFactory = (methodName, configLevel, loggerName) => { const rawMethod = originalFactory(methodName, configLevel, loggerName); const logLevel = LogLevelEnum[methodName]; const needLog = logLevel >= configLevel && logLevel < LogLevelEnum.silent; return (msg, context) => { if (context) rawMethod(msg, context); else rawMethod(msg); if (needLog) { extension(logLevel, msg, context); } }; }; log.setLevel(log.getLevel()); setClientSdkLogExtension((_a = options.liveKitClientLogExtension) != null ? _a : extension); } // src/helper/grid-layouts.ts var GRID_LAYOUTS = [ { columns: 1, rows: 1 }, { columns: 1, rows: 2, orientation: "portrait" }, { columns: 2, rows: 1, orientation: "landscape" }, { columns: 2, rows: 2, minWidth: 560 }, { columns: 3, rows: 3, minWidth: 700 }, { columns: 4, rows: 4, minWidth: 960 }, { columns: 5, rows: 5, minWidth: 1100 } ]; function selectGridLayout(layoutDefinitions, participantCount, width, height) { if (layoutDefinitions.length < 1) { throw new Error("At least one grid layout definition must be provided."); } const layouts = expandAndSortLayoutDefinitions(layoutDefinitions); if (width <= 0 || height <= 0) { return layouts[0]; } let currentLayoutIndex = 0; const containerOrientation = width / height > 1 ? "landscape" : "portrait"; let layout = layouts.find((layout_, index, allLayouts) => { currentLayoutIndex = index; const isBiggerLayoutAvailable = allLayouts.findIndex((l, i) => { const fitsOrientation = !l.orientation || l.orientation === containerOrientation; const layoutIsBiggerThanCurrent = i > index; const layoutFitsSameAmountOfParticipants = l.maxTiles === layout_.maxTiles; return layoutIsBiggerThanCurrent && layoutFitsSameAmountOfParticipants && fitsOrientation; }) !== -1; return layout_.maxTiles >= participantCount && !isBiggerLayoutAvailable; }); if (layout === void 0) { layout = layouts[layouts.length - 1]; if (layout) { log.warn( `No layout found for: participantCount: ${participantCount}, width/height: ${width}/${height} fallback to biggest available layout (${layout}).` ); } else { throw new Error(`No layout or fallback layout found.`); } } if (width < layout.minWidth || height < layout.minHeight) { if (currentLayoutIndex > 0) { const smallerLayout = layouts[currentLayoutIndex - 1]; layout = selectGridLayout( layouts.slice(0, currentLayoutIndex), smallerLayout.maxTiles, width, height ); } } return layout; } function expandAndSortLayoutDefinitions(layouts) { return [...layouts].map((layout) => { var _a, _b; return { name: `${layout.columns}x${layout.rows}`, columns: layout.columns, rows: layout.rows, maxTiles: layout.columns * layout.rows, minWidth: (_a = layout.minWidth) != null ? _a : 0, minHeight: (_b = layout.minHeight) != null ? _b : 0, orientation: layout.orientation }; }).sort((a, b) => { if (a.maxTiles !== b.maxTiles) { return a.maxTiles - b.maxTiles; } else if (a.minWidth !== 0 || b.minWidth !== 0) { return a.minWidth - b.minWidth; } else if (a.minHeight !== 0 || b.minHeight !== 0) { return a.minHeight - b.minHeight; } else { return 0; } }); } // src/helper/set-helper.ts function setDifference(setA, setB) { const _difference = new Set(setA); for (const elem of setB) { _difference.delete(elem); } return _difference; } // src/helper/featureDetection.ts function supportsScreenSharing() { return typeof navigator !== "undefined" && navigator.mediaDevices && !!navigator.mediaDevices.getDisplayMedia; } // src/helper/transcriptions.ts function getActiveTranscriptionSegments(segments, syncTimes, maxAge = 0) { return segments.filter((segment) => { var _a; const hasTrackSync = !!syncTimes.rtpTimestamp; const currentTrackTime = (_a = syncTimes.rtpTimestamp) != null ? _a : performance.timeOrigin + performance.now(); const displayStartTime = hasTrackSync ? Math.max(segment.receivedAtMediaTimestamp, segment.startTime) : segment.receivedAt; const segmentDuration = maxAge + segment.endTime - segment.startTime; return currentTrackTime >= displayStartTime && currentTrackTime <= displayStartTime + segmentDuration; }); } function addMediaTimestampToTranscription(segment, timestamps) { var _a; return __spreadProps(__spreadValues({}, segment), { receivedAtMediaTimestamp: (_a = timestamps.rtpTimestamp) != null ? _a : 0, receivedAt: timestamps.timestamp }); } function dedupeSegments(prevSegments, newSegments, windowSize) { return [...prevSegments, ...newSegments].reduceRight((acc, segment) => { if (!acc.find((val) => val.id === segment.id)) { acc.unshift(segment); } return acc; }, []).slice(0 - windowSize); } function didActiveSegmentsChange(prevActive, newActive) { if (newActive.length !== prevActive.length) { return true; } return !newActive.every((newSegment) => { return prevActive.find( (prevSegment) => prevSegment.id === newSegment.id && prevSegment.text === newSegment.text && prevSegment.final === newSegment.final && prevSegment.language === newSegment.language && prevSegment.startTime === newSegment.startTime && prevSegment.endTime === newSegment.endTime ); }); } // src/helper/participant-attributes.ts var ParticipantAgentAttributes = /* @__PURE__ */ ((ParticipantAgentAttributes2) => { ParticipantAgentAttributes2["AgentState"] = "lk.agent.state"; ParticipantAgentAttributes2["PublishOnBehalf"] = "lk.publish_on_behalf"; ParticipantAgentAttributes2["TranscriptionFinal"] = "lk.transcription_final"; ParticipantAgentAttributes2["TranscriptionSegmentId"] = "lk.segment_id"; ParticipantAgentAttributes2["TranscribedTrackId"] = "lk.transcribed_track_id"; return ParticipantAgentAttributes2; })(ParticipantAgentAttributes || {}); // src/types.ts var PIN_DEFAULT_STATE = []; var WIDGET_DEFAULT_STATE = { showChat: false, unreadMessages: 0, showSettings: false }; function isSourceWitOptions(source) { return typeof source === "object"; } function isSourcesWithOptions(sources) { return Array.isArray(sources) && sources.filter(isSourceWitOptions).length > 0; } // src/sorting/sort-track-bundles.ts import { Track as Track2 } from "livekit-client"; // src/sorting/base-sort-functions.ts import { Track } from "livekit-client"; function sortParticipantsByAudioLevel(a, b) { return b.audioLevel - a.audioLevel; } function sortParticipantsByIsSpeaking(a, b) { if (a.isSpeaking === b.isSpeaking) { return 0; } else { return a.isSpeaking ? -1 : 1; } } function sortParticipantsByLastSpokenAT(a, b) { var _a, _b, _c, _d; if (a.lastSpokeAt !== void 0 || b.lastSpokeAt !== void 0) { return ((_b = (_a = b.lastSpokeAt) == null ? void 0 : _a.getTime()) != null ? _b : 0) - ((_d = (_c = a.lastSpokeAt) == null ? void 0 : _c.getTime()) != null ? _d : 0); } else { return 0; } } function sortParticipantsByJoinedAt(a, b) { var _a, _b, _c, _d; return ((_b = (_a = a.joinedAt) == null ? void 0 : _a.getTime()) != null ? _b : 0) - ((_d = (_c = b.joinedAt) == null ? void 0 : _c.getTime()) != null ? _d : 0); } function sortTrackReferencesByType(a, b) { if (isTrackReference(a)) { if (isTrackReference(b)) { return 0; } else { return -1; } } else if (isTrackReference(b)) { return 1; } else { return 0; } } function sortTrackRefsByIsCameraEnabled(a, b) { const aVideo = a.participant.isCameraEnabled; const bVideo = b.participant.isCameraEnabled; if (aVideo !== bVideo) { if (aVideo) { return -1; } else { return 1; } } else { return 0; } } // src/sorting/sort-track-bundles.ts function sortTrackReferences(tracks) { const localTracks = []; const screenShareTracks = []; const cameraTracks = []; const undefinedTracks = []; tracks.forEach((trackRef) => { if (trackRef.participant.isLocal && trackRef.source === Track2.Source.Camera) { localTracks.push(trackRef); } else if (trackRef.source === Track2.Source.ScreenShare) { screenShareTracks.push(trackRef); } else if (trackRef.source === Track2.Source.Camera) { cameraTracks.push(trackRef); } else { undefinedTracks.push(trackRef); } }); const sortedScreenShareTracks = sortScreenShareTracks(screenShareTracks); const sortedCameraTracks = sortCameraTracks(cameraTracks); return [...localTracks, ...sortedScreenShareTracks, ...sortedCameraTracks, ...undefinedTracks]; } function sortScreenShareTracks(screenShareTracks) { const localScreenShares = []; const remoteScreenShares = []; screenShareTracks.forEach((trackRef) => { if (trackRef.participant.isLocal) { localScreenShares.push(trackRef); } else { remoteScreenShares.push(trackRef); } }); localScreenShares.sort((a, b) => sortParticipantsByJoinedAt(a.participant, b.participant)); remoteScreenShares.sort((a, b) => sortParticipantsByJoinedAt(a.participant, b.participant)); const sortedScreenShareTrackRefs = [...remoteScreenShares, ...localScreenShares]; return sortedScreenShareTrackRefs; } function sortCameraTracks(cameraTrackReferences) { const localCameraTracks = []; const remoteCameraTracks = []; cameraTrackReferences.forEach((trackRef) => { if (trackRef.participant.isLocal) { localCameraTracks.push(trackRef); } else { remoteCameraTracks.push(trackRef); } }); remoteCameraTracks.sort((a, b) => { if (a.participant.isSpeaking && b.participant.isSpeaking) { return sortParticipantsByAudioLevel(a.participant, b.participant); } if (a.participant.isSpeaking !== b.participant.isSpeaking) { return sortParticipantsByIsSpeaking(a.participant, b.participant); } if (a.participant.lastSpokeAt !== b.participant.lastSpokeAt) { return sortParticipantsByLastSpokenAT(a.participant, b.participant); } if (isTrackReference(a) !== isTrackReference(b)) { return sortTrackReferencesByType(a, b); } if (a.participant.isCameraEnabled !== b.participant.isCameraEnabled) { return sortTrackRefsByIsCameraEnabled(a, b); } return sortParticipantsByJoinedAt(a.participant, b.participant); }); return [...localCameraTracks, ...remoteCameraTracks]; } // src/sorting/sort-participants.ts function sortParticipants(participants) { const sortedParticipants = [...participants]; sortedParticipants.sort((a, b) => { if (a.isSpeaking && b.isSpeaking) { return sortParticipantsByAudioLevel(a, b); } if (a.isSpeaking !== b.isSpeaking) { return sortParticipantsByIsSpeaking(a, b); } if (a.lastSpokeAt !== b.lastSpokeAt) { return sortParticipantsByLastSpokenAT(a, b); } const aVideo = a.videoTrackPublications.size > 0; const bVideo = b.videoTrackPublications.size > 0; if (aVideo !== bVideo) { if (aVideo) { return -1; } else { return 1; } } return sortParticipantsByJoinedAt(a, b); }); const localParticipant = sortedParticipants.find((p) => p.isLocal); if (localParticipant) { const localIdx = sortedParticipants.indexOf(localParticipant); if (localIdx >= 0) { sortedParticipants.splice(localIdx, 1); if (sortedParticipants.length > 0) { sortedParticipants.splice(0, 0, localParticipant); } else { sortedParticipants.push(localParticipant); } } } return sortedParticipants; } // src/helper/array-helper.ts function chunk(input, size) { return input.reduce( (arr, item, idx) => { return idx % size === 0 ? [...arr, [item]] : [...arr.slice(0, -1), [...arr.slice(-1)[0], item]]; }, [] ); } function zip(a1, a2) { const resultLength = Math.max(a1.length, a2.length); return new Array(resultLength).fill([]).map((_val, idx) => [a1[idx], a2[idx]]); } function differenceBy(a1, a2, by) { return a1.filter((c) => !a2.map((v) => by(v)).includes(by(c))); } // src/track-reference/test-utils.ts import { Participant, RemoteTrackPublication, Track as Track3 } from "livekit-client"; import { TrackInfo } from "@livekit/protocol"; function flatTrackReferenceArray(list) { return list.map((item) => { if (typeof item === "string" || typeof item === "number") { return `${item}`; } else { return getTrackReferenceId(item); } }); } // src/sorting/tile-array-update.ts function visualPageChange(state, next) { return { dropped: differenceBy(state, next, getTrackReferenceId), added: differenceBy(next, state, getTrackReferenceId) }; } function listNeedsUpdating(changes) { return changes.added.length !== 0 || changes.dropped.length !== 0; } function findIndex(trackReference, trackReferences) { const indexToReplace = trackReferences.findIndex( (trackReference_) => getTrackReferenceId(trackReference_) === getTrackReferenceId(trackReference) ); if (indexToReplace === -1) { throw new Error( `Element not part of the array: ${getTrackReferenceId( trackReference )} not in ${flatTrackReferenceArray(trackReferences)}` ); } return indexToReplace; } function swapItems(moveForward, moveBack, trackReferences) { const indexToReplace = findIndex(moveForward, trackReferences); const indexReplaceWith = findIndex(moveBack, trackReferences); trackReferences.splice(indexToReplace, 1, moveBack); trackReferences.splice(indexReplaceWith, 1, moveForward); return trackReferences; } function dropItem(itemToDrop, list) { const indexOfElementToDrop = findIndex(itemToDrop, list); list.splice(indexOfElementToDrop, 1); return list; } function addItem(itemToAdd, list) { return [...list, itemToAdd]; } function divideIntoPages(list, maxElementsOnPage) { const pages = chunk(list, maxElementsOnPage); return pages; } function updatePages(currentList, nextList, maxItemsOnPage) { let updatedList = refreshList(currentList, nextList); if (updatedList.length < nextList.length) { const addedItems = differenceBy(nextList, updatedList, getTrackReferenceId); updatedList = [...updatedList, ...addedItems]; } const currentPages = divideIntoPages(updatedList, maxItemsOnPage); const nextPages = divideIntoPages(nextList, maxItemsOnPage); zip(currentPages, nextPages).forEach(([currentPage, nextPage], pageIndex) => { if (currentPage && nextPage) { const updatedPage = divideIntoPages(updatedList, maxItemsOnPage)[pageIndex]; const changes = visualPageChange(updatedPage, nextPage); if (listNeedsUpdating(changes)) { log.debug( `Detected visual changes on page: ${pageIndex}, current: ${flatTrackReferenceArray( currentPage )}, next: ${flatTrackReferenceArray(nextPage)}`, { changes } ); if (changes.added.length === changes.dropped.length) { zip(changes.added, changes.dropped).forEach(([added, dropped]) => { if (added && dropped) { updatedList = swapItems(added, dropped, updatedList); } else { throw new Error( `For a swap action we need a addition and a removal one is missing: ${added}, ${dropped}` ); } }); } if (changes.added.length === 0 && changes.dropped.length > 0) { changes.dropped.forEach((item) => { updatedList = dropItem(item, updatedList); }); } if (changes.added.length > 0 && changes.dropped.length === 0) { changes.added.forEach((item) => { updatedList = addItem(item, updatedList); }); } } } }); if (updatedList.length > nextList.length) { const missingItems = differenceBy(updatedList, nextList, getTrackReferenceId); updatedList = updatedList.filter( (item) => !missingItems.map(getTrackReferenceId).includes(getTrackReferenceId(item)) ); } return updatedList; } function refreshList(currentList, nextList) { return currentList.map((currentItem) => { const updateForCurrentItem = nextList.find( (newItem_) => ( // If the IDs match or .. getTrackReferenceId(currentItem) === getTrackReferenceId(newItem_) || // ... if the current item is a placeholder and the new item is the track reference can replace it. typeof currentItem !== "number" && isTrackReferencePlaceholder(currentItem) && isTrackReference(newItem_) && isPlaceholderReplacement(currentItem, newItem_) ) ); return updateForCurrentItem != null ? updateForCurrentItem : currentItem; }); } // src/components/mediaToggle.ts import { Track as Track7 } from "livekit-client"; import { Subject, map as map4, startWith as startWith4 } from "rxjs"; // src/observables/participant.ts import { ParticipantEvent as ParticipantEvent2, RoomEvent as RoomEvent3, Track as Track6 } from "livekit-client"; import { Observable as Observable2, map as map3, startWith as startWith3, switchMap } from "rxjs"; // src/components/mediaTrack.ts import { Track as Track4 } from "livekit-client"; import { map, startWith } from "rxjs"; // src/styles-interface/class-prefixer.ts function prefixClass(unprefixedClassName) { return `${cssPrefix}-${unprefixedClassName}`; } // src/components/mediaTrack.ts function setupMediaTrack(trackIdentifier) { const initialPub = getTrackByIdentifier(trackIdentifier); const trackObserver = observeParticipantMedia(trackIdentifier.participant).pipe( map(() => { return getTrackByIdentifier(trackIdentifier); }), startWith(initialPub) ); const className = prefixClass( trackIdentifier.source === Track4.Source.Camera || trackIdentifier.source === Track4.Source.ScreenShare ? "participant-media-video" : "participant-media-audio" ); return { className, trackObserver }; } function getTrackByIdentifier(options) { if (isTrackReference(options)) { return options.publication; } else { const { source, name, participant } = options; if (source && name) { return participant.getTrackPublications().find((pub) => pub.source === source && pub.trackName === name); } else if (name) { return participant.getTrackPublicationByName(name); } else if (source) { return participant.getTrackPublication(source); } else { throw new Error("At least one of source and name needs to be defined"); } } } // src/observables/room.ts import { Room, RoomEvent as RoomEvent2, Track as Track5 } from "livekit-client"; import { concat, filter, map as map2, Observable, startWith as startWith2 } from "rxjs"; function observeRoomEvents(room, ...events) { const observable = new Observable((subscribe) => { const onRoomUpdate = () => { subscribe.next(room); }; events.forEach((evt) => { room.on(evt, onRoomUpdate); }); const unsubscribe = () => { events.forEach((evt) => { room.off(evt, onRoomUpdate); }); }; return unsubscribe; }).pipe(startWith2(room)); return observable; } function roomEventSelector(room, event) { const observable = new Observable((subscribe) => { const update = (...params) => { subscribe.next(params); }; room.on(event, update); const unsubscribe = () => { room.off(event, update); }; return unsubscribe; }); return observable; } function roomObserver(room) { const observable = observeRoomEvents( room, RoomEvent2.ParticipantConnected, RoomEvent2.ParticipantDisconnected, RoomEvent2.ActiveSpeakersChanged, RoomEvent2.TrackSubscribed, RoomEvent2.TrackUnsubscribed, RoomEvent2.LocalTrackPublished, RoomEvent2.LocalTrackUnpublished, RoomEvent2.AudioPlaybackStatusChanged, RoomEvent2.ConnectionStateChanged ); return observable; } function connectionStateObserver(room) { return roomEventSelector(room, RoomEvent2.ConnectionStateChanged).pipe( map2(([connectionState]) => connectionState), startWith2(room.state) ); } function screenShareObserver(room) { let screenShareSubscriber; const observers = []; const observable = new Observable((subscriber) => { screenShareSubscriber = subscriber; return () => { observers.forEach((observer) => { observer.unsubscribe(); }); }; }); const screenShareTracks = []; const handleSub = (publication, participant) => { if (publication.source !== Track5.Source.ScreenShare && publication.source !== Track5.Source.ScreenShareAudio) { return; } let trackMap = screenShareTracks.find((tr) => tr.participant.identity === participant.identity); const getScreenShareTracks = (participant2) => { return participant2.getTrackPublications().filter( (track) => (track.source === Track5.Source.ScreenShare || track.source === Track5.Source.ScreenShareAudio) && track.track ); }; if (!trackMap) { trackMap = { participant, tracks: getScreenShareTracks(participant) }; } else { const index = screenShareTracks.indexOf(trackMap); screenShareTracks.splice(index, 1); trackMap.tracks = getScreenShareTracks(participant); } if (trackMap.tracks.length > 0) { screenShareTracks.push(trackMap); } screenShareSubscriber.next(screenShareTracks); }; observers.push( roomEventSelector(room, RoomEvent2.TrackSubscribed).subscribe( ([, ...args]) => handleSub(...args) ) ); observers.push( roomEventSelector(room, RoomEvent2.TrackUnsubscribed).subscribe( ([, ...args]) => handleSub(...args) ) ); observers.push( roomEventSelector(room, RoomEvent2.LocalTrackPublished).subscribe((args) => handleSub(...args)) ); observers.push( roomEventSelector(room, RoomEvent2.LocalTrackUnpublished).subscribe((args) => { handleSub(...args); }) ); observers.push( roomEventSelector(room, RoomEvent2.TrackMuted).subscribe((args) => { handleSub(...args); }) ); observers.push( roomEventSelector(room, RoomEvent2.TrackUnmuted).subscribe((args) => { handleSub(...args); }) ); setTimeout(() => { for (const p of room.remoteParticipants.values()) { p.getTrackPublications().forEach((track) => { handleSub(track, p); }); } }, 1); return observable; } function roomInfoObserver(room) { const observer = observeRoomEvents( room, RoomEvent2.RoomMetadataChanged, RoomEvent2.ConnectionStateChanged ).pipe( map2((r) => { return { name: r.name, metadata: r.metadata }; }) ); return observer; } function activeSpeakerObserver(room) { return roomEventSelector(room, RoomEvent2.ActiveSpeakersChanged).pipe( map2(([speakers]) => speakers) ); } function createMediaDeviceObserver(kind, onError, requestPermissions = true) { const initialDevices$ = new Observable((subscriber) => { Room.getLocalDevices(kind, requestPermissions).then((devices) => { subscriber.next(devices); subscriber.complete(); }).catch((e) => { onError == null ? void 0 : onError(e); subscriber.next([]); subscriber.complete(); }); }); const deviceChanges$ = new Observable((subscriber) => { var _a; const onDeviceChange = () => __async(this, null, function* () { try { const newDevices = yield Room.getLocalDevices(kind, requestPermissions); subscriber.next(newDevices); } catch (e) { onError == null ? void 0 : onError(e); } }); if (typeof window !== "undefined") { if (!window.isSecureContext) { throw new Error( `Accessing media devices is available only in secure contexts (HTTPS and localhost), in some or all supporting browsers. See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/mediaDevices` ); } (_a = navigator == null ? void 0 : navigator.mediaDevices) == null ? void 0 : _a.addEventListener("devicechange", onDeviceChange); } return () => { var _a2; (_a2 = navigator == null ? void 0 : navigator.mediaDevices) == null ? void 0 : _a2.removeEventListener("devicechange", onDeviceChange); }; }); return concat(initialDevices$, deviceChanges$); } function createDataObserver(room) { return roomEventSelector(room, RoomEvent2.DataReceived); } function createChatObserver(room) { return roomEventSelector(room, RoomEvent2.ChatMessage); } function roomAudioPlaybackAllowedObservable(room) { const observable = observeRoomEvents(room, RoomEvent2.AudioPlaybackStatusChanged).pipe( map2((room2) => { return { canPlayAudio: room2.canPlaybackAudio }; }) ); return observable; } function roomVideoPlaybackAllowedObservable(room) { const observable = observeRoomEvents(room, RoomEvent2.VideoPlaybackStatusChanged).pipe( map2((room2) => { return { canPlayVideo: room2.canPlaybackVideo }; }) ); return observable; } function createActiveDeviceObservable(room, kind) { return roomEventSelector(room, RoomEvent2.ActiveDeviceChanged).pipe( filter(([kindOfDevice]) => kindOfDevice === kind), map2(([kind2, deviceId]) => { log.debug("activeDeviceObservable | RoomEvent.ActiveDeviceChanged", { kind: kind2, deviceId }); return deviceId; }) ); } function encryptionStatusObservable(room, participant) { return roomEventSelector(room, RoomEvent2.ParticipantEncryptionStatusChanged).pipe( filter( ([, p]) => (participant == null ? void 0 : participant.identity) === (p == null ? void 0 : p.identity) || !p && (participant == null ? void 0 : participant.identity) === room.localParticipant.identity ), map2(([encrypted]) => encrypted), startWith2( (participant == null ? void 0 : participant.isLocal) ? participant.isE2EEEnabled : !!(participant == null ? void 0 : participant.isEncrypted) ) ); } function recordingStatusObservable(room) { return roomEventSelector(room, RoomEvent2.RecordingStatusChanged).pipe( map2(([recording]) => recording), startWith2(room.isRecording) ); } // src/observables/participant.ts function observeParticipantEvents(participant, ...events) { const observable = new Observable2((subscribe) => { const onParticipantUpdate = () => { subscribe.next(participant); }; events.forEach((evt) => { participant.on(evt, onParticipantUpdate); }); const unsubscribe = () => { events.forEach((evt) => { participant.off(evt, onParticipantUpdate); }); }; return unsubscribe; }).pipe(startWith3(participant)); return observable; } function observeParticipantMedia(participant) { const participantObserver = observeParticipantEvents( participant, ParticipantEvent2.TrackMuted, ParticipantEvent2.TrackUnmuted, ParticipantEvent2.ParticipantPermissionsChanged, // ParticipantEvent.IsSpeakingChanged, ParticipantEvent2.TrackPublished, ParticipantEvent2.TrackUnpublished, ParticipantEvent2.LocalTrackPublished, ParticipantEvent2.LocalTrackUnpublished, ParticipantEvent2.MediaDevicesError, ParticipantEvent2.TrackSubscriptionStatusChanged // ParticipantEvent.ConnectionQualityChanged, ).pipe( map3((p) => { const { isMicrophoneEnabled, isCameraEnabled, isScreenShareEnabled } = p; const microphoneTrack = p.getTrackPublication(Track6.Source.Microphone); const cameraTrack = p.getTrackPublication(Track6.Source.Camera); const participantMedia = { isCameraEnabled, isMicrophoneEnabled, isScreenShareEnabled, cameraTrack, microphoneTrack, participant: p }; return participantMedia; }) ); return participantObserver; } function createTrackObserver(participant, options) { return observeParticipantMedia(participant).pipe( map3(() => { return { publication: getTrackByIdentifier(options) }; }) ); } function participantInfoObserver(participant) { if (!participant) { return void 0; } const observer = observeParticipantEvents( participant, ParticipantEvent2.ParticipantMetadataChanged, ParticipantEvent2.ParticipantNameChanged ).pipe( map3(({ name, identity, metadata }) => { return { name, identity, metadata }; }), startWith3({ name: participant.name, identity: participant.identity, metadata: participant.metadata }) ); return observer; } function createConnectionQualityObserver(participant) { const observer = participantEventSelector( participant, ParticipantEvent2.ConnectionQualityChanged ).pipe( map3(([quality]) => quality), startWith3(participant.connectionQuality) ); return observer; } function participantEventSelector(participant, event) { const observable = new Observable2((subscribe) => { const update = (...params) => { subscribe.next(params); }; participant.on(event, update); const unsubscribe = () => { participant.off(event, update); }; return unsubscribe; }); return observable; } function mutedObserver(trackRef) { var _a, _b, _c, _d; return observeParticipantEvents( trackRef.participant, ParticipantEvent2.TrackMuted, ParticipantEvent2.TrackUnmuted, ParticipantEvent2.TrackSubscribed, ParticipantEvent2.TrackUnsubscribed, ParticipantEvent2.LocalTrackPublished, ParticipantEvent2.LocalTrackUnpublished ).pipe( map3((participant) => { var _a2, _b2; const pub = (_a2 = trackRef.publication) != null ? _a2 : participant.getTrackPublication(trackRef.source); return (_b2 = pub == null ? void 0 : pub.isMuted) != null ? _b2 : true; }), startWith3( (_d = (_c = (_a = trackRef.publication) == null ? void 0 : _a.isMuted) != null ? _c : (_b = trackRef.participant.getTrackPublication(trackRef.source)) == null ? void 0 : _b.isMuted) != null ? _d : true ) ); } function createIsSpeakingObserver(participant) { return participantEventSelector(participant, ParticipantEvent2.IsSpeakingChanged).pipe( map3(([isSpeaking]) => isSpeaking) ); } function connectedParticipantsObserver(room, options = {}) { var _a; let subscriber; const observable = new Observable2((sub) => { subscriber = sub; return () => listener.unsubscribe(); }).pipe(startWith3(Array.from(room.remoteParticipants.values()))); const additionalRoomEvents = (_a = options.additionalRoomEvents) != null ? _a : allParticipantRoomEvents; const roomEvents = Array.from( /* @__PURE__ */ new Set([ RoomEvent3.ParticipantConnected, RoomEvent3.ParticipantDisconnected, RoomEvent3.ConnectionStateChanged, ...additionalRoomEvents ]) ); const listener = observeRoomEvents(room, ...roomEvents).subscribe( (r) => subscriber == null ? void 0 : subscriber.next(Array.from(r.remoteParticipants.values())) ); if (room.remoteParticipants.size > 0) { subscriber == null ? void 0 : subscriber.next(Array.from(room.remoteParticipants.values())); } return observable; } function connectedParticipantObserver(room, identity, options = {}) { var _a; const additionalEvents = (_a = options.additionalEvents) != null ? _a : allParticipantEvents; const observable = observeRoomEvents( room, RoomEvent3.ParticipantConnected, RoomEvent3.ParticipantDisconnected, RoomEvent3.ConnectionStateChanged ).pipe( switchMap((r) => { const participant = r.getParticipantByIdentity(identity); if (participant) { return observeParticipantEvents(participant, ...additionalEvents); } else { return new Observable2((subscribe) => subscribe.next(void 0)); } }), startWith3(room.getParticipantByIdentity(identity)) ); return observable; } function participantPermissionObserver(participant) { const observer = participantEventSelector( participant, ParticipantEvent2.ParticipantPermissionsChanged ).pipe( map3(() => participant.permissions), startWith3(participant.permissions) ); return observer; } function participantByIdentifierObserver(room, { kind, identity }, options = {}) { var _a; const additionalEvents = (_a = options.additionalEvents) != null ? _a : allParticipantEvents; const matchesIdentifier = (participant) => { let isMatch = true; if (kind) { isMatch = isMatch && participant.kind === kind; } if (identity) { isMatch = isMatch && participant.identity === identity; } return isMatch; }; const observable = observeRoomEvents( room, RoomEvent3.ParticipantConnected, RoomEvent3.ParticipantDisconnected, RoomEvent3.ConnectionStateChanged ).pipe( switchMap((r) => { const participant = Array.from(r.remoteParticipants.values()).find( (p) => matchesIdentifier(p) ); if (participant) { return observeParticipantEvents(participant, ...additionalEvents); } else { return new Observable2((subscribe) => subscribe.next(void 0)); } }), startWith3(Array.from(room.remoteParticipants.values()).find((p) => matchesIdentifier(p))) ); return observable; } function participantAttributesObserver(participant) { if (typeof participant === "undefined") { return new Observable2(); } return participantEventSelector(participant, ParticipantEvent2.AttributesChanged).pipe( map3(([changedAttributes]) => { return { changed: changedAttributes, attributes: participant.attributes }; }), startWith3({ changed: participant.attributes, attributes: participant.attributes }) ); } // src/components/mediaToggle.ts function setupMediaToggle(source, room, options, publishOptions, onError) { const { localParticipant } = room; const getSourceEnabled = (source2, localParticipant2) => { let isEnabled = false; switch (source2) { case Track7.Source.Camera: isEnabled = localParticipant2.isCameraEnabled; break; case Track7.Source.Microphone: isEnabled = localParticipant2.isMicrophoneEnabled; break; case Track7.Source.ScreenShare: isEnabled = localParticipant2.isScreenShareEnabled; break; default: break; } return isEnabled; }; const enabledObserver = observeParticipantMedia(localParticipant).pipe( map4((media) => { return getSourceEnabled(source, media.participant); }), startWith4(getSourceEnabled(source, localParticipant)) ); const pendingSubject = new Subject(); const toggle = (forceState, captureOptions) => __async(this, null, function* () { try { captureOptions != null ? captureOptions : captureOptions = options; pendingSubject.next(true); switch (source) { case Track7.Source.Camera: yield localParticipant.setCameraEnabled( forceState != null ? forceState : !localParticipant.isCameraEnabled, captureOptions, publishOptions ); return localParticipant.isCameraEnabled; case Track7.Source.Microphone: yield localParticipant.setMicrophoneEnabled( forceState != null ? forceState : !localParticipant.isMicrophoneEnabled, captureOptions, publishOptions ); return localParticipant.isMicrophoneEnabled; case Track7.Source.ScreenShare: yield localParticipant.setScreenShareEnabled( forceState != null ? forceState : !localParticipant.isScreenShareEnabled, captureOptions, publishOptions ); return localParticipant.isScreenShareEnabled; default: throw new TypeError("Tried to toggle unsupported source"); } } catch (e) { if (onError && e instanceof Error) { onError == null ? void 0 : onError(e); return void 0; } else { throw e; } } finally { pendingSubject.next(false); } }); const className = prefixClass("button"); return { className, toggle, enabledObserver, pendingObserver: pendingSubject.asObservable() }; } function setupManualToggle() { let state = false; const enabledSubject = new Subject(); const pendingSubject = new Subject(); const toggle = (forceState) => __async(this, null, function* () { pendingSubject.next(true); state = forceState != null ? forceState : !state; enabledSubject.next(state); pendingSubject.next(false); }); const className = prefixClass("button"); return { className, toggle, enabledObserver: enabledSubject.asObservable(), pendingObserver: pendingSubject.asObservable() }; } // src/components/mediaDeviceSelect.ts import { Track as Track8, getBrowser } from "livekit-client"; import { BehaviorSubject } from "rxjs"; function setupDeviceSelector(kind, room, localTrack) { const activeDeviceSubject = new BehaviorSubject(void 0); const activeDeviceObservable = createActiveDeviceObservable(room, kind); const setActiveMediaDevice = (_0, ..._1) => __async(this, [_0, ..._1], function* (id, options = {}) { var _a, _b, _c; if (localTrack) { yield localTrack.setDeviceId(options.exact ? { exact: id } : id); const actualId = yield localTrack.getDeviceId(false); activeDeviceSubject.next( id === "default" && localTrack.mediaStreamTrack.label.startsWith("Default") ? id : actualId ); } else if (room) { const browser = getBrowser(); if (kind === "audiooutput" && ((browser == null ? void 0 : browser.name) === "Safari" || (browser == null ? void 0 : browser.os) === "iOS")) { log.warn(`Switching audio output dev