{ "version": 3, "sources": ["../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/adapters.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/logger.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/connection_monitor.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/internal.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/connection.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscription.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscription_guarantor.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/subscriptions.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/consumer.js", "../../../node_modules/@hotwired/turbo-rails/node_modules/@rails/actioncable/src/index.js", "../../../node_modules/object-assign/index.js", "../../../node_modules/react/cjs/react.production.min.js", "../../../node_modules/react/index.js", "../../../node_modules/scheduler/cjs/scheduler.production.min.js", "../../../node_modules/scheduler/index.js", "../../../node_modules/react-dom/cjs/react-dom.production.min.js", "../../../node_modules/react-dom/index.js", "../../../node_modules/prop-types/lib/ReactPropTypesSecret.js", "../../../node_modules/prop-types/factoryWithThrowingShims.js", "../../../node_modules/prop-types/index.js", "../../../node_modules/isarray/index.js", "../../../node_modules/path-to-regexp/index.js", "../../../node_modules/react-is/cjs/react-is.production.min.js", "../../../node_modules/react-is/index.js", "../../../node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js", "../../../node_modules/moment/moment.js", "../../../node_modules/twilio-video/lib/util/dynamicimport.js", "../../../node_modules/twilio-video/lib/vendor/loglevel.js", "../../../node_modules/twilio-video/package.json", "../../../node_modules/twilio-video/lib/util/constants.js", "../../../node_modules/twilio-video/lib/util/log.js", "../../../node_modules/twilio-video/lib/noisecancellationadapter.ts", "../../../node_modules/twilio-video/lib/media/track/noisecancellationimpl.ts", "../../../node_modules/twilio-video/lib/webrtc/util/index.js", "../../../node_modules/twilio-video/lib/util/sid.js", "../../../node_modules/twilio-video/lib/util/twiliowarning.js", "../../../node_modules/twilio-video/lib/util/index.js", "../../../node_modules/twilio-video/lib/webrtc/util/sdp.js", "../../../node_modules/twilio-video/lib/webrtc/getstats.js", "../../../node_modules/twilio-video/lib/webrtc/getusermedia.js", "../../../node_modules/twilio-video/lib/webrtc/mediastream.js", "../../../node_modules/twilio-video/lib/webrtc/mediastreamtrack.js", "../../../node_modules/twilio-video/lib/webrtc/rtcicecandidate.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/chrome.js", "../../../node_modules/events/events.js", "../../../node_modules/twilio-video/lib/eventtarget.js", "../../../node_modules/twilio-video/lib/webrtc/util/latch.js", "../../../node_modules/twilio-video/lib/webrtc/rtcrtpsender.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/chrome.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/firefox.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/firefox.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/safari.js", "../../../node_modules/twilio-video/lib/webrtc/rtcpeerconnection/index.js", "../../../node_modules/twilio-video/lib/webrtc/rtcsessiondescription/index.js", "../../../node_modules/twilio-video/lib/webrtc/index.js", "../../../node_modules/twilio-video/lib/vendor/inherits.js", "../../../node_modules/twilio-video/lib/util/browserdetection.js", "../../../node_modules/twilio-video/lib/util/localmediarestartdeferreds.js", "../../../node_modules/twilio-video/lib/eventemitter.js", "../../../node_modules/twilio-video/lib/media/track/index.js", "../../../node_modules/twilio-video/lib/media/track/mediatrack.js", "../../../node_modules/twilio-video/lib/media/track/audiotrack.js", "../../../node_modules/twilio-video/lib/webaudio/detectsilence.js", "../../../node_modules/twilio-video/lib/webaudio/audiocontext.js", "../../../node_modules/twilio-video/lib/util/detectsilentaudio.js", "../../../node_modules/twilio-video/lib/util/detectsilentvideo.js", "../../../node_modules/twilio-video/lib/util/documentvisibilitymonitor.js", "../../../node_modules/twilio-video/lib/webaudio/workaround180748.js", "../../../node_modules/twilio-video/lib/queueingeventemitter.js", "../../../node_modules/twilio-video/lib/transceiver.js", "../../../node_modules/twilio-video/lib/media/track/transceiver.js", "../../../node_modules/twilio-video/lib/media/track/sender.js", "../../../node_modules/twilio-video/lib/media/track/localmediatrack.js", "../../../node_modules/twilio-video/lib/media/track/localaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/videoprocessoreventobserver.js", "../../../node_modules/twilio-video/lib/media/track/videotrack.js", "../../../node_modules/twilio-video/lib/media/track/localvideotrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localvideotrack.js", "../../../node_modules/twilio-video/lib/data/transceiver.js", "../../../node_modules/twilio-video/lib/data/sender.js", "../../../node_modules/twilio-video/lib/media/track/localdatatrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/localdatatrack.js", "../../../node_modules/twilio-video/lib/media/track/es5/index.js", "../../../node_modules/twilio-video/lib/createlocaltracks.ts", "../../../node_modules/twilio-video/lib/preflight/timer.ts", "../../../node_modules/twilio-video/lib/preflight/mos.ts", "../../../node_modules/twilio-video/lib/preflight/getCombinedConnectionStats.ts", "../../../node_modules/twilio-video/lib/statemachine.js", "../../../node_modules/twilio-video/lib/util/networkmonitor.js", "../../../node_modules/twilio-video/lib/util/timeout.js", "../../../node_modules/twilio-video/src/ws.js", "../../../node_modules/twilio-video/lib/twilioconnection.js", "../../../node_modules/twilio-video/lib/util/twilioerror.js", "../../../node_modules/twilio-video/lib/util/twilio-video-errors.js", "../../../node_modules/twilio-video/lib/preflight/getturncredentials.ts", "../../../node_modules/twilio-video/lib/preflight/makestat.ts", "../../../node_modules/twilio-video/lib/preflight/syntheticaudio.ts", "../../../node_modules/twilio-video/lib/preflight/syntheticvideo.ts", "../../../node_modules/twilio-video/lib/util/movingaveragedelta.js", "../../../node_modules/twilio-video/lib/util/eventobserver.js", "../../../node_modules/twilio-video/lib/util/insightspublisher/index.js", "../../../node_modules/twilio-video/lib/preflight/preflighttest.ts", "../../../node_modules/twilio-video/lib/util/cancelablepromise.js", "../../../node_modules/twilio-video/lib/cancelableroompromise.js", "../../../node_modules/twilio-video/lib/encodingparameters.js", "../../../node_modules/twilio-video/lib/util/validate.js", "../../../node_modules/twilio-video/lib/media/track/trackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localtrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localaudiotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localdatatrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/localvideotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remotemediatrack.js", "../../../node_modules/twilio-video/lib/media/track/remoteaudiotrack.js", "../../../node_modules/twilio-video/lib/media/track/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remoteaudiotrackpublication.js", "../../../node_modules/twilio-video/lib/media/track/remotedatatrack.js", "../../../node_modules/twilio-video/lib/media/track/remotedatatrackpublication.js", "../../../node_modules/twilio-video/lib/util/nullobserver.js", "../../../node_modules/twilio-video/lib/media/track/remotevideotrack.js", "../../../node_modules/twilio-video/lib/media/track/remotevideotrackpublication.js", "../../../node_modules/twilio-video/lib/participant.js", "../../../node_modules/twilio-video/lib/localparticipant.js", "../../../node_modules/twilio-video/lib/util/insightspublisher/null.js", "../../../node_modules/twilio-video/lib/networkqualityconfiguration.js", "../../../node_modules/twilio-video/lib/remoteparticipant.js", "../../../node_modules/twilio-video/lib/stats/trackstats.js", "../../../node_modules/twilio-video/lib/stats/localtrackstats.js", "../../../node_modules/twilio-video/lib/stats/localaudiotrackstats.js", "../../../node_modules/twilio-video/lib/stats/localvideotrackstats.js", "../../../node_modules/twilio-video/lib/stats/remotetrackstats.js", "../../../node_modules/twilio-video/lib/stats/remoteaudiotrackstats.js", "../../../node_modules/twilio-video/lib/stats/remotevideotrackstats.js", "../../../node_modules/twilio-video/lib/stats/statsreport.js", "../../../node_modules/twilio-video/lib/room.js", "../../../node_modules/twilio-video/lib/util/backoff.js", "../../../node_modules/twilio-video/lib/util/sdp/simulcast.js", "../../../node_modules/twilio-video/lib/util/sdp/index.js", "../../../node_modules/twilio-video/lib/util/filter.js", "../../../node_modules/twilio-video/lib/signaling/v2/icebox.js", "../../../node_modules/twilio-video/lib/signaling/v2/iceconnectionmonitor.js", "../../../node_modules/twilio-video/lib/data/transport.js", "../../../node_modules/twilio-video/lib/data/receiver.js", "../../../node_modules/twilio-video/lib/media/track/receiver.js", "../../../node_modules/twilio-video/lib/util/sdp/trackmatcher.js", "../../../node_modules/twilio-video/lib/util/sdp/issue8329.js", "../../../node_modules/twilio-video/lib/signaling/v2/peerconnection.js", "../../../node_modules/twilio-video/lib/signaling/v2/peerconnectionmanager.js", "../../../node_modules/twilio-video/lib/signaling/v2/mediasignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/dominantspeakersignaling.js", "../../../node_modules/twilio-video/lib/stats/icereport.js", "../../../node_modules/twilio-video/lib/stats/icereportfactory.js", "../../../node_modules/twilio-video/lib/stats/average.js", "../../../node_modules/twilio-video/lib/stats/senderorreceiverreport.js", "../../../node_modules/twilio-video/lib/stats/sum.js", "../../../node_modules/twilio-video/lib/stats/receiverreport.js", "../../../node_modules/twilio-video/lib/stats/senderreport.js", "../../../node_modules/twilio-video/lib/stats/peerconnectionreport.js", "../../../node_modules/twilio-video/lib/stats/senderorreceiverreportfactory.js", "../../../node_modules/twilio-video/lib/stats/receiverreportfactory.js", "../../../node_modules/twilio-video/lib/stats/senderreportfactory.js", "../../../node_modules/twilio-video/lib/stats/peerconnectionreportfactory.js", "../../../node_modules/twilio-video/lib/signaling/v2/networkqualitymonitor.js", "../../../node_modules/twilio-video/lib/util/asyncvar.js", "../../../node_modules/twilio-video/lib/signaling/v2/networkqualitysignaling.js", "../../../node_modules/twilio-video/lib/signaling/recording.js", "../../../node_modules/twilio-video/lib/signaling/v2/recording.js", "../../../node_modules/twilio-video/lib/signaling/room.js", "../../../node_modules/twilio-video/lib/stats/networkqualitybandwidthstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityfractionloststats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitylatencystats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitysendorrecvstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitysendstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityrecvstats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitymediastats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityaudiostats.js", "../../../node_modules/twilio-video/lib/stats/networkqualityvideostats.js", "../../../node_modules/twilio-video/lib/stats/networkqualitystats.js", "../../../node_modules/twilio-video/lib/signaling/participant.js", "../../../node_modules/twilio-video/lib/signaling/remoteparticipant.js", "../../../node_modules/twilio-video/lib/signaling/track.js", "../../../node_modules/twilio-video/lib/signaling/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/remotetrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/remoteparticipant.js", "../../../node_modules/twilio-video/lib/signaling/v2/trackprioritysignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/trackswitchoffsignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/renderhintssignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/publisherhintsignaling.js", "../../../node_modules/twilio-video/lib/signaling/v2/room.js", "../../../node_modules/twilio-video/lib/signaling/v2/twilioconnectiontransport.js", "../../../node_modules/twilio-video/lib/signaling/v2/cancelableroomsignalingpromise.js", "../../../node_modules/twilio-video/lib/signaling/localparticipant.js", "../../../node_modules/twilio-video/lib/signaling/localtrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/localtrackpublication.js", "../../../node_modules/twilio-video/lib/signaling/v2/localparticipant.js", "../../../node_modules/twilio-video/lib/signaling/index.js", "../../../node_modules/twilio-video/lib/signaling/v2/index.js", "../../../node_modules/twilio-video/lib/connect.js", "../../../node_modules/twilio-video/lib/createlocaltrack.js", "../../../node_modules/twilio-video/lib/util/support.js", "../../../node_modules/twilio-video/lib/index.ts", "../../../node_modules/heap/lib/heap.js", "../../../node_modules/heap/index.js", "../../../node_modules/is-buffer/index.js", "../../../node_modules/extend/index.js", "../../../node_modules/mdurl/encode.js", "../../../node_modules/inline-style-parser/index.js", "../../../node_modules/style-to-object/index.js", "../../../node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-is/convert.js", "../../../node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-visit-parents/index.js", "../../../node_modules/@mapbox/hast-util-table-cell-style/node_modules/unist-util-visit/index.js", "../../../node_modules/@mapbox/hast-util-table-cell-style/index.js", "../../../node_modules/grapheme-splitter/index.js", "../../../node_modules/react-youtube/node_modules/prop-types/lib/ReactPropTypesSecret.js", "../../../node_modules/react-youtube/node_modules/prop-types/factoryWithThrowingShims.js", "../../../node_modules/react-youtube/node_modules/prop-types/index.js", "../../../node_modules/fast-deep-equal/index.js", "../../../node_modules/sister/src/sister.js", "../../../node_modules/load-script/index.js", "../../../node_modules/youtube-player/dist/loadYouTubeIframeApi.js", "../../../node_modules/ms/index.js", "../../../node_modules/debug/src/debug.js", "../../../node_modules/debug/src/browser.js", "../../../node_modules/youtube-player/dist/functionNames.js", "../../../node_modules/youtube-player/dist/eventNames.js", "../../../node_modules/youtube-player/dist/constants/PlayerStates.js", "../../../node_modules/youtube-player/dist/FunctionStateMap.js", "../../../node_modules/youtube-player/dist/YouTubePlayer.js", "../../../node_modules/youtube-player/dist/index.js", "../../../node_modules/attr-accept/dist/es/index.js", "../../../node_modules/@hotwired/turbo/dist/turbo.es2017-esm.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/snakeize.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/cable_stream_source_element.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/fetch_requests.js", "../../../node_modules/@hotwired/turbo-rails/app/javascript/turbo/index.js", "../../../node_modules/@rails/activestorage/app/assets/javascripts/activestorage.esm.js", "../../../node_modules/underscore/modules/index.js", "../../../node_modules/underscore/modules/_setup.js", "../../../node_modules/underscore/modules/restArguments.js", "../../../node_modules/underscore/modules/isObject.js", "../../../node_modules/underscore/modules/isNull.js", "../../../node_modules/underscore/modules/isUndefined.js", "../../../node_modules/underscore/modules/isBoolean.js", "../../../node_modules/underscore/modules/isElement.js", "../../../node_modules/underscore/modules/_tagTester.js", "../../../node_modules/underscore/modules/isString.js", "../../../node_modules/underscore/modules/isNumber.js", "../../../node_modules/underscore/modules/isDate.js", "../../../node_modules/underscore/modules/isRegExp.js", "../../../node_modules/underscore/modules/isError.js", "../../../node_modules/underscore/modules/isSymbol.js", "../../../node_modules/underscore/modules/isArrayBuffer.js", "../../../node_modules/underscore/modules/isFunction.js", "../../../node_modules/underscore/modules/_hasObjectTag.js", "../../../node_modules/underscore/modules/_stringTagBug.js", "../../../node_modules/underscore/modules/isDataView.js", "../../../node_modules/underscore/modules/isArray.js", "../../../node_modules/underscore/modules/_has.js", "../../../node_modules/underscore/modules/isArguments.js", "../../../node_modules/underscore/modules/isFinite.js", "../../../node_modules/underscore/modules/isNaN.js", "../../../node_modules/underscore/modules/constant.js", "../../../node_modules/underscore/modules/_createSizePropertyCheck.js", "../../../node_modules/underscore/modules/_shallowProperty.js", "../../../node_modules/underscore/modules/_getByteLength.js", "../../../node_modules/underscore/modules/_isBufferLike.js", "../../../node_modules/underscore/modules/isTypedArray.js", "../../../node_modules/underscore/modules/_getLength.js", "../../../node_modules/underscore/modules/_collectNonEnumProps.js", "../../../node_modules/underscore/modules/keys.js", "../../../node_modules/underscore/modules/isEmpty.js", "../../../node_modules/underscore/modules/isMatch.js", "../../../node_modules/underscore/modules/underscore.js", "../../../node_modules/underscore/modules/_toBufferView.js", "../../../node_modules/underscore/modules/isEqual.js", "../../../node_modules/underscore/modules/allKeys.js", "../../../node_modules/underscore/modules/_methodFingerprint.js", "../../../node_modules/underscore/modules/isMap.js", "../../../node_modules/underscore/modules/isWeakMap.js", "../../../node_modules/underscore/modules/isSet.js", "../../../node_modules/underscore/modules/isWeakSet.js", "../../../node_modules/underscore/modules/values.js", "../../../node_modules/underscore/modules/pairs.js", "../../../node_modules/underscore/modules/invert.js", "../../../node_modules/underscore/modules/functions.js", "../../../node_modules/underscore/modules/_createAssigner.js", "../../../node_modules/underscore/modules/extend.js", "../../../node_modules/underscore/modules/extendOwn.js", "../../../node_modules/underscore/modules/defaults.js", "../../../node_modules/underscore/modules/_baseCreate.js", "../../../node_modules/underscore/modules/create.js", "../../../node_modules/underscore/modules/clone.js", "../../../node_modules/underscore/modules/tap.js", "../../../node_modules/underscore/modules/toPath.js", "../../../node_modules/underscore/modules/_toPath.js", "../../../node_modules/underscore/modules/_deepGet.js", "../../../node_modules/underscore/modules/get.js", "../../../node_modules/underscore/modules/has.js", "../../../node_modules/underscore/modules/identity.js", "../../../node_modules/underscore/modules/matcher.js", "../../../node_modules/underscore/modules/property.js", "../../../node_modules/underscore/modules/_optimizeCb.js", "../../../node_modules/underscore/modules/_baseIteratee.js", "../../../node_modules/underscore/modules/iteratee.js", "../../../node_modules/underscore/modules/_cb.js", "../../../node_modules/underscore/modules/mapObject.js", "../../../node_modules/underscore/modules/noop.js", "../../../node_modules/underscore/modules/propertyOf.js", "../../../node_modules/underscore/modules/times.js", "../../../node_modules/underscore/modules/random.js", "../../../node_modules/underscore/modules/now.js", "../../../node_modules/underscore/modules/_createEscaper.js", "../../../node_modules/underscore/modules/_escapeMap.js", "../../../node_modules/underscore/modules/escape.js", "../../../node_modules/underscore/modules/_unescapeMap.js", "../../../node_modules/underscore/modules/unescape.js", "../../../node_modules/underscore/modules/templateSettings.js", "../../../node_modules/underscore/modules/template.js", "../../../node_modules/underscore/modules/result.js", "../../../node_modules/underscore/modules/uniqueId.js", "../../../node_modules/underscore/modules/chain.js", "../../../node_modules/underscore/modules/_executeBound.js", "../../../node_modules/underscore/modules/partial.js", "../../../node_modules/underscore/modules/bind.js", "../../../node_modules/underscore/modules/_isArrayLike.js", "../../../node_modules/underscore/modules/_flatten.js", "../../../node_modules/underscore/modules/bindAll.js", "../../../node_modules/underscore/modules/memoize.js", "../../../node_modules/underscore/modules/delay.js", "../../../node_modules/underscore/modules/defer.js", "../../../node_modules/underscore/modules/throttle.js", "../../../node_modules/underscore/modules/debounce.js", "../../../node_modules/underscore/modules/wrap.js", "../../../node_modules/underscore/modules/negate.js", "../../../node_modules/underscore/modules/compose.js", "../../../node_modules/underscore/modules/after.js", "../../../node_modules/underscore/modules/before.js", "../../../node_modules/underscore/modules/once.js", "../../../node_modules/underscore/modules/findKey.js", "../../../node_modules/underscore/modules/_createPredicateIndexFinder.js", "../../../node_modules/underscore/modules/findIndex.js", "../../../node_modules/underscore/modules/findLastIndex.js", "../../../node_modules/underscore/modules/sortedIndex.js", "../../../node_modules/underscore/modules/_createIndexFinder.js", "../../../node_modules/underscore/modules/indexOf.js", "../../../node_modules/underscore/modules/lastIndexOf.js", "../../../node_modules/underscore/modules/find.js", "../../../node_modules/underscore/modules/findWhere.js", "../../../node_modules/underscore/modules/each.js", "../../../node_modules/underscore/modules/map.js", "../../../node_modules/underscore/modules/_createReduce.js", "../../../node_modules/underscore/modules/reduce.js", "../../../node_modules/underscore/modules/reduceRight.js", "../../../node_modules/underscore/modules/filter.js", "../../../node_modules/underscore/modules/reject.js", "../../../node_modules/underscore/modules/every.js", "../../../node_modules/underscore/modules/some.js", "../../../node_modules/underscore/modules/contains.js", "../../../node_modules/underscore/modules/invoke.js", "../../../node_modules/underscore/modules/pluck.js", "../../../node_modules/underscore/modules/where.js", "../../../node_modules/underscore/modules/max.js", "../../../node_modules/underscore/modules/min.js", "../../../node_modules/underscore/modules/toArray.js", "../../../node_modules/underscore/modules/sample.js", "../../../node_modules/underscore/modules/shuffle.js", "../../../node_modules/underscore/modules/sortBy.js", "../../../node_modules/underscore/modules/_group.js", "../../../node_modules/underscore/modules/groupBy.js", "../../../node_modules/underscore/modules/indexBy.js", "../../../node_modules/underscore/modules/countBy.js", "../../../node_modules/underscore/modules/partition.js", "../../../node_modules/underscore/modules/size.js", "../../../node_modules/underscore/modules/_keyInObj.js", "../../../node_modules/underscore/modules/pick.js", "../../../node_modules/underscore/modules/omit.js", "../../../node_modules/underscore/modules/initial.js", "../../../node_modules/underscore/modules/first.js", "../../../node_modules/underscore/modules/rest.js", "../../../node_modules/underscore/modules/last.js", "../../../node_modules/underscore/modules/compact.js", "../../../node_modules/underscore/modules/flatten.js", "../../../node_modules/underscore/modules/difference.js", "../../../node_modules/underscore/modules/without.js", "../../../node_modules/underscore/modules/uniq.js", "../../../node_modules/underscore/modules/union.js", "../../../node_modules/underscore/modules/intersection.js", "../../../node_modules/underscore/modules/unzip.js", "../../../node_modules/underscore/modules/zip.js", "../../../node_modules/underscore/modules/object.js", "../../../node_modules/underscore/modules/range.js", "../../../node_modules/underscore/modules/chunk.js", "../../../node_modules/underscore/modules/_chainResult.js", "../../../node_modules/underscore/modules/mixin.js", "../../../node_modules/underscore/modules/underscore-array-methods.js", "../../../node_modules/underscore/modules/index-default.js", "../../javascript/emoji_utils.js", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Picker/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Picker/Picker.tsx", "../../../node_modules/emoji-mart/dist/node_modules/@swc/helpers/src/_define_property.mjs", "../../../node_modules/emoji-mart/dist/node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/jsx-runtime/src/index.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/dist/preact.module.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/constants.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/util.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/options.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/create-element.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/component.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/create-context.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/diff/children.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/diff/props.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/diff/index.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/render.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/clone-element.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/src/diff/catch-error.js", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/utils.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/helpers/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/helpers/store.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/helpers/native-support.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/helpers/frequently-used.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/helpers/search-index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/config.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart-data/i18n/en.json", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Picker/PickerProps.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/icons.tsx", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Emoji/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Emoji/Emoji.tsx", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Emoji/EmojiElement.jsx", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/HTMLElement/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/HTMLElement/HTMLElement.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/HTMLElement/ShadowElement.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Emoji/EmojiProps.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Navigation/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Navigation/Navigation.tsx", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/dist/compat.module.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/util.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/PureComponent.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/memo.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/forwardRef.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/Children.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/suspense.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/suspense-list.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/portals.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/render.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/compat/src/index.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/hooks/dist/hooks.module.js", "../../../node_modules/emoji-mart/dist/node_modules/preact/hooks/src/index.js", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/HOCs/index.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/HOCs/PureInlineComponent.ts", "../../../node_modules/emoji-mart/dist/packages/emoji-mart/src/components/Picker/PickerElement.tsx", "../../../node_modules/emoji-mart/dist/node_modules/@parcel/runtime-js/lib/bundles/runtime-1b9572f9f2947a02.js", "../../../node_modules/@emoji-mart/data/sets/14/apple.json", "../../../node_modules/@hotwired/stimulus/dist/stimulus.js", "../../javascript/controllers/application.js", "../../javascript/grid_world.js", "../../../node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js", "../../../node_modules/@babel/runtime/helpers/esm/inheritsLoose.js", "../../../node_modules/@babel/runtime/helpers/esm/extends.js", "../../../node_modules/resolve-pathname/esm/resolve-pathname.js", "../../../node_modules/tiny-invariant/dist/esm/tiny-invariant.js", "../../../node_modules/history/esm/history.js", "../../../node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js", "../../../node_modules/react-router/modules/miniCreateReactContext.js", "../../../node_modules/react-router/modules/createContext.js", "../../../node_modules/react-router/modules/createNamedContext.js", "../../../node_modules/react-router/modules/HistoryContext.js", "../../../node_modules/react-router/modules/RouterContext.js", "../../../node_modules/react-router/modules/Router.js", "../../../node_modules/react-router/modules/MemoryRouter.js", "../../../node_modules/react-router/modules/Lifecycle.js", "../../../node_modules/react-router/modules/Prompt.js", "../../../node_modules/react-router/modules/generatePath.js", "../../../node_modules/react-router/modules/Redirect.js", "../../../node_modules/react-router/modules/matchPath.js", "../../../node_modules/react-router/modules/Route.js", "../../../node_modules/react-router/modules/StaticRouter.js", "../../../node_modules/react-router/modules/Switch.js", "../../../node_modules/react-router/modules/withRouter.js", "../../../node_modules/react-router/modules/hooks.js", "../../../node_modules/react-router/modules/index.js", "../../../node_modules/react-router-dom/modules/BrowserRouter.js", "../../../node_modules/react-router-dom/modules/HashRouter.js", "../../../node_modules/react-router-dom/modules/utils/locationUtils.js", "../../../node_modules/react-router-dom/modules/Link.js", "../../../node_modules/react-router-dom/modules/NavLink.js", "../../../node_modules/dom-helpers/esm/hasClass.js", "../../../node_modules/dom-helpers/esm/addClass.js", "../../../node_modules/dom-helpers/esm/removeClass.js", "../../../node_modules/react-transition-group/esm/CSSTransition.js", "../../../node_modules/react-transition-group/esm/Transition.js", "../../../node_modules/react-transition-group/esm/config.js", "../../../node_modules/react-transition-group/esm/TransitionGroupContext.js", "../../../node_modules/react-transition-group/esm/utils/reflow.js", "../../../node_modules/bail/index.js", "../../../node_modules/unified/lib/index.js", "../../../node_modules/is-plain-obj/index.js", "../../../node_modules/trough/index.js", "../../../node_modules/vfile/lib/index.js", "../../../node_modules/unist-util-stringify-position/index.js", "../../../node_modules/vfile-message/index.js", "../../../node_modules/vfile/lib/minpath.browser.js", "../../../node_modules/vfile/lib/minproc.browser.js", "../../../node_modules/vfile/lib/minurl.shared.js", "../../../node_modules/vfile/lib/minurl.browser.js", "../../../node_modules/mdast-util-to-string/index.js", "../../../node_modules/micromark-util-chunked/index.js", "../../../node_modules/micromark-util-combine-extensions/index.js", "../../../node_modules/micromark-util-character/lib/unicode-punctuation-regex.js", "../../../node_modules/micromark-util-character/index.js", "../../../node_modules/micromark-factory-space/index.js", "../../../node_modules/micromark/lib/initialize/content.js", "../../../node_modules/micromark/lib/initialize/document.js", "../../../node_modules/micromark-util-classify-character/index.js", "../../../node_modules/micromark-util-resolve-all/index.js", "../../../node_modules/micromark-core-commonmark/lib/attention.js", "../../../node_modules/micromark-core-commonmark/lib/autolink.js", "../../../node_modules/micromark-core-commonmark/lib/blank-line.js", "../../../node_modules/micromark-core-commonmark/lib/block-quote.js", "../../../node_modules/micromark-core-commonmark/lib/character-escape.js", "../../../node_modules/decode-named-character-reference/index.dom.js", "../../../node_modules/micromark-core-commonmark/lib/character-reference.js", "../../../node_modules/micromark-core-commonmark/lib/code-fenced.js", "../../../node_modules/micromark-core-commonmark/lib/code-indented.js", "../../../node_modules/micromark-core-commonmark/lib/code-text.js", "../../../node_modules/micromark-util-subtokenize/index.js", "../../../node_modules/micromark-core-commonmark/lib/content.js", "../../../node_modules/micromark-factory-destination/index.js", "../../../node_modules/micromark-factory-label/index.js", "../../../node_modules/micromark-factory-title/index.js", "../../../node_modules/micromark-factory-whitespace/index.js", "../../../node_modules/micromark-util-normalize-identifier/index.js", "../../../node_modules/micromark-core-commonmark/lib/definition.js", "../../../node_modules/micromark-core-commonmark/lib/hard-break-escape.js", "../../../node_modules/micromark-core-commonmark/lib/heading-atx.js", "../../../node_modules/micromark-util-html-tag-name/index.js", "../../../node_modules/micromark-core-commonmark/lib/html-flow.js", "../../../node_modules/micromark-core-commonmark/lib/html-text.js", "../../../node_modules/micromark-core-commonmark/lib/label-end.js", "../../../node_modules/micromark-core-commonmark/lib/label-start-image.js", "../../../node_modules/micromark-core-commonmark/lib/label-start-link.js", "../../../node_modules/micromark-core-commonmark/lib/line-ending.js", "../../../node_modules/micromark-core-commonmark/lib/thematic-break.js", "../../../node_modules/micromark-core-commonmark/lib/list.js", "../../../node_modules/micromark-core-commonmark/lib/setext-underline.js", "../../../node_modules/micromark/lib/initialize/flow.js", "../../../node_modules/micromark/lib/initialize/text.js", "../../../node_modules/micromark/lib/create-tokenizer.js", "../../../node_modules/micromark/lib/constructs.js", "../../../node_modules/micromark/lib/parse.js", "../../../node_modules/micromark/lib/preprocess.js", "../../../node_modules/micromark/lib/postprocess.js", "../../../node_modules/micromark-util-decode-numeric-character-reference/index.js", "../../../node_modules/micromark-util-decode-string/index.js", "../../../node_modules/mdast-util-from-markdown/lib/index.js", "../../../node_modules/remark-parse/lib/index.js", "../../../node_modules/remark-parse/index.js", "../../../node_modules/unist-builder/index.js", "../../../node_modules/mdast-util-to-hast/lib/traverse.js", "../../../node_modules/unist-util-is/index.js", "../../../node_modules/unist-util-visit-parents/index.js", "../../../node_modules/unist-util-visit/index.js", "../../../node_modules/unist-util-position/index.js", "../../../node_modules/unist-util-generated/lib/index.js", "../../../node_modules/mdast-util-definitions/index.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/thematic-break.js", "../../../node_modules/mdast-util-to-hast/lib/wrap.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/list.js", "../../../node_modules/mdast-util-to-hast/lib/footer.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/blockquote.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/break.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/code.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/delete.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/emphasis.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/footnote-reference.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/footnote.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/heading.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/html.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/image-reference.js", "../../../node_modules/mdast-util-to-hast/lib/revert.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/image.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/inline-code.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/link-reference.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/link.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/list-item.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/paragraph.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/root.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/strong.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/table.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/text.js", "../../../node_modules/mdast-util-to-hast/lib/handlers/index.js", "../../../node_modules/mdast-util-to-hast/lib/index.js", "../../../node_modules/hast-util-sanitize/lib/schema.js", "../../../node_modules/hast-util-sanitize/lib/index.js", "../../../node_modules/property-information/lib/util/schema.js", "../../../node_modules/property-information/lib/util/merge.js", "../../../node_modules/property-information/lib/normalize.js", "../../../node_modules/property-information/lib/util/info.js", "../../../node_modules/property-information/lib/util/types.js", "../../../node_modules/property-information/lib/util/defined-info.js", "../../../node_modules/property-information/lib/util/create.js", "../../../node_modules/property-information/lib/xlink.js", "../../../node_modules/property-information/lib/xml.js", "../../../node_modules/property-information/lib/util/case-sensitive-transform.js", "../../../node_modules/property-information/lib/util/case-insensitive-transform.js", "../../../node_modules/property-information/lib/xmlns.js", "../../../node_modules/property-information/lib/aria.js", "../../../node_modules/property-information/lib/html.js", "../../../node_modules/property-information/lib/svg.js", "../../../node_modules/property-information/lib/find.js", "../../../node_modules/property-information/lib/hast-to-react.js", "../../../node_modules/property-information/index.js", "../../../node_modules/space-separated-tokens/index.js", "../../../node_modules/comma-separated-tokens/index.js", "../../../node_modules/style-to-object/index.mjs", "../../../node_modules/web-namespaces/index.js", "../../../node_modules/hast-to-hyperscript/lib/index.js", "../../../node_modules/remark-react/lib/index.js", "../../../node_modules/remark-react/index.js", "../../../node_modules/is-absolute-url/index.js", "../../../node_modules/remark-external-links/index.js", "../../../node_modules/@rails/request.js/src/fetch_response.js", "../../../node_modules/@rails/request.js/src/request_interceptor.js", "../../../node_modules/@rails/request.js/src/lib/utils.js", "../../../node_modules/@rails/request.js/src/fetch_request.js", "../../../node_modules/@rails/request.js/src/verbs.js", "../../../node_modules/resize-observer-polyfill/dist/ResizeObserver.es.js", "../../../node_modules/@rails/actioncable/app/assets/javascripts/actioncable.esm.js", "../../javascript/channels/consumer.js", "../../javascript/viewport_context.js", "../../javascript/tooltip.js", "../../javascript/utils.js", "../../javascript/popover.js", "../../javascript/modal.js", "../../javascript/click_outside.js", "../../javascript/rest_client.js", "../../javascript/use_event_listener.js", "../../javascript/use_previous.js", "../../javascript/use_interval.js", "../../javascript/use_action_cable.js", "../../javascript/emoji_and_text_input.js", "../../../node_modules/@emoji-mart/react/dist/packages/emoji-mart-react/react.tsx", "../../javascript/date_picker.js", "../../javascript/youtube_player.js", "../../../node_modules/react-youtube/dist/index.esm.js", "../../javascript/use_sticky_state.js", "../../javascript/marquee.js", "../../javascript/screenshare_player.js", "../../javascript/shared_timer.js", "../../javascript/use_title.js", "../../javascript/components/ngw_banner.js", "../../javascript/image_uploader.js", "../../../node_modules/react-dropzone/dist/es/index.js", "../../../node_modules/tslib/tslib.es6.js", "../../../node_modules/file-selector/src/file.ts", "../../../node_modules/file-selector/src/file-selector.ts", "../../../node_modules/react-dropzone/dist/es/utils/index.js", "../../javascript/use_direct_upload.js", "../../javascript/controllers/grid_world_controller.js", "../../javascript/controllers/react_controller.js", "../../javascript/components/index.js", "../../javascript/components/safe_mode_indicator.js", "../../javascript/controllers/index.js", "../../javascript/application.js"], "sourcesContent": ["export default {\n logger: self.console,\n WebSocket: self.WebSocket\n}\n", "import adapters from \"./adapters\"\n\n// The logger is disabled by default. You can enable it with:\n//\n// ActionCable.logger.enabled = true\n//\n// Example:\n//\n// import * as ActionCable from '@rails/actioncable'\n//\n// ActionCable.logger.enabled = true\n// ActionCable.logger.log('Connection Established.')\n//\n\nexport default {\n log(...messages) {\n if (this.enabled) {\n messages.push(Date.now())\n adapters.logger.log(\"[ActionCable]\", ...messages)\n }\n },\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting\n// revival reconnections if things go astray. Internal class, not intended for direct user manipulation.\n\nconst now = () => new Date().getTime()\n\nconst secondsSince = time => (now() - time) / 1000\n\nclass ConnectionMonitor {\n constructor(connection) {\n this.visibilityDidChange = this.visibilityDidChange.bind(this)\n this.connection = connection\n this.reconnectAttempts = 0\n }\n\n start() {\n if (!this.isRunning()) {\n this.startedAt = now()\n delete this.stoppedAt\n this.startPolling()\n addEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(`ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`)\n }\n }\n\n stop() {\n if (this.isRunning()) {\n this.stoppedAt = now()\n this.stopPolling()\n removeEventListener(\"visibilitychange\", this.visibilityDidChange)\n logger.log(\"ConnectionMonitor stopped\")\n }\n }\n\n isRunning() {\n return this.startedAt && !this.stoppedAt\n }\n\n recordPing() {\n this.pingedAt = now()\n }\n\n recordConnect() {\n this.reconnectAttempts = 0\n this.recordPing()\n delete this.disconnectedAt\n logger.log(\"ConnectionMonitor recorded connect\")\n }\n\n recordDisconnect() {\n this.disconnectedAt = now()\n logger.log(\"ConnectionMonitor recorded disconnect\")\n }\n\n // Private\n\n startPolling() {\n this.stopPolling()\n this.poll()\n }\n\n stopPolling() {\n clearTimeout(this.pollTimeout)\n }\n\n poll() {\n this.pollTimeout = setTimeout(() => {\n this.reconnectIfStale()\n this.poll()\n }\n , this.getPollInterval())\n }\n\n getPollInterval() {\n const { staleThreshold, reconnectionBackoffRate } = this.constructor\n const backoff = Math.pow(1 + reconnectionBackoffRate, Math.min(this.reconnectAttempts, 10))\n const jitterMax = this.reconnectAttempts === 0 ? 1.0 : reconnectionBackoffRate\n const jitter = jitterMax * Math.random()\n return staleThreshold * 1000 * backoff * (1 + jitter)\n }\n\n reconnectIfStale() {\n if (this.connectionIsStale()) {\n logger.log(`ConnectionMonitor detected stale connection. reconnectAttempts = ${this.reconnectAttempts}, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${this.constructor.staleThreshold} s`)\n this.reconnectAttempts++\n if (this.disconnectedRecently()) {\n logger.log(`ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince(this.disconnectedAt)} s`)\n } else {\n logger.log(\"ConnectionMonitor reopening\")\n this.connection.reopen()\n }\n }\n }\n\n get refreshedAt() {\n return this.pingedAt ? this.pingedAt : this.startedAt\n }\n\n connectionIsStale() {\n return secondsSince(this.refreshedAt) > this.constructor.staleThreshold\n }\n\n disconnectedRecently() {\n return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)\n }\n\n visibilityDidChange() {\n if (document.visibilityState === \"visible\") {\n setTimeout(() => {\n if (this.connectionIsStale() || !this.connection.isOpen()) {\n logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`)\n this.connection.reopen()\n }\n }\n , 200)\n }\n }\n\n}\n\nConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)\nConnectionMonitor.reconnectionBackoffRate = 0.15\n\nexport default ConnectionMonitor\n", "export default {\n \"message_types\": {\n \"welcome\": \"welcome\",\n \"disconnect\": \"disconnect\",\n \"ping\": \"ping\",\n \"confirmation\": \"confirm_subscription\",\n \"rejection\": \"reject_subscription\"\n },\n \"disconnect_reasons\": {\n \"unauthorized\": \"unauthorized\",\n \"invalid_request\": \"invalid_request\",\n \"server_restart\": \"server_restart\"\n },\n \"default_mount_path\": \"/cable\",\n \"protocols\": [\n \"actioncable-v1-json\",\n \"actioncable-unsupported\"\n ]\n}\n", "import adapters from \"./adapters\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport INTERNAL from \"./internal\"\nimport logger from \"./logger\"\n\n// Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.\n\nconst {message_types, protocols} = INTERNAL\nconst supportedProtocols = protocols.slice(0, protocols.length - 1)\n\nconst indexOf = [].indexOf\n\nclass Connection {\n constructor(consumer) {\n this.open = this.open.bind(this)\n this.consumer = consumer\n this.subscriptions = this.consumer.subscriptions\n this.monitor = new ConnectionMonitor(this)\n this.disconnected = true\n }\n\n send(data) {\n if (this.isOpen()) {\n this.webSocket.send(JSON.stringify(data))\n return true\n } else {\n return false\n }\n }\n\n open() {\n if (this.isActive()) {\n logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`)\n return false\n } else {\n logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`)\n if (this.webSocket) { this.uninstallEventHandlers() }\n this.webSocket = new adapters.WebSocket(this.consumer.url, protocols)\n this.installEventHandlers()\n this.monitor.start()\n return true\n }\n }\n\n close({allowReconnect} = {allowReconnect: true}) {\n if (!allowReconnect) { this.monitor.stop() }\n // Avoid closing websockets in a \"connecting\" state due to Safari 15.1+ bug. See: https://github.com/rails/rails/issues/43835#issuecomment-1002288478\n if (this.isOpen()) {\n return this.webSocket.close()\n }\n }\n\n reopen() {\n logger.log(`Reopening WebSocket, current state is ${this.getState()}`)\n if (this.isActive()) {\n try {\n return this.close()\n } catch (error) {\n logger.log(\"Failed to reopen WebSocket\", error)\n }\n finally {\n logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`)\n setTimeout(this.open, this.constructor.reopenDelay)\n }\n } else {\n return this.open()\n }\n }\n\n getProtocol() {\n if (this.webSocket) {\n return this.webSocket.protocol\n }\n }\n\n isOpen() {\n return this.isState(\"open\")\n }\n\n isActive() {\n return this.isState(\"open\", \"connecting\")\n }\n\n // Private\n\n isProtocolSupported() {\n return indexOf.call(supportedProtocols, this.getProtocol()) >= 0\n }\n\n isState(...states) {\n return indexOf.call(states, this.getState()) >= 0\n }\n\n getState() {\n if (this.webSocket) {\n for (let state in adapters.WebSocket) {\n if (adapters.WebSocket[state] === this.webSocket.readyState) {\n return state.toLowerCase()\n }\n }\n }\n return null\n }\n\n installEventHandlers() {\n for (let eventName in this.events) {\n const handler = this.events[eventName].bind(this)\n this.webSocket[`on${eventName}`] = handler\n }\n }\n\n uninstallEventHandlers() {\n for (let eventName in this.events) {\n this.webSocket[`on${eventName}`] = function() {}\n }\n }\n\n}\n\nConnection.reopenDelay = 500\n\nConnection.prototype.events = {\n message(event) {\n if (!this.isProtocolSupported()) { return }\n const {identifier, message, reason, reconnect, type} = JSON.parse(event.data)\n switch (type) {\n case message_types.welcome:\n this.monitor.recordConnect()\n return this.subscriptions.reload()\n case message_types.disconnect:\n logger.log(`Disconnecting. Reason: ${reason}`)\n return this.close({allowReconnect: reconnect})\n case message_types.ping:\n return this.monitor.recordPing()\n case message_types.confirmation:\n this.subscriptions.confirmSubscription(identifier)\n return this.subscriptions.notify(identifier, \"connected\")\n case message_types.rejection:\n return this.subscriptions.reject(identifier)\n default:\n return this.subscriptions.notify(identifier, \"received\", message)\n }\n },\n\n open() {\n logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`)\n this.disconnected = false\n if (!this.isProtocolSupported()) {\n logger.log(\"Protocol is unsupported. Stopping monitor and disconnecting.\")\n return this.close({allowReconnect: false})\n }\n },\n\n close(event) {\n logger.log(\"WebSocket onclose event\")\n if (this.disconnected) { return }\n this.disconnected = true\n this.monitor.recordDisconnect()\n return this.subscriptions.notifyAll(\"disconnected\", {willAttemptReconnect: this.monitor.isRunning()})\n },\n\n error() {\n logger.log(\"WebSocket onerror event\")\n }\n}\n\nexport default Connection\n", "// A new subscription is created through the ActionCable.Subscriptions instance available on the consumer.\n// It provides a number of callbacks and a method for calling remote procedure calls on the corresponding\n// Channel instance on the server side.\n//\n// An example demonstrates the basic functionality:\n//\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\", {\n// connected() {\n// // Called once the subscription has been successfully completed\n// },\n//\n// disconnected({ willAttemptReconnect: boolean }) {\n// // Called when the client has disconnected with the server.\n// // The object will have an `willAttemptReconnect` property which\n// // says whether the client has the intention of attempting\n// // to reconnect.\n// },\n//\n// appear() {\n// this.perform('appear', {appearing_on: this.appearingOn()})\n// },\n//\n// away() {\n// this.perform('away')\n// },\n//\n// appearingOn() {\n// $('main').data('appearing-on')\n// }\n// })\n//\n// The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server\n// by calling the `perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away).\n// The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter.\n//\n// This is how the server component would look:\n//\n// class AppearanceChannel < ApplicationActionCable::Channel\n// def subscribed\n// current_user.appear\n// end\n//\n// def unsubscribed\n// current_user.disappear\n// end\n//\n// def appear(data)\n// current_user.appear on: data['appearing_on']\n// end\n//\n// def away\n// current_user.away\n// end\n// end\n//\n// The \"AppearanceChannel\" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.\n// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the perform method.\n\nconst extend = function(object, properties) {\n if (properties != null) {\n for (let key in properties) {\n const value = properties[key]\n object[key] = value\n }\n }\n return object\n}\n\nexport default class Subscription {\n constructor(consumer, params = {}, mixin) {\n this.consumer = consumer\n this.identifier = JSON.stringify(params)\n extend(this, mixin)\n }\n\n // Perform a channel action with the optional data passed as an attribute\n perform(action, data = {}) {\n data.action = action\n return this.send(data)\n }\n\n send(data) {\n return this.consumer.send({command: \"message\", identifier: this.identifier, data: JSON.stringify(data)})\n }\n\n unsubscribe() {\n return this.consumer.subscriptions.remove(this)\n }\n}\n", "import logger from \"./logger\"\n\n// Responsible for ensuring channel subscribe command is confirmed, retrying until confirmation is received.\n// Internal class, not intended for direct user manipulation.\n\nclass SubscriptionGuarantor {\n constructor(subscriptions) {\n this.subscriptions = subscriptions\n this.pendingSubscriptions = []\n }\n\n guarantee(subscription) {\n if(this.pendingSubscriptions.indexOf(subscription) == -1){ \n logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`)\n this.pendingSubscriptions.push(subscription) \n }\n else {\n logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`)\n }\n this.startGuaranteeing()\n }\n\n forget(subscription) {\n logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`)\n this.pendingSubscriptions = (this.pendingSubscriptions.filter((s) => s !== subscription))\n }\n\n startGuaranteeing() {\n this.stopGuaranteeing()\n this.retrySubscribing()\n }\n \n stopGuaranteeing() {\n clearTimeout(this.retryTimeout)\n }\n\n retrySubscribing() {\n this.retryTimeout = setTimeout(() => {\n if (this.subscriptions && typeof(this.subscriptions.subscribe) === \"function\") {\n this.pendingSubscriptions.map((subscription) => {\n logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`)\n this.subscriptions.subscribe(subscription)\n })\n }\n }\n , 500)\n }\n}\n\nexport default SubscriptionGuarantor", "import Subscription from \"./subscription\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport logger from \"./logger\"\n\n// Collection class for creating (and internally managing) channel subscriptions.\n// The only method intended to be triggered by the user is ActionCable.Subscriptions#create,\n// and it should be called through the consumer like so:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n\nexport default class Subscriptions {\n constructor(consumer) {\n this.consumer = consumer\n this.guarantor = new SubscriptionGuarantor(this)\n this.subscriptions = []\n }\n\n create(channelName, mixin) {\n const channel = channelName\n const params = typeof channel === \"object\" ? channel : {channel}\n const subscription = new Subscription(this.consumer, params, mixin)\n return this.add(subscription)\n }\n\n // Private\n\n add(subscription) {\n this.subscriptions.push(subscription)\n this.consumer.ensureActiveConnection()\n this.notify(subscription, \"initialized\")\n this.subscribe(subscription)\n return subscription\n }\n\n remove(subscription) {\n this.forget(subscription)\n if (!this.findAll(subscription.identifier).length) {\n this.sendCommand(subscription, \"unsubscribe\")\n }\n return subscription\n }\n\n reject(identifier) {\n return this.findAll(identifier).map((subscription) => {\n this.forget(subscription)\n this.notify(subscription, \"rejected\")\n return subscription\n })\n }\n\n forget(subscription) {\n this.guarantor.forget(subscription)\n this.subscriptions = (this.subscriptions.filter((s) => s !== subscription))\n return subscription\n }\n\n findAll(identifier) {\n return this.subscriptions.filter((s) => s.identifier === identifier)\n }\n\n reload() {\n return this.subscriptions.map((subscription) =>\n this.subscribe(subscription))\n }\n\n notifyAll(callbackName, ...args) {\n return this.subscriptions.map((subscription) =>\n this.notify(subscription, callbackName, ...args))\n }\n\n notify(subscription, callbackName, ...args) {\n let subscriptions\n if (typeof subscription === \"string\") {\n subscriptions = this.findAll(subscription)\n } else {\n subscriptions = [subscription]\n }\n\n return subscriptions.map((subscription) =>\n (typeof subscription[callbackName] === \"function\" ? subscription[callbackName](...args) : undefined))\n }\n\n subscribe(subscription) {\n if (this.sendCommand(subscription, \"subscribe\")) {\n this.guarantor.guarantee(subscription)\n }\n }\n\n confirmSubscription(identifier) {\n logger.log(`Subscription confirmed ${identifier}`)\n this.findAll(identifier).map((subscription) =>\n this.guarantor.forget(subscription))\n }\n\n sendCommand(subscription, command) {\n const {identifier} = subscription\n return this.consumer.send({command, identifier})\n }\n}\n", "import Connection from \"./connection\"\nimport Subscriptions from \"./subscriptions\"\n\n// The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established,\n// the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates.\n// The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription\n// method.\n//\n// The following example shows how this can be set up:\n//\n// App = {}\n// App.cable = ActionCable.createConsumer(\"ws://example.com/accounts/1\")\n// App.appearance = App.cable.subscriptions.create(\"AppearanceChannel\")\n//\n// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.\n//\n// When a consumer is created, it automatically connects with the server.\n//\n// To disconnect from the server, call\n//\n// App.cable.disconnect()\n//\n// and to restart the connection:\n//\n// App.cable.connect()\n//\n// Any channel subscriptions which existed prior to disconnecting will\n// automatically resubscribe.\n\nexport default class Consumer {\n constructor(url) {\n this._url = url\n this.subscriptions = new Subscriptions(this)\n this.connection = new Connection(this)\n }\n\n get url() {\n return createWebSocketURL(this._url)\n }\n\n send(data) {\n return this.connection.send(data)\n }\n\n connect() {\n return this.connection.open()\n }\n\n disconnect() {\n return this.connection.close({allowReconnect: false})\n }\n\n ensureActiveConnection() {\n if (!this.connection.isActive()) {\n return this.connection.open()\n }\n }\n}\n\nexport function createWebSocketURL(url) {\n if (typeof url === \"function\") {\n url = url()\n }\n\n if (url && !/^wss?:/i.test(url)) {\n const a = document.createElement(\"a\")\n a.href = url\n // Fix populating Location properties in IE. Otherwise, protocol will be blank.\n a.href = a.href\n a.protocol = a.protocol.replace(\"http\", \"ws\")\n return a.href\n } else {\n return url\n }\n}\n", "import Connection from \"./connection\"\nimport ConnectionMonitor from \"./connection_monitor\"\nimport Consumer, { createWebSocketURL } from \"./consumer\"\nimport INTERNAL from \"./internal\"\nimport Subscription from \"./subscription\"\nimport Subscriptions from \"./subscriptions\"\nimport SubscriptionGuarantor from \"./subscription_guarantor\"\nimport adapters from \"./adapters\"\nimport logger from \"./logger\"\n\nexport {\n Connection,\n ConnectionMonitor,\n Consumer,\n INTERNAL,\n Subscription,\n Subscriptions,\n SubscriptionGuarantor,\n adapters,\n createWebSocketURL,\n logger,\n}\n\nexport function createConsumer(url = getConfig(\"url\") || INTERNAL.default_mount_path) {\n return new Consumer(url)\n}\n\nexport function getConfig(name) {\n const element = document.head.querySelector(`meta[name='action-cable-${name}']`)\n if (element) {\n return element.getAttribute(\"content\")\n }\n}\n", "/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n'use strict';\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n", "/** @license React v16.14.0\n * react.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';var l=require(\"object-assign\"),n=\"function\"===typeof Symbol&&Symbol.for,p=n?Symbol.for(\"react.element\"):60103,q=n?Symbol.for(\"react.portal\"):60106,r=n?Symbol.for(\"react.fragment\"):60107,t=n?Symbol.for(\"react.strict_mode\"):60108,u=n?Symbol.for(\"react.profiler\"):60114,v=n?Symbol.for(\"react.provider\"):60109,w=n?Symbol.for(\"react.context\"):60110,x=n?Symbol.for(\"react.forward_ref\"):60112,y=n?Symbol.for(\"react.suspense\"):60113,z=n?Symbol.for(\"react.memo\"):60115,A=n?Symbol.for(\"react.lazy\"):\n60116,B=\"function\"===typeof Symbol&&Symbol.iterator;function C(a){for(var b=\"https://reactjs.org/docs/error-decoder.html?invariant=\"+a,c=1;cQ.length&&Q.push(a)}\nfunction T(a,b,c,e){var d=typeof a;if(\"undefined\"===d||\"boolean\"===d)a=null;var g=!1;if(null===a)g=!0;else switch(d){case \"string\":case \"number\":g=!0;break;case \"object\":switch(a.$$typeof){case p:case q:g=!0}}if(g)return c(e,a,\"\"===b?\".\"+U(a,0):b),1;g=0;b=\"\"===b?\".\":b+\":\";if(Array.isArray(a))for(var k=0;k=G};l=function(){};exports.unstable_forceFrameRate=function(a){0>a||125>>1,e=a[d];if(void 0!==e&&0K(n,c))void 0!==r&&0>K(r,n)?(a[d]=r,a[v]=c,d=v):(a[d]=n,a[m]=c,d=m);else if(void 0!==r&&0>K(r,c))a[d]=r,a[v]=c,d=v;else break a}}return b}return null}function K(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}var N=[],O=[],P=1,Q=null,R=3,S=!1,T=!1,U=!1;\nfunction V(a){for(var b=L(O);null!==b;){if(null===b.callback)M(O);else if(b.startTime<=a)M(O),b.sortIndex=b.expirationTime,J(N,b);else break;b=L(O)}}function W(a){U=!1;V(a);if(!T)if(null!==L(N))T=!0,f(X);else{var b=L(O);null!==b&&g(W,b.startTime-a)}}\nfunction X(a,b){T=!1;U&&(U=!1,h());S=!0;var c=R;try{V(b);for(Q=L(N);null!==Q&&(!(Q.expirationTime>b)||a&&!k());){var d=Q.callback;if(null!==d){Q.callback=null;R=Q.priorityLevel;var e=d(Q.expirationTime<=b);b=exports.unstable_now();\"function\"===typeof e?Q.callback=e:Q===L(N)&&M(N);V(b)}else M(N);Q=L(N)}if(null!==Q)var m=!0;else{var n=L(O);null!==n&&g(W,n.startTime-b);m=!1}return m}finally{Q=null,R=c,S=!1}}\nfunction Y(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var Z=l;exports.unstable_IdlePriority=5;exports.unstable_ImmediatePriority=1;exports.unstable_LowPriority=4;exports.unstable_NormalPriority=3;exports.unstable_Profiling=null;exports.unstable_UserBlockingPriority=2;exports.unstable_cancelCallback=function(a){a.callback=null};exports.unstable_continueExecution=function(){T||S||(T=!0,f(X))};\nexports.unstable_getCurrentPriorityLevel=function(){return R};exports.unstable_getFirstCallbackNode=function(){return L(N)};exports.unstable_next=function(a){switch(R){case 1:case 2:case 3:var b=3;break;default:b=R}var c=R;R=b;try{return a()}finally{R=c}};exports.unstable_pauseExecution=function(){};exports.unstable_requestPaint=Z;exports.unstable_runWithPriority=function(a,b){switch(a){case 1:case 2:case 3:case 4:case 5:break;default:a=3}var c=R;R=a;try{return b()}finally{R=c}};\nexports.unstable_scheduleCallback=function(a,b,c){var d=exports.unstable_now();if(\"object\"===typeof c&&null!==c){var e=c.delay;e=\"number\"===typeof e&&0d?(a.sortIndex=e,J(O,a),null===L(N)&&a===L(O)&&(U?h():U=!0,g(W,e-d))):(a.sortIndex=c,J(N,a),T||S||(T=!0,f(X)));return a};\nexports.unstable_shouldYield=function(){var a=exports.unstable_now();V(a);var b=L(N);return b!==Q&&null!==Q&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTimeb}return!1}function v(a,b,c,d,e,f){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f}var C={};\n\"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style\".split(\" \").forEach(function(a){C[a]=new v(a,0,!1,a,null,!1)});[[\"acceptCharset\",\"accept-charset\"],[\"className\",\"class\"],[\"htmlFor\",\"for\"],[\"httpEquiv\",\"http-equiv\"]].forEach(function(a){var b=a[0];C[b]=new v(b,1,!1,a[1],null,!1)});[\"contentEditable\",\"draggable\",\"spellCheck\",\"value\"].forEach(function(a){C[a]=new v(a,2,!1,a.toLowerCase(),null,!1)});\n[\"autoReverse\",\"externalResourcesRequired\",\"focusable\",\"preserveAlpha\"].forEach(function(a){C[a]=new v(a,2,!1,a,null,!1)});\"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope\".split(\" \").forEach(function(a){C[a]=new v(a,3,!1,a.toLowerCase(),null,!1)});\n[\"checked\",\"multiple\",\"muted\",\"selected\"].forEach(function(a){C[a]=new v(a,3,!0,a,null,!1)});[\"capture\",\"download\"].forEach(function(a){C[a]=new v(a,4,!1,a,null,!1)});[\"cols\",\"rows\",\"size\",\"span\"].forEach(function(a){C[a]=new v(a,6,!1,a,null,!1)});[\"rowSpan\",\"start\"].forEach(function(a){C[a]=new v(a,5,!1,a.toLowerCase(),null,!1)});var Ua=/[\\-:]([a-z])/g;function Va(a){return a[1].toUpperCase()}\n\"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height\".split(\" \").forEach(function(a){var b=a.replace(Ua,\nVa);C[b]=new v(b,1,!1,a,null,!1)});\"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type\".split(\" \").forEach(function(a){var b=a.replace(Ua,Va);C[b]=new v(b,1,!1,a,\"http://www.w3.org/1999/xlink\",!1)});[\"xml:base\",\"xml:lang\",\"xml:space\"].forEach(function(a){var b=a.replace(Ua,Va);C[b]=new v(b,1,!1,a,\"http://www.w3.org/XML/1998/namespace\",!1)});[\"tabIndex\",\"crossOrigin\"].forEach(function(a){C[a]=new v(a,1,!1,a.toLowerCase(),null,!1)});\nC.xlinkHref=new v(\"xlinkHref\",1,!1,\"xlink:href\",\"http://www.w3.org/1999/xlink\",!0);[\"src\",\"href\",\"action\",\"formAction\"].forEach(function(a){C[a]=new v(a,1,!1,a.toLowerCase(),null,!0)});var Wa=aa.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;Wa.hasOwnProperty(\"ReactCurrentDispatcher\")||(Wa.ReactCurrentDispatcher={current:null});Wa.hasOwnProperty(\"ReactCurrentBatchConfig\")||(Wa.ReactCurrentBatchConfig={suspense:null});\nfunction Xa(a,b,c,d){var e=C.hasOwnProperty(b)?C[b]:null;var f=null!==e?0===e.type:d?!1:!(2=c.length))throw Error(u(93));c=c[0]}b=c}null==b&&(b=\"\");c=b}a._wrapperState={initialValue:rb(c)}}\nfunction Kb(a,b){var c=rb(b.value),d=rb(b.defaultValue);null!=c&&(c=\"\"+c,c!==a.value&&(a.value=c),null==b.defaultValue&&a.defaultValue!==c&&(a.defaultValue=c));null!=d&&(a.defaultValue=\"\"+d)}function Lb(a){var b=a.textContent;b===a._wrapperState.initialValue&&\"\"!==b&&null!==b&&(a.value=b)}var Mb={html:\"http://www.w3.org/1999/xhtml\",mathml:\"http://www.w3.org/1998/Math/MathML\",svg:\"http://www.w3.org/2000/svg\"};\nfunction Nb(a){switch(a){case \"svg\":return\"http://www.w3.org/2000/svg\";case \"math\":return\"http://www.w3.org/1998/Math/MathML\";default:return\"http://www.w3.org/1999/xhtml\"}}function Ob(a,b){return null==a||\"http://www.w3.org/1999/xhtml\"===a?Nb(b):\"http://www.w3.org/2000/svg\"===a&&\"foreignObject\"===b?\"http://www.w3.org/1999/xhtml\":a}\nvar Pb,Qb=function(a){return\"undefined\"!==typeof MSApp&&MSApp.execUnsafeLocalFunction?function(b,c,d,e){MSApp.execUnsafeLocalFunction(function(){return a(b,c,d,e)})}:a}(function(a,b){if(a.namespaceURI!==Mb.svg||\"innerHTML\"in a)a.innerHTML=b;else{Pb=Pb||document.createElement(\"div\");Pb.innerHTML=\"\"+b.valueOf().toString()+\"\";for(b=Pb.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}});\nfunction Rb(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b}function Sb(a,b){var c={};c[a.toLowerCase()]=b.toLowerCase();c[\"Webkit\"+a]=\"webkit\"+b;c[\"Moz\"+a]=\"moz\"+b;return c}var Tb={animationend:Sb(\"Animation\",\"AnimationEnd\"),animationiteration:Sb(\"Animation\",\"AnimationIteration\"),animationstart:Sb(\"Animation\",\"AnimationStart\"),transitionend:Sb(\"Transition\",\"TransitionEnd\")},Ub={},Vb={};\nya&&(Vb=document.createElement(\"div\").style,\"AnimationEvent\"in window||(delete Tb.animationend.animation,delete Tb.animationiteration.animation,delete Tb.animationstart.animation),\"TransitionEvent\"in window||delete Tb.transitionend.transition);function Wb(a){if(Ub[a])return Ub[a];if(!Tb[a])return a;var b=Tb[a],c;for(c in b)if(b.hasOwnProperty(c)&&c in Vb)return Ub[a]=b[c];return a}\nvar Xb=Wb(\"animationend\"),Yb=Wb(\"animationiteration\"),Zb=Wb(\"animationstart\"),$b=Wb(\"transitionend\"),ac=\"abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting\".split(\" \"),bc=new (\"function\"===typeof WeakMap?WeakMap:Map);function cc(a){var b=bc.get(a);void 0===b&&(b=new Map,bc.set(a,b));return b}\nfunction dc(a){var b=a,c=a;if(a.alternate)for(;b.return;)b=b.return;else{a=b;do b=a,0!==(b.effectTag&1026)&&(c=b.return),a=b.return;while(a)}return 3===b.tag?c:null}function ec(a){if(13===a.tag){var b=a.memoizedState;null===b&&(a=a.alternate,null!==a&&(b=a.memoizedState));if(null!==b)return b.dehydrated}return null}function fc(a){if(dc(a)!==a)throw Error(u(188));}\nfunction gc(a){var b=a.alternate;if(!b){b=dc(a);if(null===b)throw Error(u(188));return b!==a?null:a}for(var c=a,d=b;;){var e=c.return;if(null===e)break;var f=e.alternate;if(null===f){d=e.return;if(null!==d){c=d;continue}break}if(e.child===f.child){for(f=e.child;f;){if(f===c)return fc(e),a;if(f===d)return fc(e),b;f=f.sibling}throw Error(u(188));}if(c.return!==d.return)c=e,d=f;else{for(var g=!1,h=e.child;h;){if(h===c){g=!0;c=e;d=f;break}if(h===d){g=!0;d=e;c=f;break}h=h.sibling}if(!g){for(h=f.child;h;){if(h===\nc){g=!0;c=f;d=e;break}if(h===d){g=!0;d=f;c=e;break}h=h.sibling}if(!g)throw Error(u(189));}}if(c.alternate!==d)throw Error(u(190));}if(3!==c.tag)throw Error(u(188));return c.stateNode.current===c?a:b}function hc(a){a=gc(a);if(!a)return null;for(var b=a;;){if(5===b.tag||6===b.tag)return b;if(b.child)b.child.return=b,b=b.child;else{if(b===a)break;for(;!b.sibling;){if(!b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}}return null}\nfunction ic(a,b){if(null==b)throw Error(u(30));if(null==a)return b;if(Array.isArray(a)){if(Array.isArray(b))return a.push.apply(a,b),a;a.push(b);return a}return Array.isArray(b)?[a].concat(b):[a,b]}function jc(a,b,c){Array.isArray(a)?a.forEach(b,c):a&&b.call(c,a)}var kc=null;\nfunction lc(a){if(a){var b=a._dispatchListeners,c=a._dispatchInstances;if(Array.isArray(b))for(var d=0;dpc.length&&pc.push(a)}\nfunction rc(a,b,c,d){if(pc.length){var e=pc.pop();e.topLevelType=a;e.eventSystemFlags=d;e.nativeEvent=b;e.targetInst=c;return e}return{topLevelType:a,eventSystemFlags:d,nativeEvent:b,targetInst:c,ancestors:[]}}\nfunction sc(a){var b=a.targetInst,c=b;do{if(!c){a.ancestors.push(c);break}var d=c;if(3===d.tag)d=d.stateNode.containerInfo;else{for(;d.return;)d=d.return;d=3!==d.tag?null:d.stateNode.containerInfo}if(!d)break;b=c.tag;5!==b&&6!==b||a.ancestors.push(c);c=tc(d)}while(c);for(c=0;c=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=ud(c)}}\nfunction wd(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?wd(a,b.parentNode):\"contains\"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1}function xd(){for(var a=window,b=td();b instanceof a.HTMLIFrameElement;){try{var c=\"string\"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=td(a.document)}return b}\nfunction yd(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&(\"input\"===b&&(\"text\"===a.type||\"search\"===a.type||\"tel\"===a.type||\"url\"===a.type||\"password\"===a.type)||\"textarea\"===b||\"true\"===a.contentEditable)}var zd=\"$\",Ad=\"/$\",Bd=\"$?\",Cd=\"$!\",Dd=null,Ed=null;function Fd(a,b){switch(a){case \"button\":case \"input\":case \"select\":case \"textarea\":return!!b.autoFocus}return!1}\nfunction Gd(a,b){return\"textarea\"===a||\"option\"===a||\"noscript\"===a||\"string\"===typeof b.children||\"number\"===typeof b.children||\"object\"===typeof b.dangerouslySetInnerHTML&&null!==b.dangerouslySetInnerHTML&&null!=b.dangerouslySetInnerHTML.__html}var Hd=\"function\"===typeof setTimeout?setTimeout:void 0,Id=\"function\"===typeof clearTimeout?clearTimeout:void 0;function Jd(a){for(;null!=a;a=a.nextSibling){var b=a.nodeType;if(1===b||3===b)break}return a}\nfunction Kd(a){a=a.previousSibling;for(var b=0;a;){if(8===a.nodeType){var c=a.data;if(c===zd||c===Cd||c===Bd){if(0===b)return a;b--}else c===Ad&&b++}a=a.previousSibling}return null}var Ld=Math.random().toString(36).slice(2),Md=\"__reactInternalInstance$\"+Ld,Nd=\"__reactEventHandlers$\"+Ld,Od=\"__reactContainere$\"+Ld;\nfunction tc(a){var b=a[Md];if(b)return b;for(var c=a.parentNode;c;){if(b=c[Od]||c[Md]){c=b.alternate;if(null!==b.child||null!==c&&null!==c.child)for(a=Kd(a);null!==a;){if(c=a[Md])return c;a=Kd(a)}return b}a=c;c=a.parentNode}return null}function Nc(a){a=a[Md]||a[Od];return!a||5!==a.tag&&6!==a.tag&&13!==a.tag&&3!==a.tag?null:a}function Pd(a){if(5===a.tag||6===a.tag)return a.stateNode;throw Error(u(33));}function Qd(a){return a[Nd]||null}\nfunction Rd(a){do a=a.return;while(a&&5!==a.tag);return a?a:null}\nfunction Sd(a,b){var c=a.stateNode;if(!c)return null;var d=la(c);if(!d)return null;c=d[b];a:switch(b){case \"onClick\":case \"onClickCapture\":case \"onDoubleClick\":case \"onDoubleClickCapture\":case \"onMouseDown\":case \"onMouseDownCapture\":case \"onMouseMove\":case \"onMouseMoveCapture\":case \"onMouseUp\":case \"onMouseUpCapture\":case \"onMouseEnter\":(d=!d.disabled)||(a=a.type,d=!(\"button\"===a||\"input\"===a||\"select\"===a||\"textarea\"===a));a=!d;break a;default:a=!1}if(a)return null;if(c&&\"function\"!==typeof c)throw Error(u(231,\nb,typeof c));return c}function Td(a,b,c){if(b=Sd(a,c.dispatchConfig.phasedRegistrationNames[b]))c._dispatchListeners=ic(c._dispatchListeners,b),c._dispatchInstances=ic(c._dispatchInstances,a)}function Ud(a){if(a&&a.dispatchConfig.phasedRegistrationNames){for(var b=a._targetInst,c=[];b;)c.push(b),b=Rd(b);for(b=c.length;0this.eventPool.length&&this.eventPool.push(a)}function de(a){a.eventPool=[];a.getPooled=ee;a.release=fe}var ge=G.extend({data:null}),he=G.extend({data:null}),ie=[9,13,27,32],je=ya&&\"CompositionEvent\"in window,ke=null;ya&&\"documentMode\"in document&&(ke=document.documentMode);\nvar le=ya&&\"TextEvent\"in window&&!ke,me=ya&&(!je||ke&&8=ke),ne=String.fromCharCode(32),oe={beforeInput:{phasedRegistrationNames:{bubbled:\"onBeforeInput\",captured:\"onBeforeInputCapture\"},dependencies:[\"compositionend\",\"keypress\",\"textInput\",\"paste\"]},compositionEnd:{phasedRegistrationNames:{bubbled:\"onCompositionEnd\",captured:\"onCompositionEndCapture\"},dependencies:\"blur compositionend keydown keypress keyup mousedown\".split(\" \")},compositionStart:{phasedRegistrationNames:{bubbled:\"onCompositionStart\",\ncaptured:\"onCompositionStartCapture\"},dependencies:\"blur compositionstart keydown keypress keyup mousedown\".split(\" \")},compositionUpdate:{phasedRegistrationNames:{bubbled:\"onCompositionUpdate\",captured:\"onCompositionUpdateCapture\"},dependencies:\"blur compositionupdate keydown keypress keyup mousedown\".split(\" \")}},pe=!1;\nfunction qe(a,b){switch(a){case \"keyup\":return-1!==ie.indexOf(b.keyCode);case \"keydown\":return 229!==b.keyCode;case \"keypress\":case \"mousedown\":case \"blur\":return!0;default:return!1}}function re(a){a=a.detail;return\"object\"===typeof a&&\"data\"in a?a.data:null}var se=!1;function te(a,b){switch(a){case \"compositionend\":return re(b);case \"keypress\":if(32!==b.which)return null;pe=!0;return ne;case \"textInput\":return a=b.data,a===ne&&pe?null:a;default:return null}}\nfunction ue(a,b){if(se)return\"compositionend\"===a||!je&&qe(a,b)?(a=ae(),$d=Zd=Yd=null,se=!1,a):null;switch(a){case \"paste\":return null;case \"keypress\":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1=document.documentMode,df={select:{phasedRegistrationNames:{bubbled:\"onSelect\",captured:\"onSelectCapture\"},dependencies:\"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange\".split(\" \")}},ef=null,ff=null,gf=null,hf=!1;\nfunction jf(a,b){var c=b.window===b?b.document:9===b.nodeType?b:b.ownerDocument;if(hf||null==ef||ef!==td(c))return null;c=ef;\"selectionStart\"in c&&yd(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset});return gf&&bf(gf,c)?null:(gf=c,a=G.getPooled(df.select,ff,a,b),a.type=\"select\",a.target=ef,Xd(a),a)}\nvar kf={eventTypes:df,extractEvents:function(a,b,c,d,e,f){e=f||(d.window===d?d.document:9===d.nodeType?d:d.ownerDocument);if(!(f=!e)){a:{e=cc(e);f=wa.onSelect;for(var g=0;gzf||(a.current=yf[zf],yf[zf]=null,zf--)}\nfunction I(a,b){zf++;yf[zf]=a.current;a.current=b}var Af={},J={current:Af},K={current:!1},Bf=Af;function Cf(a,b){var c=a.type.contextTypes;if(!c)return Af;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e}function L(a){a=a.childContextTypes;return null!==a&&void 0!==a}\nfunction Df(){H(K);H(J)}function Ef(a,b,c){if(J.current!==Af)throw Error(u(168));I(J,b);I(K,c)}function Ff(a,b,c){var d=a.stateNode;a=b.childContextTypes;if(\"function\"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in a))throw Error(u(108,pb(b)||\"Unknown\",e));return n({},c,{},d)}function Gf(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Af;Bf=J.current;I(J,a);I(K,K.current);return!0}\nfunction Hf(a,b,c){var d=a.stateNode;if(!d)throw Error(u(169));c?(a=Ff(a,b,Bf),d.__reactInternalMemoizedMergedChildContext=a,H(K),H(J),I(J,a)):H(K);I(K,c)}\nvar If=r.unstable_runWithPriority,Jf=r.unstable_scheduleCallback,Kf=r.unstable_cancelCallback,Lf=r.unstable_requestPaint,Mf=r.unstable_now,Nf=r.unstable_getCurrentPriorityLevel,Of=r.unstable_ImmediatePriority,Pf=r.unstable_UserBlockingPriority,Qf=r.unstable_NormalPriority,Rf=r.unstable_LowPriority,Sf=r.unstable_IdlePriority,Tf={},Uf=r.unstable_shouldYield,Vf=void 0!==Lf?Lf:function(){},Wf=null,Xf=null,Yf=!1,Zf=Mf(),$f=1E4>Zf?Mf:function(){return Mf()-Zf};\nfunction ag(){switch(Nf()){case Of:return 99;case Pf:return 98;case Qf:return 97;case Rf:return 96;case Sf:return 95;default:throw Error(u(332));}}function bg(a){switch(a){case 99:return Of;case 98:return Pf;case 97:return Qf;case 96:return Rf;case 95:return Sf;default:throw Error(u(332));}}function cg(a,b){a=bg(a);return If(a,b)}function dg(a,b,c){a=bg(a);return Jf(a,b,c)}function eg(a){null===Wf?(Wf=[a],Xf=Jf(Of,fg)):Wf.push(a);return Tf}function gg(){if(null!==Xf){var a=Xf;Xf=null;Kf(a)}fg()}\nfunction fg(){if(!Yf&&null!==Wf){Yf=!0;var a=0;try{var b=Wf;cg(99,function(){for(;a=b&&(rg=!0),a.firstContext=null)}\nfunction sg(a,b){if(mg!==a&&!1!==b&&0!==b){if(\"number\"!==typeof b||1073741823===b)mg=a,b=1073741823;b={context:a,observedBits:b,next:null};if(null===lg){if(null===kg)throw Error(u(308));lg=b;kg.dependencies={expirationTime:0,firstContext:b,responders:null}}else lg=lg.next=b}return a._currentValue}var tg=!1;function ug(a){a.updateQueue={baseState:a.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}\nfunction vg(a,b){a=a.updateQueue;b.updateQueue===a&&(b.updateQueue={baseState:a.baseState,baseQueue:a.baseQueue,shared:a.shared,effects:a.effects})}function wg(a,b){a={expirationTime:a,suspenseConfig:b,tag:0,payload:null,callback:null,next:null};return a.next=a}function xg(a,b){a=a.updateQueue;if(null!==a){a=a.shared;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}}\nfunction yg(a,b){var c=a.alternate;null!==c&&vg(c,a);a=a.updateQueue;c=a.baseQueue;null===c?(a.baseQueue=b.next=b,b.next=b):(b.next=c.next,c.next=b)}\nfunction zg(a,b,c,d){var e=a.updateQueue;tg=!1;var f=e.baseQueue,g=e.shared.pending;if(null!==g){if(null!==f){var h=f.next;f.next=g.next;g.next=h}f=g;e.shared.pending=null;h=a.alternate;null!==h&&(h=h.updateQueue,null!==h&&(h.baseQueue=g))}if(null!==f){h=f.next;var k=e.baseState,l=0,m=null,p=null,x=null;if(null!==h){var z=h;do{g=z.expirationTime;if(gl&&(l=g)}else{null!==x&&(x=x.next={expirationTime:1073741823,suspenseConfig:z.suspenseConfig,tag:z.tag,payload:z.payload,callback:z.callback,next:null});Ag(g,z.suspenseConfig);a:{var D=a,t=z;g=b;ca=c;switch(t.tag){case 1:D=t.payload;if(\"function\"===typeof D){k=D.call(ca,k,g);break a}k=D;break a;case 3:D.effectTag=D.effectTag&-4097|64;case 0:D=t.payload;g=\"function\"===typeof D?D.call(ca,k,g):D;if(null===g||void 0===g)break a;k=n({},k,g);break a;case 2:tg=!0}}null!==z.callback&&\n(a.effectTag|=32,g=e.effects,null===g?e.effects=[z]:g.push(z))}z=z.next;if(null===z||z===h)if(g=e.shared.pending,null===g)break;else z=f.next=g.next,g.next=h,e.baseQueue=f=g,e.shared.pending=null}while(1)}null===x?m=k:x.next=p;e.baseState=m;e.baseQueue=x;Bg(l);a.expirationTime=l;a.memoizedState=k}}\nfunction Cg(a,b,c){a=b.effects;b.effects=null;if(null!==a)for(b=0;by?(A=m,m=null):A=m.sibling;var q=x(e,m,h[y],k);if(null===q){null===m&&(m=A);break}a&&\nm&&null===q.alternate&&b(e,m);g=f(q,g,y);null===t?l=q:t.sibling=q;t=q;m=A}if(y===h.length)return c(e,m),l;if(null===m){for(;yy?(A=t,t=null):A=t.sibling;var D=x(e,t,q.value,l);if(null===D){null===t&&(t=A);break}a&&t&&null===D.alternate&&b(e,t);g=f(D,g,y);null===m?k=D:m.sibling=D;m=D;t=A}if(q.done)return c(e,t),k;if(null===t){for(;!q.done;y++,q=h.next())q=p(e,q.value,l),null!==q&&(g=f(q,g,y),null===m?k=q:m.sibling=q,m=q);return k}for(t=d(e,t);!q.done;y++,q=h.next())q=z(t,e,y,q.value,l),null!==q&&(a&&null!==\nq.alternate&&t.delete(null===q.key?y:q.key),g=f(q,g,y),null===m?k=q:m.sibling=q,m=q);a&&t.forEach(function(a){return b(e,a)});return k}return function(a,d,f,h){var k=\"object\"===typeof f&&null!==f&&f.type===ab&&null===f.key;k&&(f=f.props.children);var l=\"object\"===typeof f&&null!==f;if(l)switch(f.$$typeof){case Za:a:{l=f.key;for(k=d;null!==k;){if(k.key===l){switch(k.tag){case 7:if(f.type===ab){c(a,k.sibling);d=e(k,f.props.children);d.return=a;a=d;break a}break;default:if(k.elementType===f.type){c(a,\nk.sibling);d=e(k,f.props);d.ref=Pg(a,k,f);d.return=a;a=d;break a}}c(a,k);break}else b(a,k);k=k.sibling}f.type===ab?(d=Wg(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=Ug(f.type,f.key,f.props,null,a.mode,h),h.ref=Pg(a,d,f),h.return=a,a=h)}return g(a);case $a:a:{for(k=f.key;null!==d;){if(d.key===k)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=\nd.sibling}d=Vg(f,a.mode,h);d.return=a;a=d}return g(a)}if(\"string\"===typeof f||\"number\"===typeof f)return f=\"\"+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d):(c(a,d),d=Tg(f,a.mode,h),d.return=a,a=d),g(a);if(Og(f))return ca(a,d,f,h);if(nb(f))return D(a,d,f,h);l&&Qg(a,f);if(\"undefined\"===typeof f&&!k)switch(a.tag){case 1:case 0:throw a=a.type,Error(u(152,a.displayName||a.name||\"Component\"));}return c(a,d)}}var Xg=Rg(!0),Yg=Rg(!1),Zg={},$g={current:Zg},ah={current:Zg},bh={current:Zg};\nfunction ch(a){if(a===Zg)throw Error(u(174));return a}function dh(a,b){I(bh,b);I(ah,a);I($g,Zg);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:Ob(null,\"\");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=Ob(b,a)}H($g);I($g,b)}function eh(){H($g);H(ah);H(bh)}function fh(a){ch(bh.current);var b=ch($g.current);var c=Ob(b,a.type);b!==c&&(I(ah,a),I($g,c))}function gh(a){ah.current===a&&(H($g),H(ah))}var M={current:0};\nfunction hh(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||c.data===Bd||c.data===Cd))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.effectTag&64))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}function ih(a,b){return{responder:a,props:b}}\nvar jh=Wa.ReactCurrentDispatcher,kh=Wa.ReactCurrentBatchConfig,lh=0,N=null,O=null,P=null,mh=!1;function Q(){throw Error(u(321));}function nh(a,b){if(null===b)return!1;for(var c=0;cf))throw Error(u(301));f+=1;P=O=null;b.updateQueue=null;jh.current=rh;a=c(d,e)}while(b.expirationTime===lh)}jh.current=sh;b=null!==O&&null!==O.next;lh=0;P=O=N=null;mh=!1;if(b)throw Error(u(300));return a}\nfunction th(){var a={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};null===P?N.memoizedState=P=a:P=P.next=a;return P}function uh(){if(null===O){var a=N.alternate;a=null!==a?a.memoizedState:null}else a=O.next;var b=null===P?N.memoizedState:P.next;if(null!==b)P=b,O=a;else{if(null===a)throw Error(u(310));O=a;a={memoizedState:O.memoizedState,baseState:O.baseState,baseQueue:O.baseQueue,queue:O.queue,next:null};null===P?N.memoizedState=P=a:P=P.next=a}return P}\nfunction vh(a,b){return\"function\"===typeof b?b(a):b}\nfunction wh(a){var b=uh(),c=b.queue;if(null===c)throw Error(u(311));c.lastRenderedReducer=a;var d=O,e=d.baseQueue,f=c.pending;if(null!==f){if(null!==e){var g=e.next;e.next=f.next;f.next=g}d.baseQueue=e=f;c.pending=null}if(null!==e){e=e.next;d=d.baseState;var h=g=f=null,k=e;do{var l=k.expirationTime;if(lN.expirationTime&&\n(N.expirationTime=l,Bg(l))}else null!==h&&(h=h.next={expirationTime:1073741823,suspenseConfig:k.suspenseConfig,action:k.action,eagerReducer:k.eagerReducer,eagerState:k.eagerState,next:null}),Ag(l,k.suspenseConfig),d=k.eagerReducer===a?k.eagerState:a(d,k.action);k=k.next}while(null!==k&&k!==e);null===h?f=d:h.next=g;$e(d,b.memoizedState)||(rg=!0);b.memoizedState=d;b.baseState=f;b.baseQueue=h;c.lastRenderedState=d}return[b.memoizedState,c.dispatch]}\nfunction xh(a){var b=uh(),c=b.queue;if(null===c)throw Error(u(311));c.lastRenderedReducer=a;var d=c.dispatch,e=c.pending,f=b.memoizedState;if(null!==e){c.pending=null;var g=e=e.next;do f=a(f,g.action),g=g.next;while(g!==e);$e(f,b.memoizedState)||(rg=!0);b.memoizedState=f;null===b.baseQueue&&(b.baseState=f);c.lastRenderedState=f}return[f,d]}\nfunction yh(a){var b=th();\"function\"===typeof a&&(a=a());b.memoizedState=b.baseState=a;a=b.queue={pending:null,dispatch:null,lastRenderedReducer:vh,lastRenderedState:a};a=a.dispatch=zh.bind(null,N,a);return[b.memoizedState,a]}function Ah(a,b,c,d){a={tag:a,create:b,destroy:c,deps:d,next:null};b=N.updateQueue;null===b?(b={lastEffect:null},N.updateQueue=b,b.lastEffect=a.next=a):(c=b.lastEffect,null===c?b.lastEffect=a.next=a:(d=c.next,c.next=a,a.next=d,b.lastEffect=a));return a}\nfunction Bh(){return uh().memoizedState}function Ch(a,b,c,d){var e=th();N.effectTag|=a;e.memoizedState=Ah(1|b,c,void 0,void 0===d?null:d)}function Dh(a,b,c,d){var e=uh();d=void 0===d?null:d;var f=void 0;if(null!==O){var g=O.memoizedState;f=g.destroy;if(null!==d&&nh(d,g.deps)){Ah(b,c,f,d);return}}N.effectTag|=a;e.memoizedState=Ah(1|b,c,f,d)}function Eh(a,b){return Ch(516,4,a,b)}function Fh(a,b){return Dh(516,4,a,b)}function Gh(a,b){return Dh(4,2,a,b)}\nfunction Hh(a,b){if(\"function\"===typeof b)return a=a(),b(a),function(){b(null)};if(null!==b&&void 0!==b)return a=a(),b.current=a,function(){b.current=null}}function Ih(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return Dh(4,2,Hh.bind(null,b,a),c)}function Jh(){}function Kh(a,b){th().memoizedState=[a,void 0===b?null:b];return a}function Lh(a,b){var c=uh();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&nh(b,d[1]))return d[0];c.memoizedState=[a,b];return a}\nfunction Mh(a,b){var c=uh();b=void 0===b?null:b;var d=c.memoizedState;if(null!==d&&null!==b&&nh(b,d[1]))return d[0];a=a();c.memoizedState=[a,b];return a}function Nh(a,b,c){var d=ag();cg(98>d?98:d,function(){a(!0)});cg(97\\x3c/script>\",a=a.removeChild(a.firstChild)):\"string\"===typeof d.is?a=g.createElement(e,{is:d.is}):(a=g.createElement(e),\"select\"===e&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,e);a[Md]=b;a[Nd]=d;ni(a,b,!1,!1);b.stateNode=a;g=pd(e,d);switch(e){case \"iframe\":case \"object\":case \"embed\":F(\"load\",\na);h=d;break;case \"video\":case \"audio\":for(h=0;hd.tailExpiration&&1b)&&tj.set(a,b)))}}\nfunction xj(a,b){a.expirationTimea?c:a;return 2>=a&&b!==a?0:a}\nfunction Z(a){if(0!==a.lastExpiredTime)a.callbackExpirationTime=1073741823,a.callbackPriority=99,a.callbackNode=eg(yj.bind(null,a));else{var b=zj(a),c=a.callbackNode;if(0===b)null!==c&&(a.callbackNode=null,a.callbackExpirationTime=0,a.callbackPriority=90);else{var d=Gg();1073741823===b?d=99:1===b||2===b?d=95:(d=10*(1073741821-b)-10*(1073741821-d),d=0>=d?99:250>=d?98:5250>=d?97:95);if(null!==c){var e=a.callbackPriority;if(a.callbackExpirationTime===b&&e>=d)return;c!==Tf&&Kf(c)}a.callbackExpirationTime=\nb;a.callbackPriority=d;b=1073741823===b?eg(yj.bind(null,a)):dg(d,Bj.bind(null,a),{timeout:10*(1073741821-b)-$f()});a.callbackNode=b}}}\nfunction Bj(a,b){wj=0;if(b)return b=Gg(),Cj(a,b),Z(a),null;var c=zj(a);if(0!==c){b=a.callbackNode;if((W&(fj|gj))!==V)throw Error(u(327));Dj();a===T&&c===U||Ej(a,c);if(null!==X){var d=W;W|=fj;var e=Fj();do try{Gj();break}catch(h){Hj(a,h)}while(1);ng();W=d;cj.current=e;if(S===hj)throw b=kj,Ej(a,c),xi(a,c),Z(a),b;if(null===X)switch(e=a.finishedWork=a.current.alternate,a.finishedExpirationTime=c,d=S,T=null,d){case ti:case hj:throw Error(u(345));case ij:Cj(a,2=c){a.lastPingedTime=c;Ej(a,c);break}}f=zj(a);if(0!==f&&f!==c)break;if(0!==d&&d!==c){a.lastPingedTime=d;break}a.timeoutHandle=Hd(Jj.bind(null,a),e);break}Jj(a);break;case vi:xi(a,c);d=a.lastSuspendedTime;c===d&&(a.nextKnownPendingLevel=Ij(e));if(oj&&(e=a.lastPingedTime,0===e||e>=c)){a.lastPingedTime=c;Ej(a,c);break}e=zj(a);if(0!==e&&e!==c)break;if(0!==d&&d!==c){a.lastPingedTime=\nd;break}1073741823!==mj?d=10*(1073741821-mj)-$f():1073741823===lj?d=0:(d=10*(1073741821-lj)-5E3,e=$f(),c=10*(1073741821-c)-e,d=e-d,0>d&&(d=0),d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*bj(d/1960))-d,c=d?d=0:(e=g.busyDelayMs|0,f=$f()-(10*(1073741821-f)-(g.timeoutMs|0||5E3)),d=f<=e?0:e+d-f);if(10 component higher in the tree to provide a loading indicator or placeholder to display.\"+qb(g))}S!==\njj&&(S=ij);h=Ai(h,g);p=f;do{switch(p.tag){case 3:k=h;p.effectTag|=4096;p.expirationTime=b;var B=Xi(p,k,b);yg(p,B);break a;case 1:k=h;var w=p.type,ub=p.stateNode;if(0===(p.effectTag&64)&&(\"function\"===typeof w.getDerivedStateFromError||null!==ub&&\"function\"===typeof ub.componentDidCatch&&(null===aj||!aj.has(ub)))){p.effectTag|=4096;p.expirationTime=b;var vb=$i(p,k,b);yg(p,vb);break a}}p=p.return}while(null!==p)}X=Pj(X)}catch(Xc){b=Xc;continue}break}while(1)}\nfunction Fj(){var a=cj.current;cj.current=sh;return null===a?sh:a}function Ag(a,b){awi&&(wi=a)}function Kj(){for(;null!==X;)X=Qj(X)}function Gj(){for(;null!==X&&!Uf();)X=Qj(X)}function Qj(a){var b=Rj(a.alternate,a,U);a.memoizedProps=a.pendingProps;null===b&&(b=Pj(a));dj.current=null;return b}\nfunction Pj(a){X=a;do{var b=X.alternate;a=X.return;if(0===(X.effectTag&2048)){b=si(b,X,U);if(1===U||1!==X.childExpirationTime){for(var c=0,d=X.child;null!==d;){var e=d.expirationTime,f=d.childExpirationTime;e>c&&(c=e);f>c&&(c=f);d=d.sibling}X.childExpirationTime=c}if(null!==b)return b;null!==a&&0===(a.effectTag&2048)&&(null===a.firstEffect&&(a.firstEffect=X.firstEffect),null!==X.lastEffect&&(null!==a.lastEffect&&(a.lastEffect.nextEffect=X.firstEffect),a.lastEffect=X.lastEffect),1a?b:a}function Jj(a){var b=ag();cg(99,Sj.bind(null,a,b));return null}\nfunction Sj(a,b){do Dj();while(null!==rj);if((W&(fj|gj))!==V)throw Error(u(327));var c=a.finishedWork,d=a.finishedExpirationTime;if(null===c)return null;a.finishedWork=null;a.finishedExpirationTime=0;if(c===a.current)throw Error(u(177));a.callbackNode=null;a.callbackExpirationTime=0;a.callbackPriority=90;a.nextKnownPendingLevel=0;var e=Ij(c);a.firstPendingTime=e;d<=a.lastSuspendedTime?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:d<=a.firstSuspendedTime&&(a.firstSuspendedTime=\nd-1);d<=a.lastPingedTime&&(a.lastPingedTime=0);d<=a.lastExpiredTime&&(a.lastExpiredTime=0);a===T&&(X=T=null,U=0);1h&&(l=h,h=g,g=l),l=vd(q,g),m=vd(q,h),l&&m&&(1!==w.rangeCount||w.anchorNode!==l.node||w.anchorOffset!==l.offset||w.focusNode!==m.node||w.focusOffset!==m.offset)&&(B=B.createRange(),B.setStart(l.node,l.offset),w.removeAllRanges(),g>h?(w.addRange(B),w.extend(m.node,m.offset)):(B.setEnd(m.node,m.offset),w.addRange(B))))));B=[];for(w=q;w=w.parentNode;)1===w.nodeType&&B.push({element:w,left:w.scrollLeft,\ntop:w.scrollTop});\"function\"===typeof q.focus&&q.focus();for(q=0;q=c)return ji(a,b,c);I(M,M.current&1);b=$h(a,b,c);return null!==b?b.sibling:null}I(M,M.current&1);break;case 19:d=b.childExpirationTime>=c;if(0!==(a.effectTag&64)){if(d)return mi(a,b,c);b.effectTag|=64}e=b.memoizedState;null!==e&&(e.rendering=null,e.tail=null);I(M,M.current);if(!d)return null}return $h(a,b,c)}rg=!1}}else rg=!1;b.expirationTime=0;switch(b.tag){case 2:d=b.type;null!==a&&(a.alternate=null,b.alternate=null,b.effectTag|=2);a=b.pendingProps;e=Cf(b,J.current);qg(b,c);e=oh(null,\nb,d,a,e,c);b.effectTag|=1;if(\"object\"===typeof e&&null!==e&&\"function\"===typeof e.render&&void 0===e.$$typeof){b.tag=1;b.memoizedState=null;b.updateQueue=null;if(L(d)){var f=!0;Gf(b)}else f=!1;b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null;ug(b);var g=d.getDerivedStateFromProps;\"function\"===typeof g&&Fg(b,d,g,a);e.updater=Jg;b.stateNode=e;e._reactInternalFiber=b;Ng(b,d,a,c);b=gi(null,b,d,!0,f,c)}else b.tag=0,R(null,b,e,c),b=b.child;return b;case 16:a:{e=b.elementType;null!==a&&(a.alternate=\nnull,b.alternate=null,b.effectTag|=2);a=b.pendingProps;ob(e);if(1!==e._status)throw e._result;e=e._result;b.type=e;f=b.tag=Xj(e);a=ig(e,a);switch(f){case 0:b=di(null,b,e,a,c);break a;case 1:b=fi(null,b,e,a,c);break a;case 11:b=Zh(null,b,e,a,c);break a;case 14:b=ai(null,b,e,ig(e.type,a),d,c);break a}throw Error(u(306,e,\"\"));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ig(d,e),di(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ig(d,e),fi(a,b,d,e,c);\ncase 3:hi(b);d=b.updateQueue;if(null===a||null===d)throw Error(u(282));d=b.pendingProps;e=b.memoizedState;e=null!==e?e.element:null;vg(a,b);zg(b,d,null,c);d=b.memoizedState.element;if(d===e)Xh(),b=$h(a,b,c);else{if(e=b.stateNode.hydrate)Ph=Jd(b.stateNode.containerInfo.firstChild),Oh=b,e=Qh=!0;if(e)for(c=Yg(b,null,d,c),b.child=c;c;)c.effectTag=c.effectTag&-3|1024,c=c.sibling;else R(a,b,d,c),Xh();b=b.child}return b;case 5:return fh(b),null===a&&Uh(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:\nnull,g=e.children,Gd(d,e)?g=null:null!==f&&Gd(d,f)&&(b.effectTag|=16),ei(a,b),b.mode&4&&1!==c&&e.hidden?(b.expirationTime=b.childExpirationTime=1,b=null):(R(a,b,g,c),b=b.child),b;case 6:return null===a&&Uh(b),null;case 13:return ji(a,b,c);case 4:return dh(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=Xg(b,null,d,c):R(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:ig(d,e),Zh(a,b,d,e,c);case 7:return R(a,b,b.pendingProps,c),b.child;case 8:return R(a,\nb,b.pendingProps.children,c),b.child;case 12:return R(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;g=b.memoizedProps;f=e.value;var h=b.type._context;I(jg,h._currentValue);h._currentValue=f;if(null!==g)if(h=g.value,f=$e(h,f)?0:(\"function\"===typeof d._calculateChangedBits?d._calculateChangedBits(h,f):1073741823)|0,0===f){if(g.children===e.children&&!K.current){b=$h(a,b,c);break a}}else for(h=b.child,null!==h&&(h.return=b);null!==h;){var k=h.dependencies;if(null!==\nk){g=h.child;for(var l=k.firstContext;null!==l;){if(l.context===d&&0!==(l.observedBits&f)){1===h.tag&&(l=wg(c,null),l.tag=2,xg(h,l));h.expirationTime=b&&a<=b}function xi(a,b){var c=a.firstSuspendedTime,d=a.lastSuspendedTime;cb||0===c)a.lastSuspendedTime=b;b<=a.lastPingedTime&&(a.lastPingedTime=0);b<=a.lastExpiredTime&&(a.lastExpiredTime=0)}\nfunction yi(a,b){b>a.firstPendingTime&&(a.firstPendingTime=b);var c=a.firstSuspendedTime;0!==c&&(b>=c?a.firstSuspendedTime=a.lastSuspendedTime=a.nextKnownPendingLevel=0:b>=a.lastSuspendedTime&&(a.lastSuspendedTime=b+1),b>a.nextKnownPendingLevel&&(a.nextKnownPendingLevel=b))}function Cj(a,b){var c=a.lastExpiredTime;if(0===c||c>b)a.lastExpiredTime=b}\nfunction bk(a,b,c,d){var e=b.current,f=Gg(),g=Dg.suspense;f=Hg(f,e,g);a:if(c){c=c._reactInternalFiber;b:{if(dc(c)!==c||1!==c.tag)throw Error(u(170));var h=c;do{switch(h.tag){case 3:h=h.stateNode.context;break b;case 1:if(L(h.type)){h=h.stateNode.__reactInternalMemoizedMergedChildContext;break b}}h=h.return}while(null!==h);throw Error(u(171));}if(1===c.tag){var k=c.type;if(L(k)){c=Ff(c,k,h);break a}}c=h}else c=Af;null===b.context?b.context=c:b.pendingContext=c;b=wg(f,g);b.payload={element:a};d=void 0===\nd?null:d;null!==d&&(b.callback=d);xg(e,b);Ig(e,f);return f}function ck(a){a=a.current;if(!a.child)return null;switch(a.child.tag){case 5:return a.child.stateNode;default:return a.child.stateNode}}function dk(a,b){a=a.memoizedState;null!==a&&null!==a.dehydrated&&a.retryTime [\"/\", \"test\", \"\\d+\", undefined, \"?\", undefined]\n // \"/route(\\\\d+)\" => [undefined, undefined, undefined, \"\\d+\", undefined, undefined]\n // \"/*\" => [\"/\", undefined, undefined, undefined, undefined, \"*\"]\n '([\\\\/.])?(?:(?:\\\\:(\\\\w+)(?:\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))?|\\\\(((?:\\\\\\\\.|[^\\\\\\\\()])+)\\\\))([+*?])?|(\\\\*))'\n].join('|'), 'g')\n\n/**\n * Parse a string for the raw tokens.\n *\n * @param {string} str\n * @param {Object=} options\n * @return {!Array}\n */\nfunction parse (str, options) {\n var tokens = []\n var key = 0\n var index = 0\n var path = ''\n var defaultDelimiter = options && options.delimiter || '/'\n var res\n\n while ((res = PATH_REGEXP.exec(str)) != null) {\n var m = res[0]\n var escaped = res[1]\n var offset = res.index\n path += str.slice(index, offset)\n index = offset + m.length\n\n // Ignore already escaped sequences.\n if (escaped) {\n path += escaped[1]\n continue\n }\n\n var next = str[index]\n var prefix = res[2]\n var name = res[3]\n var capture = res[4]\n var group = res[5]\n var modifier = res[6]\n var asterisk = res[7]\n\n // Push the current path onto the tokens.\n if (path) {\n tokens.push(path)\n path = ''\n }\n\n var partial = prefix != null && next != null && next !== prefix\n var repeat = modifier === '+' || modifier === '*'\n var optional = modifier === '?' || modifier === '*'\n var delimiter = res[2] || defaultDelimiter\n var pattern = capture || group\n\n tokens.push({\n name: name || key++,\n prefix: prefix || '',\n delimiter: delimiter,\n optional: optional,\n repeat: repeat,\n partial: partial,\n asterisk: !!asterisk,\n pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')\n })\n }\n\n // Match any characters still remaining.\n if (index < str.length) {\n path += str.substr(index)\n }\n\n // If the path exists, push it onto the end.\n if (path) {\n tokens.push(path)\n }\n\n return tokens\n}\n\n/**\n * Compile a string to a template function for the path.\n *\n * @param {string} str\n * @param {Object=} options\n * @return {!function(Object=, Object=)}\n */\nfunction compile (str, options) {\n return tokensToFunction(parse(str, options), options)\n}\n\n/**\n * Prettier encoding of URI path segments.\n *\n * @param {string}\n * @return {string}\n */\nfunction encodeURIComponentPretty (str) {\n return encodeURI(str).replace(/[\\/?#]/g, function (c) {\n return '%' + c.charCodeAt(0).toString(16).toUpperCase()\n })\n}\n\n/**\n * Encode the asterisk parameter. Similar to `pretty`, but allows slashes.\n *\n * @param {string}\n * @return {string}\n */\nfunction encodeAsterisk (str) {\n return encodeURI(str).replace(/[?#]/g, function (c) {\n return '%' + c.charCodeAt(0).toString(16).toUpperCase()\n })\n}\n\n/**\n * Expose a method for transforming tokens into the path function.\n */\nfunction tokensToFunction (tokens, options) {\n // Compile all the tokens into regexps.\n var matches = new Array(tokens.length)\n\n // Compile all the patterns before compilation.\n for (var i = 0; i < tokens.length; i++) {\n if (typeof tokens[i] === 'object') {\n matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options))\n }\n }\n\n return function (obj, opts) {\n var path = ''\n var data = obj || {}\n var options = opts || {}\n var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent\n\n for (var i = 0; i < tokens.length; i++) {\n var token = tokens[i]\n\n if (typeof token === 'string') {\n path += token\n\n continue\n }\n\n var value = data[token.name]\n var segment\n\n if (value == null) {\n if (token.optional) {\n // Prepend partial segment prefixes.\n if (token.partial) {\n path += token.prefix\n }\n\n continue\n } else {\n throw new TypeError('Expected \"' + token.name + '\" to be defined')\n }\n }\n\n if (isarray(value)) {\n if (!token.repeat) {\n throw new TypeError('Expected \"' + token.name + '\" to not repeat, but received `' + JSON.stringify(value) + '`')\n }\n\n if (value.length === 0) {\n if (token.optional) {\n continue\n } else {\n throw new TypeError('Expected \"' + token.name + '\" to not be empty')\n }\n }\n\n for (var j = 0; j < value.length; j++) {\n segment = encode(value[j])\n\n if (!matches[i].test(segment)) {\n throw new TypeError('Expected all \"' + token.name + '\" to match \"' + token.pattern + '\", but received `' + JSON.stringify(segment) + '`')\n }\n\n path += (j === 0 ? token.prefix : token.delimiter) + segment\n }\n\n continue\n }\n\n segment = token.asterisk ? encodeAsterisk(value) : encode(value)\n\n if (!matches[i].test(segment)) {\n throw new TypeError('Expected \"' + token.name + '\" to match \"' + token.pattern + '\", but received \"' + segment + '\"')\n }\n\n path += token.prefix + segment\n }\n\n return path\n }\n}\n\n/**\n * Escape a regular expression string.\n *\n * @param {string} str\n * @return {string}\n */\nfunction escapeString (str) {\n return str.replace(/([.+*?=^!:${}()[\\]|\\/\\\\])/g, '\\\\$1')\n}\n\n/**\n * Escape the capturing group by escaping special characters and meaning.\n *\n * @param {string} group\n * @return {string}\n */\nfunction escapeGroup (group) {\n return group.replace(/([=!:$\\/()])/g, '\\\\$1')\n}\n\n/**\n * Attach the keys as a property of the regexp.\n *\n * @param {!RegExp} re\n * @param {Array} keys\n * @return {!RegExp}\n */\nfunction attachKeys (re, keys) {\n re.keys = keys\n return re\n}\n\n/**\n * Get the flags for a regexp from the options.\n *\n * @param {Object} options\n * @return {string}\n */\nfunction flags (options) {\n return options && options.sensitive ? '' : 'i'\n}\n\n/**\n * Pull out keys from a regexp.\n *\n * @param {!RegExp} path\n * @param {!Array} keys\n * @return {!RegExp}\n */\nfunction regexpToRegexp (path, keys) {\n // Use a negative lookahead to match only capturing groups.\n var groups = path.source.match(/\\((?!\\?)/g)\n\n if (groups) {\n for (var i = 0; i < groups.length; i++) {\n keys.push({\n name: i,\n prefix: null,\n delimiter: null,\n optional: false,\n repeat: false,\n partial: false,\n asterisk: false,\n pattern: null\n })\n }\n }\n\n return attachKeys(path, keys)\n}\n\n/**\n * Transform an array into a regexp.\n *\n * @param {!Array} path\n * @param {Array} keys\n * @param {!Object} options\n * @return {!RegExp}\n */\nfunction arrayToRegexp (path, keys, options) {\n var parts = []\n\n for (var i = 0; i < path.length; i++) {\n parts.push(pathToRegexp(path[i], keys, options).source)\n }\n\n var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))\n\n return attachKeys(regexp, keys)\n}\n\n/**\n * Create a path regexp from string input.\n *\n * @param {string} path\n * @param {!Array} keys\n * @param {!Object} options\n * @return {!RegExp}\n */\nfunction stringToRegexp (path, keys, options) {\n return tokensToRegExp(parse(path, options), keys, options)\n}\n\n/**\n * Expose a function for taking tokens and returning a RegExp.\n *\n * @param {!Array} tokens\n * @param {(Array|Object)=} keys\n * @param {Object=} options\n * @return {!RegExp}\n */\nfunction tokensToRegExp (tokens, keys, options) {\n if (!isarray(keys)) {\n options = /** @type {!Object} */ (keys || options)\n keys = []\n }\n\n options = options || {}\n\n var strict = options.strict\n var end = options.end !== false\n var route = ''\n\n // Iterate over the tokens and create our regexp string.\n for (var i = 0; i < tokens.length; i++) {\n var token = tokens[i]\n\n if (typeof token === 'string') {\n route += escapeString(token)\n } else {\n var prefix = escapeString(token.prefix)\n var capture = '(?:' + token.pattern + ')'\n\n keys.push(token)\n\n if (token.repeat) {\n capture += '(?:' + prefix + capture + ')*'\n }\n\n if (token.optional) {\n if (!token.partial) {\n capture = '(?:' + prefix + '(' + capture + '))?'\n } else {\n capture = prefix + '(' + capture + ')?'\n }\n } else {\n capture = prefix + '(' + capture + ')'\n }\n\n route += capture\n }\n }\n\n var delimiter = escapeString(options.delimiter || '/')\n var endsWithDelimiter = route.slice(-delimiter.length) === delimiter\n\n // In non-strict mode we allow a slash at the end of match. If the path to\n // match already ends with a slash, we remove it for consistency. The slash\n // is valid at the end of a path match, not in the middle. This is important\n // in non-ending mode, where \"/test/\" shouldn't match \"/test//route\".\n if (!strict) {\n route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'\n }\n\n if (end) {\n route += '$'\n } else {\n // In non-ending mode, we need the capturing groups to match as much as\n // possible by using a positive lookahead to the end or next path segment.\n route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'\n }\n\n return attachKeys(new RegExp('^' + route, flags(options)), keys)\n}\n\n/**\n * Normalize the given path string, returning a regular expression.\n *\n * An empty array can be passed in for the keys, which will hold the\n * placeholder key descriptions. For example, using `/user/:id`, `keys` will\n * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.\n *\n * @param {(string|RegExp|Array)} path\n * @param {(Array|Object)=} keys\n * @param {Object=} options\n * @return {!RegExp}\n */\nfunction pathToRegexp (path, keys, options) {\n if (!isarray(keys)) {\n options = /** @type {!Object} */ (keys || options)\n keys = []\n }\n\n options = options || {}\n\n if (path instanceof RegExp) {\n return regexpToRegexp(path, /** @type {!Array} */ (keys))\n }\n\n if (isarray(path)) {\n return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)\n }\n\n return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)\n}\n", "/** @license React v16.13.1\n * react-is.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';var b=\"function\"===typeof Symbol&&Symbol.for,c=b?Symbol.for(\"react.element\"):60103,d=b?Symbol.for(\"react.portal\"):60106,e=b?Symbol.for(\"react.fragment\"):60107,f=b?Symbol.for(\"react.strict_mode\"):60108,g=b?Symbol.for(\"react.profiler\"):60114,h=b?Symbol.for(\"react.provider\"):60109,k=b?Symbol.for(\"react.context\"):60110,l=b?Symbol.for(\"react.async_mode\"):60111,m=b?Symbol.for(\"react.concurrent_mode\"):60111,n=b?Symbol.for(\"react.forward_ref\"):60112,p=b?Symbol.for(\"react.suspense\"):60113,q=b?\nSymbol.for(\"react.suspense_list\"):60120,r=b?Symbol.for(\"react.memo\"):60115,t=b?Symbol.for(\"react.lazy\"):60116,v=b?Symbol.for(\"react.block\"):60121,w=b?Symbol.for(\"react.fundamental\"):60117,x=b?Symbol.for(\"react.responder\"):60118,y=b?Symbol.for(\"react.scope\"):60119;\nfunction z(a){if(\"object\"===typeof a&&null!==a){var u=a.$$typeof;switch(u){case c:switch(a=a.type,a){case l:case m:case e:case g:case f:case p:return a;default:switch(a=a&&a.$$typeof,a){case k:case n:case t:case r:case h:return a;default:return u}}case d:return u}}}function A(a){return z(a)===m}exports.AsyncMode=l;exports.ConcurrentMode=m;exports.ContextConsumer=k;exports.ContextProvider=h;exports.Element=c;exports.ForwardRef=n;exports.Fragment=e;exports.Lazy=t;exports.Memo=r;exports.Portal=d;\nexports.Profiler=g;exports.StrictMode=f;exports.Suspense=p;exports.isAsyncMode=function(a){return A(a)||z(a)===l};exports.isConcurrentMode=A;exports.isContextConsumer=function(a){return z(a)===k};exports.isContextProvider=function(a){return z(a)===h};exports.isElement=function(a){return\"object\"===typeof a&&null!==a&&a.$$typeof===c};exports.isForwardRef=function(a){return z(a)===n};exports.isFragment=function(a){return z(a)===e};exports.isLazy=function(a){return z(a)===t};\nexports.isMemo=function(a){return z(a)===r};exports.isPortal=function(a){return z(a)===d};exports.isProfiler=function(a){return z(a)===g};exports.isStrictMode=function(a){return z(a)===f};exports.isSuspense=function(a){return z(a)===p};\nexports.isValidElementType=function(a){return\"string\"===typeof a||\"function\"===typeof a||a===e||a===m||a===g||a===f||a===p||a===q||\"object\"===typeof a&&null!==a&&(a.$$typeof===t||a.$$typeof===r||a.$$typeof===h||a.$$typeof===k||a.$$typeof===n||a.$$typeof===w||a.$$typeof===x||a.$$typeof===y||a.$$typeof===v)};exports.typeOf=z;\n", "'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-is.production.min.js');\n} else {\n module.exports = require('./cjs/react-is.development.js');\n}\n", "'use strict';\n\nvar reactIs = require('react-is');\n\n/**\n * Copyright 2015, Yahoo! Inc.\n * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.\n */\nvar REACT_STATICS = {\n childContextTypes: true,\n contextType: true,\n contextTypes: true,\n defaultProps: true,\n displayName: true,\n getDefaultProps: true,\n getDerivedStateFromError: true,\n getDerivedStateFromProps: true,\n mixins: true,\n propTypes: true,\n type: true\n};\nvar KNOWN_STATICS = {\n name: true,\n length: true,\n prototype: true,\n caller: true,\n callee: true,\n arguments: true,\n arity: true\n};\nvar FORWARD_REF_STATICS = {\n '$$typeof': true,\n render: true,\n defaultProps: true,\n displayName: true,\n propTypes: true\n};\nvar MEMO_STATICS = {\n '$$typeof': true,\n compare: true,\n defaultProps: true,\n displayName: true,\n propTypes: true,\n type: true\n};\nvar TYPE_STATICS = {};\nTYPE_STATICS[reactIs.ForwardRef] = FORWARD_REF_STATICS;\nTYPE_STATICS[reactIs.Memo] = MEMO_STATICS;\n\nfunction getStatics(component) {\n // React v16.11 and below\n if (reactIs.isMemo(component)) {\n return MEMO_STATICS;\n } // React v16.12 and above\n\n\n return TYPE_STATICS[component['$$typeof']] || REACT_STATICS;\n}\n\nvar defineProperty = Object.defineProperty;\nvar getOwnPropertyNames = Object.getOwnPropertyNames;\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;\nvar getPrototypeOf = Object.getPrototypeOf;\nvar objectPrototype = Object.prototype;\nfunction hoistNonReactStatics(targetComponent, sourceComponent, blacklist) {\n if (typeof sourceComponent !== 'string') {\n // don't hoist over string (html) components\n if (objectPrototype) {\n var inheritedComponent = getPrototypeOf(sourceComponent);\n\n if (inheritedComponent && inheritedComponent !== objectPrototype) {\n hoistNonReactStatics(targetComponent, inheritedComponent, blacklist);\n }\n }\n\n var keys = getOwnPropertyNames(sourceComponent);\n\n if (getOwnPropertySymbols) {\n keys = keys.concat(getOwnPropertySymbols(sourceComponent));\n }\n\n var targetStatics = getStatics(targetComponent);\n var sourceStatics = getStatics(sourceComponent);\n\n for (var i = 0; i < keys.length; ++i) {\n var key = keys[i];\n\n if (!KNOWN_STATICS[key] && !(blacklist && blacklist[key]) && !(sourceStatics && sourceStatics[key]) && !(targetStatics && targetStatics[key])) {\n var descriptor = getOwnPropertyDescriptor(sourceComponent, key);\n\n try {\n // Avoid failures from read-only properties\n defineProperty(targetComponent, key, descriptor);\n } catch (e) {}\n }\n }\n }\n\n return targetComponent;\n}\n\nmodule.exports = hoistNonReactStatics;\n", "//! moment.js\n//! version : 2.29.4\n//! authors : Tim Wood, Iskren Chernev, Moment.js contributors\n//! license : MIT\n//! momentjs.com\n\n;(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n global.moment = factory()\n}(this, (function () { 'use strict';\n\n var hookCallback;\n\n function hooks() {\n return hookCallback.apply(null, arguments);\n }\n\n // This is done to register the method called with moment()\n // without creating circular dependencies.\n function setHookCallback(callback) {\n hookCallback = callback;\n }\n\n function isArray(input) {\n return (\n input instanceof Array ||\n Object.prototype.toString.call(input) === '[object Array]'\n );\n }\n\n function isObject(input) {\n // IE8 will treat undefined and null as object if it wasn't for\n // input != null\n return (\n input != null &&\n Object.prototype.toString.call(input) === '[object Object]'\n );\n }\n\n function hasOwnProp(a, b) {\n return Object.prototype.hasOwnProperty.call(a, b);\n }\n\n function isObjectEmpty(obj) {\n if (Object.getOwnPropertyNames) {\n return Object.getOwnPropertyNames(obj).length === 0;\n } else {\n var k;\n for (k in obj) {\n if (hasOwnProp(obj, k)) {\n return false;\n }\n }\n return true;\n }\n }\n\n function isUndefined(input) {\n return input === void 0;\n }\n\n function isNumber(input) {\n return (\n typeof input === 'number' ||\n Object.prototype.toString.call(input) === '[object Number]'\n );\n }\n\n function isDate(input) {\n return (\n input instanceof Date ||\n Object.prototype.toString.call(input) === '[object Date]'\n );\n }\n\n function map(arr, fn) {\n var res = [],\n i,\n arrLen = arr.length;\n for (i = 0; i < arrLen; ++i) {\n res.push(fn(arr[i], i));\n }\n return res;\n }\n\n function extend(a, b) {\n for (var i in b) {\n if (hasOwnProp(b, i)) {\n a[i] = b[i];\n }\n }\n\n if (hasOwnProp(b, 'toString')) {\n a.toString = b.toString;\n }\n\n if (hasOwnProp(b, 'valueOf')) {\n a.valueOf = b.valueOf;\n }\n\n return a;\n }\n\n function createUTC(input, format, locale, strict) {\n return createLocalOrUTC(input, format, locale, strict, true).utc();\n }\n\n function defaultParsingFlags() {\n // We need to deep clone this object.\n return {\n empty: false,\n unusedTokens: [],\n unusedInput: [],\n overflow: -2,\n charsLeftOver: 0,\n nullInput: false,\n invalidEra: null,\n invalidMonth: null,\n invalidFormat: false,\n userInvalidated: false,\n iso: false,\n parsedDateParts: [],\n era: null,\n meridiem: null,\n rfc2822: false,\n weekdayMismatch: false,\n };\n }\n\n function getParsingFlags(m) {\n if (m._pf == null) {\n m._pf = defaultParsingFlags();\n }\n return m._pf;\n }\n\n var some;\n if (Array.prototype.some) {\n some = Array.prototype.some;\n } else {\n some = function (fun) {\n var t = Object(this),\n len = t.length >>> 0,\n i;\n\n for (i = 0; i < len; i++) {\n if (i in t && fun.call(this, t[i], i, t)) {\n return true;\n }\n }\n\n return false;\n };\n }\n\n function isValid(m) {\n if (m._isValid == null) {\n var flags = getParsingFlags(m),\n parsedParts = some.call(flags.parsedDateParts, function (i) {\n return i != null;\n }),\n isNowValid =\n !isNaN(m._d.getTime()) &&\n flags.overflow < 0 &&\n !flags.empty &&\n !flags.invalidEra &&\n !flags.invalidMonth &&\n !flags.invalidWeekday &&\n !flags.weekdayMismatch &&\n !flags.nullInput &&\n !flags.invalidFormat &&\n !flags.userInvalidated &&\n (!flags.meridiem || (flags.meridiem && parsedParts));\n\n if (m._strict) {\n isNowValid =\n isNowValid &&\n flags.charsLeftOver === 0 &&\n flags.unusedTokens.length === 0 &&\n flags.bigHour === undefined;\n }\n\n if (Object.isFrozen == null || !Object.isFrozen(m)) {\n m._isValid = isNowValid;\n } else {\n return isNowValid;\n }\n }\n return m._isValid;\n }\n\n function createInvalid(flags) {\n var m = createUTC(NaN);\n if (flags != null) {\n extend(getParsingFlags(m), flags);\n } else {\n getParsingFlags(m).userInvalidated = true;\n }\n\n return m;\n }\n\n // Plugins that add properties should also add the key here (null value),\n // so we can properly clone ourselves.\n var momentProperties = (hooks.momentProperties = []),\n updateInProgress = false;\n\n function copyConfig(to, from) {\n var i,\n prop,\n val,\n momentPropertiesLen = momentProperties.length;\n\n if (!isUndefined(from._isAMomentObject)) {\n to._isAMomentObject = from._isAMomentObject;\n }\n if (!isUndefined(from._i)) {\n to._i = from._i;\n }\n if (!isUndefined(from._f)) {\n to._f = from._f;\n }\n if (!isUndefined(from._l)) {\n to._l = from._l;\n }\n if (!isUndefined(from._strict)) {\n to._strict = from._strict;\n }\n if (!isUndefined(from._tzm)) {\n to._tzm = from._tzm;\n }\n if (!isUndefined(from._isUTC)) {\n to._isUTC = from._isUTC;\n }\n if (!isUndefined(from._offset)) {\n to._offset = from._offset;\n }\n if (!isUndefined(from._pf)) {\n to._pf = getParsingFlags(from);\n }\n if (!isUndefined(from._locale)) {\n to._locale = from._locale;\n }\n\n if (momentPropertiesLen > 0) {\n for (i = 0; i < momentPropertiesLen; i++) {\n prop = momentProperties[i];\n val = from[prop];\n if (!isUndefined(val)) {\n to[prop] = val;\n }\n }\n }\n\n return to;\n }\n\n // Moment prototype object\n function Moment(config) {\n copyConfig(this, config);\n this._d = new Date(config._d != null ? config._d.getTime() : NaN);\n if (!this.isValid()) {\n this._d = new Date(NaN);\n }\n // Prevent infinite loop in case updateOffset creates new moment\n // objects.\n if (updateInProgress === false) {\n updateInProgress = true;\n hooks.updateOffset(this);\n updateInProgress = false;\n }\n }\n\n function isMoment(obj) {\n return (\n obj instanceof Moment || (obj != null && obj._isAMomentObject != null)\n );\n }\n\n function warn(msg) {\n if (\n hooks.suppressDeprecationWarnings === false &&\n typeof console !== 'undefined' &&\n console.warn\n ) {\n console.warn('Deprecation warning: ' + msg);\n }\n }\n\n function deprecate(msg, fn) {\n var firstTime = true;\n\n return extend(function () {\n if (hooks.deprecationHandler != null) {\n hooks.deprecationHandler(null, msg);\n }\n if (firstTime) {\n var args = [],\n arg,\n i,\n key,\n argLen = arguments.length;\n for (i = 0; i < argLen; i++) {\n arg = '';\n if (typeof arguments[i] === 'object') {\n arg += '\\n[' + i + '] ';\n for (key in arguments[0]) {\n if (hasOwnProp(arguments[0], key)) {\n arg += key + ': ' + arguments[0][key] + ', ';\n }\n }\n arg = arg.slice(0, -2); // Remove trailing comma and space\n } else {\n arg = arguments[i];\n }\n args.push(arg);\n }\n warn(\n msg +\n '\\nArguments: ' +\n Array.prototype.slice.call(args).join('') +\n '\\n' +\n new Error().stack\n );\n firstTime = false;\n }\n return fn.apply(this, arguments);\n }, fn);\n }\n\n var deprecations = {};\n\n function deprecateSimple(name, msg) {\n if (hooks.deprecationHandler != null) {\n hooks.deprecationHandler(name, msg);\n }\n if (!deprecations[name]) {\n warn(msg);\n deprecations[name] = true;\n }\n }\n\n hooks.suppressDeprecationWarnings = false;\n hooks.deprecationHandler = null;\n\n function isFunction(input) {\n return (\n (typeof Function !== 'undefined' && input instanceof Function) ||\n Object.prototype.toString.call(input) === '[object Function]'\n );\n }\n\n function set(config) {\n var prop, i;\n for (i in config) {\n if (hasOwnProp(config, i)) {\n prop = config[i];\n if (isFunction(prop)) {\n this[i] = prop;\n } else {\n this['_' + i] = prop;\n }\n }\n }\n this._config = config;\n // Lenient ordinal parsing accepts just a number in addition to\n // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.\n // TODO: Remove \"ordinalParse\" fallback in next major release.\n this._dayOfMonthOrdinalParseLenient = new RegExp(\n (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +\n '|' +\n /\\d{1,2}/.source\n );\n }\n\n function mergeConfigs(parentConfig, childConfig) {\n var res = extend({}, parentConfig),\n prop;\n for (prop in childConfig) {\n if (hasOwnProp(childConfig, prop)) {\n if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {\n res[prop] = {};\n extend(res[prop], parentConfig[prop]);\n extend(res[prop], childConfig[prop]);\n } else if (childConfig[prop] != null) {\n res[prop] = childConfig[prop];\n } else {\n delete res[prop];\n }\n }\n }\n for (prop in parentConfig) {\n if (\n hasOwnProp(parentConfig, prop) &&\n !hasOwnProp(childConfig, prop) &&\n isObject(parentConfig[prop])\n ) {\n // make sure changes to properties don't modify parent config\n res[prop] = extend({}, res[prop]);\n }\n }\n return res;\n }\n\n function Locale(config) {\n if (config != null) {\n this.set(config);\n }\n }\n\n var keys;\n\n if (Object.keys) {\n keys = Object.keys;\n } else {\n keys = function (obj) {\n var i,\n res = [];\n for (i in obj) {\n if (hasOwnProp(obj, i)) {\n res.push(i);\n }\n }\n return res;\n };\n }\n\n var defaultCalendar = {\n sameDay: '[Today at] LT',\n nextDay: '[Tomorrow at] LT',\n nextWeek: 'dddd [at] LT',\n lastDay: '[Yesterday at] LT',\n lastWeek: '[Last] dddd [at] LT',\n sameElse: 'L',\n };\n\n function calendar(key, mom, now) {\n var output = this._calendar[key] || this._calendar['sameElse'];\n return isFunction(output) ? output.call(mom, now) : output;\n }\n\n function zeroFill(number, targetLength, forceSign) {\n var absNumber = '' + Math.abs(number),\n zerosToFill = targetLength - absNumber.length,\n sign = number >= 0;\n return (\n (sign ? (forceSign ? '+' : '') : '-') +\n Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +\n absNumber\n );\n }\n\n var formattingTokens =\n /(\\[[^\\[]*\\])|(\\\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,\n localFormattingTokens = /(\\[[^\\[]*\\])|(\\\\)?(LTS|LT|LL?L?L?|l{1,4})/g,\n formatFunctions = {},\n formatTokenFunctions = {};\n\n // token: 'M'\n // padded: ['MM', 2]\n // ordinal: 'Mo'\n // callback: function () { this.month() + 1 }\n function addFormatToken(token, padded, ordinal, callback) {\n var func = callback;\n if (typeof callback === 'string') {\n func = function () {\n return this[callback]();\n };\n }\n if (token) {\n formatTokenFunctions[token] = func;\n }\n if (padded) {\n formatTokenFunctions[padded[0]] = function () {\n return zeroFill(func.apply(this, arguments), padded[1], padded[2]);\n };\n }\n if (ordinal) {\n formatTokenFunctions[ordinal] = function () {\n return this.localeData().ordinal(\n func.apply(this, arguments),\n token\n );\n };\n }\n }\n\n function removeFormattingTokens(input) {\n if (input.match(/\\[[\\s\\S]/)) {\n return input.replace(/^\\[|\\]$/g, '');\n }\n return input.replace(/\\\\/g, '');\n }\n\n function makeFormatFunction(format) {\n var array = format.match(formattingTokens),\n i,\n length;\n\n for (i = 0, length = array.length; i < length; i++) {\n if (formatTokenFunctions[array[i]]) {\n array[i] = formatTokenFunctions[array[i]];\n } else {\n array[i] = removeFormattingTokens(array[i]);\n }\n }\n\n return function (mom) {\n var output = '',\n i;\n for (i = 0; i < length; i++) {\n output += isFunction(array[i])\n ? array[i].call(mom, format)\n : array[i];\n }\n return output;\n };\n }\n\n // format date using native date object\n function formatMoment(m, format) {\n if (!m.isValid()) {\n return m.localeData().invalidDate();\n }\n\n format = expandFormat(format, m.localeData());\n formatFunctions[format] =\n formatFunctions[format] || makeFormatFunction(format);\n\n return formatFunctions[format](m);\n }\n\n function expandFormat(format, locale) {\n var i = 5;\n\n function replaceLongDateFormatTokens(input) {\n return locale.longDateFormat(input) || input;\n }\n\n localFormattingTokens.lastIndex = 0;\n while (i >= 0 && localFormattingTokens.test(format)) {\n format = format.replace(\n localFormattingTokens,\n replaceLongDateFormatTokens\n );\n localFormattingTokens.lastIndex = 0;\n i -= 1;\n }\n\n return format;\n }\n\n var defaultLongDateFormat = {\n LTS: 'h:mm:ss A',\n LT: 'h:mm A',\n L: 'MM/DD/YYYY',\n LL: 'MMMM D, YYYY',\n LLL: 'MMMM D, YYYY h:mm A',\n LLLL: 'dddd, MMMM D, YYYY h:mm A',\n };\n\n function longDateFormat(key) {\n var format = this._longDateFormat[key],\n formatUpper = this._longDateFormat[key.toUpperCase()];\n\n if (format || !formatUpper) {\n return format;\n }\n\n this._longDateFormat[key] = formatUpper\n .match(formattingTokens)\n .map(function (tok) {\n if (\n tok === 'MMMM' ||\n tok === 'MM' ||\n tok === 'DD' ||\n tok === 'dddd'\n ) {\n return tok.slice(1);\n }\n return tok;\n })\n .join('');\n\n return this._longDateFormat[key];\n }\n\n var defaultInvalidDate = 'Invalid date';\n\n function invalidDate() {\n return this._invalidDate;\n }\n\n var defaultOrdinal = '%d',\n defaultDayOfMonthOrdinalParse = /\\d{1,2}/;\n\n function ordinal(number) {\n return this._ordinal.replace('%d', number);\n }\n\n var defaultRelativeTime = {\n future: 'in %s',\n past: '%s ago',\n s: 'a few seconds',\n ss: '%d seconds',\n m: 'a minute',\n mm: '%d minutes',\n h: 'an hour',\n hh: '%d hours',\n d: 'a day',\n dd: '%d days',\n w: 'a week',\n ww: '%d weeks',\n M: 'a month',\n MM: '%d months',\n y: 'a year',\n yy: '%d years',\n };\n\n function relativeTime(number, withoutSuffix, string, isFuture) {\n var output = this._relativeTime[string];\n return isFunction(output)\n ? output(number, withoutSuffix, string, isFuture)\n : output.replace(/%d/i, number);\n }\n\n function pastFuture(diff, output) {\n var format = this._relativeTime[diff > 0 ? 'future' : 'past'];\n return isFunction(format) ? format(output) : format.replace(/%s/i, output);\n }\n\n var aliases = {};\n\n function addUnitAlias(unit, shorthand) {\n var lowerCase = unit.toLowerCase();\n aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;\n }\n\n function normalizeUnits(units) {\n return typeof units === 'string'\n ? aliases[units] || aliases[units.toLowerCase()]\n : undefined;\n }\n\n function normalizeObjectUnits(inputObject) {\n var normalizedInput = {},\n normalizedProp,\n prop;\n\n for (prop in inputObject) {\n if (hasOwnProp(inputObject, prop)) {\n normalizedProp = normalizeUnits(prop);\n if (normalizedProp) {\n normalizedInput[normalizedProp] = inputObject[prop];\n }\n }\n }\n\n return normalizedInput;\n }\n\n var priorities = {};\n\n function addUnitPriority(unit, priority) {\n priorities[unit] = priority;\n }\n\n function getPrioritizedUnits(unitsObj) {\n var units = [],\n u;\n for (u in unitsObj) {\n if (hasOwnProp(unitsObj, u)) {\n units.push({ unit: u, priority: priorities[u] });\n }\n }\n units.sort(function (a, b) {\n return a.priority - b.priority;\n });\n return units;\n }\n\n function isLeapYear(year) {\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n }\n\n function absFloor(number) {\n if (number < 0) {\n // -0 -> 0\n return Math.ceil(number) || 0;\n } else {\n return Math.floor(number);\n }\n }\n\n function toInt(argumentForCoercion) {\n var coercedNumber = +argumentForCoercion,\n value = 0;\n\n if (coercedNumber !== 0 && isFinite(coercedNumber)) {\n value = absFloor(coercedNumber);\n }\n\n return value;\n }\n\n function makeGetSet(unit, keepTime) {\n return function (value) {\n if (value != null) {\n set$1(this, unit, value);\n hooks.updateOffset(this, keepTime);\n return this;\n } else {\n return get(this, unit);\n }\n };\n }\n\n function get(mom, unit) {\n return mom.isValid()\n ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()\n : NaN;\n }\n\n function set$1(mom, unit, value) {\n if (mom.isValid() && !isNaN(value)) {\n if (\n unit === 'FullYear' &&\n isLeapYear(mom.year()) &&\n mom.month() === 1 &&\n mom.date() === 29\n ) {\n value = toInt(value);\n mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](\n value,\n mom.month(),\n daysInMonth(value, mom.month())\n );\n } else {\n mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);\n }\n }\n }\n\n // MOMENTS\n\n function stringGet(units) {\n units = normalizeUnits(units);\n if (isFunction(this[units])) {\n return this[units]();\n }\n return this;\n }\n\n function stringSet(units, value) {\n if (typeof units === 'object') {\n units = normalizeObjectUnits(units);\n var prioritized = getPrioritizedUnits(units),\n i,\n prioritizedLen = prioritized.length;\n for (i = 0; i < prioritizedLen; i++) {\n this[prioritized[i].unit](units[prioritized[i].unit]);\n }\n } else {\n units = normalizeUnits(units);\n if (isFunction(this[units])) {\n return this[units](value);\n }\n }\n return this;\n }\n\n var match1 = /\\d/, // 0 - 9\n match2 = /\\d\\d/, // 00 - 99\n match3 = /\\d{3}/, // 000 - 999\n match4 = /\\d{4}/, // 0000 - 9999\n match6 = /[+-]?\\d{6}/, // -999999 - 999999\n match1to2 = /\\d\\d?/, // 0 - 99\n match3to4 = /\\d\\d\\d\\d?/, // 999 - 9999\n match5to6 = /\\d\\d\\d\\d\\d\\d?/, // 99999 - 999999\n match1to3 = /\\d{1,3}/, // 0 - 999\n match1to4 = /\\d{1,4}/, // 0 - 9999\n match1to6 = /[+-]?\\d{1,6}/, // -999999 - 999999\n matchUnsigned = /\\d+/, // 0 - inf\n matchSigned = /[+-]?\\d+/, // -inf - inf\n matchOffset = /Z|[+-]\\d\\d:?\\d\\d/gi, // +00:00 -00:00 +0000 -0000 or Z\n matchShortOffset = /Z|[+-]\\d\\d(?::?\\d\\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z\n matchTimestamp = /[+-]?\\d+(\\.\\d{1,3})?/, // 123456789 123456789.123\n // any word (or two) characters or numbers including two/three word month in arabic.\n // includes scottish gaelic two word and hyphenated months\n matchWord =\n /[0-9]{0,256}['a-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFF07\\uFF10-\\uFFEF]{1,256}|[\\u0600-\\u06FF\\/]{1,256}(\\s*?[\\u0600-\\u06FF]{1,256}){1,2}/i,\n regexes;\n\n regexes = {};\n\n function addRegexToken(token, regex, strictRegex) {\n regexes[token] = isFunction(regex)\n ? regex\n : function (isStrict, localeData) {\n return isStrict && strictRegex ? strictRegex : regex;\n };\n }\n\n function getParseRegexForToken(token, config) {\n if (!hasOwnProp(regexes, token)) {\n return new RegExp(unescapeFormat(token));\n }\n\n return regexes[token](config._strict, config._locale);\n }\n\n // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript\n function unescapeFormat(s) {\n return regexEscape(\n s\n .replace('\\\\', '')\n .replace(\n /\\\\(\\[)|\\\\(\\])|\\[([^\\]\\[]*)\\]|\\\\(.)/g,\n function (matched, p1, p2, p3, p4) {\n return p1 || p2 || p3 || p4;\n }\n )\n );\n }\n\n function regexEscape(s) {\n return s.replace(/[-\\/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');\n }\n\n var tokens = {};\n\n function addParseToken(token, callback) {\n var i,\n func = callback,\n tokenLen;\n if (typeof token === 'string') {\n token = [token];\n }\n if (isNumber(callback)) {\n func = function (input, array) {\n array[callback] = toInt(input);\n };\n }\n tokenLen = token.length;\n for (i = 0; i < tokenLen; i++) {\n tokens[token[i]] = func;\n }\n }\n\n function addWeekParseToken(token, callback) {\n addParseToken(token, function (input, array, config, token) {\n config._w = config._w || {};\n callback(input, config._w, config, token);\n });\n }\n\n function addTimeToArrayFromToken(token, input, config) {\n if (input != null && hasOwnProp(tokens, token)) {\n tokens[token](input, config._a, config, token);\n }\n }\n\n var YEAR = 0,\n MONTH = 1,\n DATE = 2,\n HOUR = 3,\n MINUTE = 4,\n SECOND = 5,\n MILLISECOND = 6,\n WEEK = 7,\n WEEKDAY = 8;\n\n function mod(n, x) {\n return ((n % x) + x) % x;\n }\n\n var indexOf;\n\n if (Array.prototype.indexOf) {\n indexOf = Array.prototype.indexOf;\n } else {\n indexOf = function (o) {\n // I know\n var i;\n for (i = 0; i < this.length; ++i) {\n if (this[i] === o) {\n return i;\n }\n }\n return -1;\n };\n }\n\n function daysInMonth(year, month) {\n if (isNaN(year) || isNaN(month)) {\n return NaN;\n }\n var modMonth = mod(month, 12);\n year += (month - modMonth) / 12;\n return modMonth === 1\n ? isLeapYear(year)\n ? 29\n : 28\n : 31 - ((modMonth % 7) % 2);\n }\n\n // FORMATTING\n\n addFormatToken('M', ['MM', 2], 'Mo', function () {\n return this.month() + 1;\n });\n\n addFormatToken('MMM', 0, 0, function (format) {\n return this.localeData().monthsShort(this, format);\n });\n\n addFormatToken('MMMM', 0, 0, function (format) {\n return this.localeData().months(this, format);\n });\n\n // ALIASES\n\n addUnitAlias('month', 'M');\n\n // PRIORITY\n\n addUnitPriority('month', 8);\n\n // PARSING\n\n addRegexToken('M', match1to2);\n addRegexToken('MM', match1to2, match2);\n addRegexToken('MMM', function (isStrict, locale) {\n return locale.monthsShortRegex(isStrict);\n });\n addRegexToken('MMMM', function (isStrict, locale) {\n return locale.monthsRegex(isStrict);\n });\n\n addParseToken(['M', 'MM'], function (input, array) {\n array[MONTH] = toInt(input) - 1;\n });\n\n addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {\n var month = config._locale.monthsParse(input, token, config._strict);\n // if we didn't find a month name, mark the date as invalid.\n if (month != null) {\n array[MONTH] = month;\n } else {\n getParsingFlags(config).invalidMonth = input;\n }\n });\n\n // LOCALES\n\n var defaultLocaleMonths =\n 'January_February_March_April_May_June_July_August_September_October_November_December'.split(\n '_'\n ),\n defaultLocaleMonthsShort =\n 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),\n MONTHS_IN_FORMAT = /D[oD]?(\\[[^\\[\\]]*\\]|\\s)+MMMM?/,\n defaultMonthsShortRegex = matchWord,\n defaultMonthsRegex = matchWord;\n\n function localeMonths(m, format) {\n if (!m) {\n return isArray(this._months)\n ? this._months\n : this._months['standalone'];\n }\n return isArray(this._months)\n ? this._months[m.month()]\n : this._months[\n (this._months.isFormat || MONTHS_IN_FORMAT).test(format)\n ? 'format'\n : 'standalone'\n ][m.month()];\n }\n\n function localeMonthsShort(m, format) {\n if (!m) {\n return isArray(this._monthsShort)\n ? this._monthsShort\n : this._monthsShort['standalone'];\n }\n return isArray(this._monthsShort)\n ? this._monthsShort[m.month()]\n : this._monthsShort[\n MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'\n ][m.month()];\n }\n\n function handleStrictParse(monthName, format, strict) {\n var i,\n ii,\n mom,\n llc = monthName.toLocaleLowerCase();\n if (!this._monthsParse) {\n // this is not used\n this._monthsParse = [];\n this._longMonthsParse = [];\n this._shortMonthsParse = [];\n for (i = 0; i < 12; ++i) {\n mom = createUTC([2000, i]);\n this._shortMonthsParse[i] = this.monthsShort(\n mom,\n ''\n ).toLocaleLowerCase();\n this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();\n }\n }\n\n if (strict) {\n if (format === 'MMM') {\n ii = indexOf.call(this._shortMonthsParse, llc);\n return ii !== -1 ? ii : null;\n } else {\n ii = indexOf.call(this._longMonthsParse, llc);\n return ii !== -1 ? ii : null;\n }\n } else {\n if (format === 'MMM') {\n ii = indexOf.call(this._shortMonthsParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._longMonthsParse, llc);\n return ii !== -1 ? ii : null;\n } else {\n ii = indexOf.call(this._longMonthsParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._shortMonthsParse, llc);\n return ii !== -1 ? ii : null;\n }\n }\n }\n\n function localeMonthsParse(monthName, format, strict) {\n var i, mom, regex;\n\n if (this._monthsParseExact) {\n return handleStrictParse.call(this, monthName, format, strict);\n }\n\n if (!this._monthsParse) {\n this._monthsParse = [];\n this._longMonthsParse = [];\n this._shortMonthsParse = [];\n }\n\n // TODO: add sorting\n // Sorting makes sure if one month (or abbr) is a prefix of another\n // see sorting in computeMonthsParse\n for (i = 0; i < 12; i++) {\n // make the regex if we don't have it already\n mom = createUTC([2000, i]);\n if (strict && !this._longMonthsParse[i]) {\n this._longMonthsParse[i] = new RegExp(\n '^' + this.months(mom, '').replace('.', '') + '$',\n 'i'\n );\n this._shortMonthsParse[i] = new RegExp(\n '^' + this.monthsShort(mom, '').replace('.', '') + '$',\n 'i'\n );\n }\n if (!strict && !this._monthsParse[i]) {\n regex =\n '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');\n this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');\n }\n // test the regex\n if (\n strict &&\n format === 'MMMM' &&\n this._longMonthsParse[i].test(monthName)\n ) {\n return i;\n } else if (\n strict &&\n format === 'MMM' &&\n this._shortMonthsParse[i].test(monthName)\n ) {\n return i;\n } else if (!strict && this._monthsParse[i].test(monthName)) {\n return i;\n }\n }\n }\n\n // MOMENTS\n\n function setMonth(mom, value) {\n var dayOfMonth;\n\n if (!mom.isValid()) {\n // No op\n return mom;\n }\n\n if (typeof value === 'string') {\n if (/^\\d+$/.test(value)) {\n value = toInt(value);\n } else {\n value = mom.localeData().monthsParse(value);\n // TODO: Another silent failure?\n if (!isNumber(value)) {\n return mom;\n }\n }\n }\n\n dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));\n mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);\n return mom;\n }\n\n function getSetMonth(value) {\n if (value != null) {\n setMonth(this, value);\n hooks.updateOffset(this, true);\n return this;\n } else {\n return get(this, 'Month');\n }\n }\n\n function getDaysInMonth() {\n return daysInMonth(this.year(), this.month());\n }\n\n function monthsShortRegex(isStrict) {\n if (this._monthsParseExact) {\n if (!hasOwnProp(this, '_monthsRegex')) {\n computeMonthsParse.call(this);\n }\n if (isStrict) {\n return this._monthsShortStrictRegex;\n } else {\n return this._monthsShortRegex;\n }\n } else {\n if (!hasOwnProp(this, '_monthsShortRegex')) {\n this._monthsShortRegex = defaultMonthsShortRegex;\n }\n return this._monthsShortStrictRegex && isStrict\n ? this._monthsShortStrictRegex\n : this._monthsShortRegex;\n }\n }\n\n function monthsRegex(isStrict) {\n if (this._monthsParseExact) {\n if (!hasOwnProp(this, '_monthsRegex')) {\n computeMonthsParse.call(this);\n }\n if (isStrict) {\n return this._monthsStrictRegex;\n } else {\n return this._monthsRegex;\n }\n } else {\n if (!hasOwnProp(this, '_monthsRegex')) {\n this._monthsRegex = defaultMonthsRegex;\n }\n return this._monthsStrictRegex && isStrict\n ? this._monthsStrictRegex\n : this._monthsRegex;\n }\n }\n\n function computeMonthsParse() {\n function cmpLenRev(a, b) {\n return b.length - a.length;\n }\n\n var shortPieces = [],\n longPieces = [],\n mixedPieces = [],\n i,\n mom;\n for (i = 0; i < 12; i++) {\n // make the regex if we don't have it already\n mom = createUTC([2000, i]);\n shortPieces.push(this.monthsShort(mom, ''));\n longPieces.push(this.months(mom, ''));\n mixedPieces.push(this.months(mom, ''));\n mixedPieces.push(this.monthsShort(mom, ''));\n }\n // Sorting makes sure if one month (or abbr) is a prefix of another it\n // will match the longer piece.\n shortPieces.sort(cmpLenRev);\n longPieces.sort(cmpLenRev);\n mixedPieces.sort(cmpLenRev);\n for (i = 0; i < 12; i++) {\n shortPieces[i] = regexEscape(shortPieces[i]);\n longPieces[i] = regexEscape(longPieces[i]);\n }\n for (i = 0; i < 24; i++) {\n mixedPieces[i] = regexEscape(mixedPieces[i]);\n }\n\n this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n this._monthsShortRegex = this._monthsRegex;\n this._monthsStrictRegex = new RegExp(\n '^(' + longPieces.join('|') + ')',\n 'i'\n );\n this._monthsShortStrictRegex = new RegExp(\n '^(' + shortPieces.join('|') + ')',\n 'i'\n );\n }\n\n // FORMATTING\n\n addFormatToken('Y', 0, 0, function () {\n var y = this.year();\n return y <= 9999 ? zeroFill(y, 4) : '+' + y;\n });\n\n addFormatToken(0, ['YY', 2], 0, function () {\n return this.year() % 100;\n });\n\n addFormatToken(0, ['YYYY', 4], 0, 'year');\n addFormatToken(0, ['YYYYY', 5], 0, 'year');\n addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');\n\n // ALIASES\n\n addUnitAlias('year', 'y');\n\n // PRIORITIES\n\n addUnitPriority('year', 1);\n\n // PARSING\n\n addRegexToken('Y', matchSigned);\n addRegexToken('YY', match1to2, match2);\n addRegexToken('YYYY', match1to4, match4);\n addRegexToken('YYYYY', match1to6, match6);\n addRegexToken('YYYYYY', match1to6, match6);\n\n addParseToken(['YYYYY', 'YYYYYY'], YEAR);\n addParseToken('YYYY', function (input, array) {\n array[YEAR] =\n input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);\n });\n addParseToken('YY', function (input, array) {\n array[YEAR] = hooks.parseTwoDigitYear(input);\n });\n addParseToken('Y', function (input, array) {\n array[YEAR] = parseInt(input, 10);\n });\n\n // HELPERS\n\n function daysInYear(year) {\n return isLeapYear(year) ? 366 : 365;\n }\n\n // HOOKS\n\n hooks.parseTwoDigitYear = function (input) {\n return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);\n };\n\n // MOMENTS\n\n var getSetYear = makeGetSet('FullYear', true);\n\n function getIsLeapYear() {\n return isLeapYear(this.year());\n }\n\n function createDate(y, m, d, h, M, s, ms) {\n // can't just apply() to create a date:\n // https://stackoverflow.com/q/181348\n var date;\n // the date constructor remaps years 0-99 to 1900-1999\n if (y < 100 && y >= 0) {\n // preserve leap years using a full 400 year cycle, then reset\n date = new Date(y + 400, m, d, h, M, s, ms);\n if (isFinite(date.getFullYear())) {\n date.setFullYear(y);\n }\n } else {\n date = new Date(y, m, d, h, M, s, ms);\n }\n\n return date;\n }\n\n function createUTCDate(y) {\n var date, args;\n // the Date.UTC function remaps years 0-99 to 1900-1999\n if (y < 100 && y >= 0) {\n args = Array.prototype.slice.call(arguments);\n // preserve leap years using a full 400 year cycle, then reset\n args[0] = y + 400;\n date = new Date(Date.UTC.apply(null, args));\n if (isFinite(date.getUTCFullYear())) {\n date.setUTCFullYear(y);\n }\n } else {\n date = new Date(Date.UTC.apply(null, arguments));\n }\n\n return date;\n }\n\n // start-of-first-week - start-of-year\n function firstWeekOffset(year, dow, doy) {\n var // first-week day -- which january is always in the first week (4 for iso, 1 for other)\n fwd = 7 + dow - doy,\n // first-week day local weekday -- which local weekday is fwd\n fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;\n\n return -fwdlw + fwd - 1;\n }\n\n // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday\n function dayOfYearFromWeeks(year, week, weekday, dow, doy) {\n var localWeekday = (7 + weekday - dow) % 7,\n weekOffset = firstWeekOffset(year, dow, doy),\n dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,\n resYear,\n resDayOfYear;\n\n if (dayOfYear <= 0) {\n resYear = year - 1;\n resDayOfYear = daysInYear(resYear) + dayOfYear;\n } else if (dayOfYear > daysInYear(year)) {\n resYear = year + 1;\n resDayOfYear = dayOfYear - daysInYear(year);\n } else {\n resYear = year;\n resDayOfYear = dayOfYear;\n }\n\n return {\n year: resYear,\n dayOfYear: resDayOfYear,\n };\n }\n\n function weekOfYear(mom, dow, doy) {\n var weekOffset = firstWeekOffset(mom.year(), dow, doy),\n week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,\n resWeek,\n resYear;\n\n if (week < 1) {\n resYear = mom.year() - 1;\n resWeek = week + weeksInYear(resYear, dow, doy);\n } else if (week > weeksInYear(mom.year(), dow, doy)) {\n resWeek = week - weeksInYear(mom.year(), dow, doy);\n resYear = mom.year() + 1;\n } else {\n resYear = mom.year();\n resWeek = week;\n }\n\n return {\n week: resWeek,\n year: resYear,\n };\n }\n\n function weeksInYear(year, dow, doy) {\n var weekOffset = firstWeekOffset(year, dow, doy),\n weekOffsetNext = firstWeekOffset(year + 1, dow, doy);\n return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;\n }\n\n // FORMATTING\n\n addFormatToken('w', ['ww', 2], 'wo', 'week');\n addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');\n\n // ALIASES\n\n addUnitAlias('week', 'w');\n addUnitAlias('isoWeek', 'W');\n\n // PRIORITIES\n\n addUnitPriority('week', 5);\n addUnitPriority('isoWeek', 5);\n\n // PARSING\n\n addRegexToken('w', match1to2);\n addRegexToken('ww', match1to2, match2);\n addRegexToken('W', match1to2);\n addRegexToken('WW', match1to2, match2);\n\n addWeekParseToken(\n ['w', 'ww', 'W', 'WW'],\n function (input, week, config, token) {\n week[token.substr(0, 1)] = toInt(input);\n }\n );\n\n // HELPERS\n\n // LOCALES\n\n function localeWeek(mom) {\n return weekOfYear(mom, this._week.dow, this._week.doy).week;\n }\n\n var defaultLocaleWeek = {\n dow: 0, // Sunday is the first day of the week.\n doy: 6, // The week that contains Jan 6th is the first week of the year.\n };\n\n function localeFirstDayOfWeek() {\n return this._week.dow;\n }\n\n function localeFirstDayOfYear() {\n return this._week.doy;\n }\n\n // MOMENTS\n\n function getSetWeek(input) {\n var week = this.localeData().week(this);\n return input == null ? week : this.add((input - week) * 7, 'd');\n }\n\n function getSetISOWeek(input) {\n var week = weekOfYear(this, 1, 4).week;\n return input == null ? week : this.add((input - week) * 7, 'd');\n }\n\n // FORMATTING\n\n addFormatToken('d', 0, 'do', 'day');\n\n addFormatToken('dd', 0, 0, function (format) {\n return this.localeData().weekdaysMin(this, format);\n });\n\n addFormatToken('ddd', 0, 0, function (format) {\n return this.localeData().weekdaysShort(this, format);\n });\n\n addFormatToken('dddd', 0, 0, function (format) {\n return this.localeData().weekdays(this, format);\n });\n\n addFormatToken('e', 0, 0, 'weekday');\n addFormatToken('E', 0, 0, 'isoWeekday');\n\n // ALIASES\n\n addUnitAlias('day', 'd');\n addUnitAlias('weekday', 'e');\n addUnitAlias('isoWeekday', 'E');\n\n // PRIORITY\n addUnitPriority('day', 11);\n addUnitPriority('weekday', 11);\n addUnitPriority('isoWeekday', 11);\n\n // PARSING\n\n addRegexToken('d', match1to2);\n addRegexToken('e', match1to2);\n addRegexToken('E', match1to2);\n addRegexToken('dd', function (isStrict, locale) {\n return locale.weekdaysMinRegex(isStrict);\n });\n addRegexToken('ddd', function (isStrict, locale) {\n return locale.weekdaysShortRegex(isStrict);\n });\n addRegexToken('dddd', function (isStrict, locale) {\n return locale.weekdaysRegex(isStrict);\n });\n\n addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {\n var weekday = config._locale.weekdaysParse(input, token, config._strict);\n // if we didn't get a weekday name, mark the date as invalid\n if (weekday != null) {\n week.d = weekday;\n } else {\n getParsingFlags(config).invalidWeekday = input;\n }\n });\n\n addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {\n week[token] = toInt(input);\n });\n\n // HELPERS\n\n function parseWeekday(input, locale) {\n if (typeof input !== 'string') {\n return input;\n }\n\n if (!isNaN(input)) {\n return parseInt(input, 10);\n }\n\n input = locale.weekdaysParse(input);\n if (typeof input === 'number') {\n return input;\n }\n\n return null;\n }\n\n function parseIsoWeekday(input, locale) {\n if (typeof input === 'string') {\n return locale.weekdaysParse(input) % 7 || 7;\n }\n return isNaN(input) ? null : input;\n }\n\n // LOCALES\n function shiftWeekdays(ws, n) {\n return ws.slice(n, 7).concat(ws.slice(0, n));\n }\n\n var defaultLocaleWeekdays =\n 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),\n defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),\n defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),\n defaultWeekdaysRegex = matchWord,\n defaultWeekdaysShortRegex = matchWord,\n defaultWeekdaysMinRegex = matchWord;\n\n function localeWeekdays(m, format) {\n var weekdays = isArray(this._weekdays)\n ? this._weekdays\n : this._weekdays[\n m && m !== true && this._weekdays.isFormat.test(format)\n ? 'format'\n : 'standalone'\n ];\n return m === true\n ? shiftWeekdays(weekdays, this._week.dow)\n : m\n ? weekdays[m.day()]\n : weekdays;\n }\n\n function localeWeekdaysShort(m) {\n return m === true\n ? shiftWeekdays(this._weekdaysShort, this._week.dow)\n : m\n ? this._weekdaysShort[m.day()]\n : this._weekdaysShort;\n }\n\n function localeWeekdaysMin(m) {\n return m === true\n ? shiftWeekdays(this._weekdaysMin, this._week.dow)\n : m\n ? this._weekdaysMin[m.day()]\n : this._weekdaysMin;\n }\n\n function handleStrictParse$1(weekdayName, format, strict) {\n var i,\n ii,\n mom,\n llc = weekdayName.toLocaleLowerCase();\n if (!this._weekdaysParse) {\n this._weekdaysParse = [];\n this._shortWeekdaysParse = [];\n this._minWeekdaysParse = [];\n\n for (i = 0; i < 7; ++i) {\n mom = createUTC([2000, 1]).day(i);\n this._minWeekdaysParse[i] = this.weekdaysMin(\n mom,\n ''\n ).toLocaleLowerCase();\n this._shortWeekdaysParse[i] = this.weekdaysShort(\n mom,\n ''\n ).toLocaleLowerCase();\n this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();\n }\n }\n\n if (strict) {\n if (format === 'dddd') {\n ii = indexOf.call(this._weekdaysParse, llc);\n return ii !== -1 ? ii : null;\n } else if (format === 'ddd') {\n ii = indexOf.call(this._shortWeekdaysParse, llc);\n return ii !== -1 ? ii : null;\n } else {\n ii = indexOf.call(this._minWeekdaysParse, llc);\n return ii !== -1 ? ii : null;\n }\n } else {\n if (format === 'dddd') {\n ii = indexOf.call(this._weekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._shortWeekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._minWeekdaysParse, llc);\n return ii !== -1 ? ii : null;\n } else if (format === 'ddd') {\n ii = indexOf.call(this._shortWeekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._weekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._minWeekdaysParse, llc);\n return ii !== -1 ? ii : null;\n } else {\n ii = indexOf.call(this._minWeekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._weekdaysParse, llc);\n if (ii !== -1) {\n return ii;\n }\n ii = indexOf.call(this._shortWeekdaysParse, llc);\n return ii !== -1 ? ii : null;\n }\n }\n }\n\n function localeWeekdaysParse(weekdayName, format, strict) {\n var i, mom, regex;\n\n if (this._weekdaysParseExact) {\n return handleStrictParse$1.call(this, weekdayName, format, strict);\n }\n\n if (!this._weekdaysParse) {\n this._weekdaysParse = [];\n this._minWeekdaysParse = [];\n this._shortWeekdaysParse = [];\n this._fullWeekdaysParse = [];\n }\n\n for (i = 0; i < 7; i++) {\n // make the regex if we don't have it already\n\n mom = createUTC([2000, 1]).day(i);\n if (strict && !this._fullWeekdaysParse[i]) {\n this._fullWeekdaysParse[i] = new RegExp(\n '^' + this.weekdays(mom, '').replace('.', '\\\\.?') + '$',\n 'i'\n );\n this._shortWeekdaysParse[i] = new RegExp(\n '^' + this.weekdaysShort(mom, '').replace('.', '\\\\.?') + '$',\n 'i'\n );\n this._minWeekdaysParse[i] = new RegExp(\n '^' + this.weekdaysMin(mom, '').replace('.', '\\\\.?') + '$',\n 'i'\n );\n }\n if (!this._weekdaysParse[i]) {\n regex =\n '^' +\n this.weekdays(mom, '') +\n '|^' +\n this.weekdaysShort(mom, '') +\n '|^' +\n this.weekdaysMin(mom, '');\n this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');\n }\n // test the regex\n if (\n strict &&\n format === 'dddd' &&\n this._fullWeekdaysParse[i].test(weekdayName)\n ) {\n return i;\n } else if (\n strict &&\n format === 'ddd' &&\n this._shortWeekdaysParse[i].test(weekdayName)\n ) {\n return i;\n } else if (\n strict &&\n format === 'dd' &&\n this._minWeekdaysParse[i].test(weekdayName)\n ) {\n return i;\n } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {\n return i;\n }\n }\n }\n\n // MOMENTS\n\n function getSetDayOfWeek(input) {\n if (!this.isValid()) {\n return input != null ? this : NaN;\n }\n var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();\n if (input != null) {\n input = parseWeekday(input, this.localeData());\n return this.add(input - day, 'd');\n } else {\n return day;\n }\n }\n\n function getSetLocaleDayOfWeek(input) {\n if (!this.isValid()) {\n return input != null ? this : NaN;\n }\n var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;\n return input == null ? weekday : this.add(input - weekday, 'd');\n }\n\n function getSetISODayOfWeek(input) {\n if (!this.isValid()) {\n return input != null ? this : NaN;\n }\n\n // behaves the same as moment#day except\n // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)\n // as a setter, sunday should belong to the previous week.\n\n if (input != null) {\n var weekday = parseIsoWeekday(input, this.localeData());\n return this.day(this.day() % 7 ? weekday : weekday - 7);\n } else {\n return this.day() || 7;\n }\n }\n\n function weekdaysRegex(isStrict) {\n if (this._weekdaysParseExact) {\n if (!hasOwnProp(this, '_weekdaysRegex')) {\n computeWeekdaysParse.call(this);\n }\n if (isStrict) {\n return this._weekdaysStrictRegex;\n } else {\n return this._weekdaysRegex;\n }\n } else {\n if (!hasOwnProp(this, '_weekdaysRegex')) {\n this._weekdaysRegex = defaultWeekdaysRegex;\n }\n return this._weekdaysStrictRegex && isStrict\n ? this._weekdaysStrictRegex\n : this._weekdaysRegex;\n }\n }\n\n function weekdaysShortRegex(isStrict) {\n if (this._weekdaysParseExact) {\n if (!hasOwnProp(this, '_weekdaysRegex')) {\n computeWeekdaysParse.call(this);\n }\n if (isStrict) {\n return this._weekdaysShortStrictRegex;\n } else {\n return this._weekdaysShortRegex;\n }\n } else {\n if (!hasOwnProp(this, '_weekdaysShortRegex')) {\n this._weekdaysShortRegex = defaultWeekdaysShortRegex;\n }\n return this._weekdaysShortStrictRegex && isStrict\n ? this._weekdaysShortStrictRegex\n : this._weekdaysShortRegex;\n }\n }\n\n function weekdaysMinRegex(isStrict) {\n if (this._weekdaysParseExact) {\n if (!hasOwnProp(this, '_weekdaysRegex')) {\n computeWeekdaysParse.call(this);\n }\n if (isStrict) {\n return this._weekdaysMinStrictRegex;\n } else {\n return this._weekdaysMinRegex;\n }\n } else {\n if (!hasOwnProp(this, '_weekdaysMinRegex')) {\n this._weekdaysMinRegex = defaultWeekdaysMinRegex;\n }\n return this._weekdaysMinStrictRegex && isStrict\n ? this._weekdaysMinStrictRegex\n : this._weekdaysMinRegex;\n }\n }\n\n function computeWeekdaysParse() {\n function cmpLenRev(a, b) {\n return b.length - a.length;\n }\n\n var minPieces = [],\n shortPieces = [],\n longPieces = [],\n mixedPieces = [],\n i,\n mom,\n minp,\n shortp,\n longp;\n for (i = 0; i < 7; i++) {\n // make the regex if we don't have it already\n mom = createUTC([2000, 1]).day(i);\n minp = regexEscape(this.weekdaysMin(mom, ''));\n shortp = regexEscape(this.weekdaysShort(mom, ''));\n longp = regexEscape(this.weekdays(mom, ''));\n minPieces.push(minp);\n shortPieces.push(shortp);\n longPieces.push(longp);\n mixedPieces.push(minp);\n mixedPieces.push(shortp);\n mixedPieces.push(longp);\n }\n // Sorting makes sure if one weekday (or abbr) is a prefix of another it\n // will match the longer piece.\n minPieces.sort(cmpLenRev);\n shortPieces.sort(cmpLenRev);\n longPieces.sort(cmpLenRev);\n mixedPieces.sort(cmpLenRev);\n\n this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n this._weekdaysShortRegex = this._weekdaysRegex;\n this._weekdaysMinRegex = this._weekdaysRegex;\n\n this._weekdaysStrictRegex = new RegExp(\n '^(' + longPieces.join('|') + ')',\n 'i'\n );\n this._weekdaysShortStrictRegex = new RegExp(\n '^(' + shortPieces.join('|') + ')',\n 'i'\n );\n this._weekdaysMinStrictRegex = new RegExp(\n '^(' + minPieces.join('|') + ')',\n 'i'\n );\n }\n\n // FORMATTING\n\n function hFormat() {\n return this.hours() % 12 || 12;\n }\n\n function kFormat() {\n return this.hours() || 24;\n }\n\n addFormatToken('H', ['HH', 2], 0, 'hour');\n addFormatToken('h', ['hh', 2], 0, hFormat);\n addFormatToken('k', ['kk', 2], 0, kFormat);\n\n addFormatToken('hmm', 0, 0, function () {\n return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);\n });\n\n addFormatToken('hmmss', 0, 0, function () {\n return (\n '' +\n hFormat.apply(this) +\n zeroFill(this.minutes(), 2) +\n zeroFill(this.seconds(), 2)\n );\n });\n\n addFormatToken('Hmm', 0, 0, function () {\n return '' + this.hours() + zeroFill(this.minutes(), 2);\n });\n\n addFormatToken('Hmmss', 0, 0, function () {\n return (\n '' +\n this.hours() +\n zeroFill(this.minutes(), 2) +\n zeroFill(this.seconds(), 2)\n );\n });\n\n function meridiem(token, lowercase) {\n addFormatToken(token, 0, 0, function () {\n return this.localeData().meridiem(\n this.hours(),\n this.minutes(),\n lowercase\n );\n });\n }\n\n meridiem('a', true);\n meridiem('A', false);\n\n // ALIASES\n\n addUnitAlias('hour', 'h');\n\n // PRIORITY\n addUnitPriority('hour', 13);\n\n // PARSING\n\n function matchMeridiem(isStrict, locale) {\n return locale._meridiemParse;\n }\n\n addRegexToken('a', matchMeridiem);\n addRegexToken('A', matchMeridiem);\n addRegexToken('H', match1to2);\n addRegexToken('h', match1to2);\n addRegexToken('k', match1to2);\n addRegexToken('HH', match1to2, match2);\n addRegexToken('hh', match1to2, match2);\n addRegexToken('kk', match1to2, match2);\n\n addRegexToken('hmm', match3to4);\n addRegexToken('hmmss', match5to6);\n addRegexToken('Hmm', match3to4);\n addRegexToken('Hmmss', match5to6);\n\n addParseToken(['H', 'HH'], HOUR);\n addParseToken(['k', 'kk'], function (input, array, config) {\n var kInput = toInt(input);\n array[HOUR] = kInput === 24 ? 0 : kInput;\n });\n addParseToken(['a', 'A'], function (input, array, config) {\n config._isPm = config._locale.isPM(input);\n config._meridiem = input;\n });\n addParseToken(['h', 'hh'], function (input, array, config) {\n array[HOUR] = toInt(input);\n getParsingFlags(config).bigHour = true;\n });\n addParseToken('hmm', function (input, array, config) {\n var pos = input.length - 2;\n array[HOUR] = toInt(input.substr(0, pos));\n array[MINUTE] = toInt(input.substr(pos));\n getParsingFlags(config).bigHour = true;\n });\n addParseToken('hmmss', function (input, array, config) {\n var pos1 = input.length - 4,\n pos2 = input.length - 2;\n array[HOUR] = toInt(input.substr(0, pos1));\n array[MINUTE] = toInt(input.substr(pos1, 2));\n array[SECOND] = toInt(input.substr(pos2));\n getParsingFlags(config).bigHour = true;\n });\n addParseToken('Hmm', function (input, array, config) {\n var pos = input.length - 2;\n array[HOUR] = toInt(input.substr(0, pos));\n array[MINUTE] = toInt(input.substr(pos));\n });\n addParseToken('Hmmss', function (input, array, config) {\n var pos1 = input.length - 4,\n pos2 = input.length - 2;\n array[HOUR] = toInt(input.substr(0, pos1));\n array[MINUTE] = toInt(input.substr(pos1, 2));\n array[SECOND] = toInt(input.substr(pos2));\n });\n\n // LOCALES\n\n function localeIsPM(input) {\n // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays\n // Using charAt should be more compatible.\n return (input + '').toLowerCase().charAt(0) === 'p';\n }\n\n var defaultLocaleMeridiemParse = /[ap]\\.?m?\\.?/i,\n // Setting the hour should keep the time, because the user explicitly\n // specified which hour they want. So trying to maintain the same hour (in\n // a new timezone) makes sense. Adding/subtracting hours does not follow\n // this rule.\n getSetHour = makeGetSet('Hours', true);\n\n function localeMeridiem(hours, minutes, isLower) {\n if (hours > 11) {\n return isLower ? 'pm' : 'PM';\n } else {\n return isLower ? 'am' : 'AM';\n }\n }\n\n var baseConfig = {\n calendar: defaultCalendar,\n longDateFormat: defaultLongDateFormat,\n invalidDate: defaultInvalidDate,\n ordinal: defaultOrdinal,\n dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,\n relativeTime: defaultRelativeTime,\n\n months: defaultLocaleMonths,\n monthsShort: defaultLocaleMonthsShort,\n\n week: defaultLocaleWeek,\n\n weekdays: defaultLocaleWeekdays,\n weekdaysMin: defaultLocaleWeekdaysMin,\n weekdaysShort: defaultLocaleWeekdaysShort,\n\n meridiemParse: defaultLocaleMeridiemParse,\n };\n\n // internal storage for locale config files\n var locales = {},\n localeFamilies = {},\n globalLocale;\n\n function commonPrefix(arr1, arr2) {\n var i,\n minl = Math.min(arr1.length, arr2.length);\n for (i = 0; i < minl; i += 1) {\n if (arr1[i] !== arr2[i]) {\n return i;\n }\n }\n return minl;\n }\n\n function normalizeLocale(key) {\n return key ? key.toLowerCase().replace('_', '-') : key;\n }\n\n // pick the locale from the array\n // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each\n // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root\n function chooseLocale(names) {\n var i = 0,\n j,\n next,\n locale,\n split;\n\n while (i < names.length) {\n split = normalizeLocale(names[i]).split('-');\n j = split.length;\n next = normalizeLocale(names[i + 1]);\n next = next ? next.split('-') : null;\n while (j > 0) {\n locale = loadLocale(split.slice(0, j).join('-'));\n if (locale) {\n return locale;\n }\n if (\n next &&\n next.length >= j &&\n commonPrefix(split, next) >= j - 1\n ) {\n //the next array item is better than a shallower substring of this one\n break;\n }\n j--;\n }\n i++;\n }\n return globalLocale;\n }\n\n function isLocaleNameSane(name) {\n // Prevent names that look like filesystem paths, i.e contain '/' or '\\'\n return name.match('^[^/\\\\\\\\]*$') != null;\n }\n\n function loadLocale(name) {\n var oldLocale = null,\n aliasedRequire;\n // TODO: Find a better way to register and load all the locales in Node\n if (\n locales[name] === undefined &&\n typeof module !== 'undefined' &&\n module &&\n module.exports &&\n isLocaleNameSane(name)\n ) {\n try {\n oldLocale = globalLocale._abbr;\n aliasedRequire = require;\n aliasedRequire('./locale/' + name);\n getSetGlobalLocale(oldLocale);\n } catch (e) {\n // mark as not found to avoid repeating expensive file require call causing high CPU\n // when trying to find en-US, en_US, en-us for every format call\n locales[name] = null; // null means not found\n }\n }\n return locales[name];\n }\n\n // This function will load locale and then set the global locale. If\n // no arguments are passed in, it will simply return the current global\n // locale key.\n function getSetGlobalLocale(key, values) {\n var data;\n if (key) {\n if (isUndefined(values)) {\n data = getLocale(key);\n } else {\n data = defineLocale(key, values);\n }\n\n if (data) {\n // moment.duration._locale = moment._locale = data;\n globalLocale = data;\n } else {\n if (typeof console !== 'undefined' && console.warn) {\n //warn user if arguments are passed but the locale could not be set\n console.warn(\n 'Locale ' + key + ' not found. Did you forget to load it?'\n );\n }\n }\n }\n\n return globalLocale._abbr;\n }\n\n function defineLocale(name, config) {\n if (config !== null) {\n var locale,\n parentConfig = baseConfig;\n config.abbr = name;\n if (locales[name] != null) {\n deprecateSimple(\n 'defineLocaleOverride',\n 'use moment.updateLocale(localeName, config) to change ' +\n 'an existing locale. moment.defineLocale(localeName, ' +\n 'config) should only be used for creating a new locale ' +\n 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'\n );\n parentConfig = locales[name]._config;\n } else if (config.parentLocale != null) {\n if (locales[config.parentLocale] != null) {\n parentConfig = locales[config.parentLocale]._config;\n } else {\n locale = loadLocale(config.parentLocale);\n if (locale != null) {\n parentConfig = locale._config;\n } else {\n if (!localeFamilies[config.parentLocale]) {\n localeFamilies[config.parentLocale] = [];\n }\n localeFamilies[config.parentLocale].push({\n name: name,\n config: config,\n });\n return null;\n }\n }\n }\n locales[name] = new Locale(mergeConfigs(parentConfig, config));\n\n if (localeFamilies[name]) {\n localeFamilies[name].forEach(function (x) {\n defineLocale(x.name, x.config);\n });\n }\n\n // backwards compat for now: also set the locale\n // make sure we set the locale AFTER all child locales have been\n // created, so we won't end up with the child locale set.\n getSetGlobalLocale(name);\n\n return locales[name];\n } else {\n // useful for testing\n delete locales[name];\n return null;\n }\n }\n\n function updateLocale(name, config) {\n if (config != null) {\n var locale,\n tmpLocale,\n parentConfig = baseConfig;\n\n if (locales[name] != null && locales[name].parentLocale != null) {\n // Update existing child locale in-place to avoid memory-leaks\n locales[name].set(mergeConfigs(locales[name]._config, config));\n } else {\n // MERGE\n tmpLocale = loadLocale(name);\n if (tmpLocale != null) {\n parentConfig = tmpLocale._config;\n }\n config = mergeConfigs(parentConfig, config);\n if (tmpLocale == null) {\n // updateLocale is called for creating a new locale\n // Set abbr so it will have a name (getters return\n // undefined otherwise).\n config.abbr = name;\n }\n locale = new Locale(config);\n locale.parentLocale = locales[name];\n locales[name] = locale;\n }\n\n // backwards compat for now: also set the locale\n getSetGlobalLocale(name);\n } else {\n // pass null for config to unupdate, useful for tests\n if (locales[name] != null) {\n if (locales[name].parentLocale != null) {\n locales[name] = locales[name].parentLocale;\n if (name === getSetGlobalLocale()) {\n getSetGlobalLocale(name);\n }\n } else if (locales[name] != null) {\n delete locales[name];\n }\n }\n }\n return locales[name];\n }\n\n // returns locale data\n function getLocale(key) {\n var locale;\n\n if (key && key._locale && key._locale._abbr) {\n key = key._locale._abbr;\n }\n\n if (!key) {\n return globalLocale;\n }\n\n if (!isArray(key)) {\n //short-circuit everything else\n locale = loadLocale(key);\n if (locale) {\n return locale;\n }\n key = [key];\n }\n\n return chooseLocale(key);\n }\n\n function listLocales() {\n return keys(locales);\n }\n\n function checkOverflow(m) {\n var overflow,\n a = m._a;\n\n if (a && getParsingFlags(m).overflow === -2) {\n overflow =\n a[MONTH] < 0 || a[MONTH] > 11\n ? MONTH\n : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])\n ? DATE\n : a[HOUR] < 0 ||\n a[HOUR] > 24 ||\n (a[HOUR] === 24 &&\n (a[MINUTE] !== 0 ||\n a[SECOND] !== 0 ||\n a[MILLISECOND] !== 0))\n ? HOUR\n : a[MINUTE] < 0 || a[MINUTE] > 59\n ? MINUTE\n : a[SECOND] < 0 || a[SECOND] > 59\n ? SECOND\n : a[MILLISECOND] < 0 || a[MILLISECOND] > 999\n ? MILLISECOND\n : -1;\n\n if (\n getParsingFlags(m)._overflowDayOfYear &&\n (overflow < YEAR || overflow > DATE)\n ) {\n overflow = DATE;\n }\n if (getParsingFlags(m)._overflowWeeks && overflow === -1) {\n overflow = WEEK;\n }\n if (getParsingFlags(m)._overflowWeekday && overflow === -1) {\n overflow = WEEKDAY;\n }\n\n getParsingFlags(m).overflow = overflow;\n }\n\n return m;\n }\n\n // iso 8601 regex\n // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)\n var extendedIsoRegex =\n /^\\s*((?:[+-]\\d{6}|\\d{4})-(?:\\d\\d-\\d\\d|W\\d\\d-\\d|W\\d\\d|\\d\\d\\d|\\d\\d))(?:(T| )(\\d\\d(?::\\d\\d(?::\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n basicIsoRegex =\n /^\\s*((?:[+-]\\d{6}|\\d{4})(?:\\d\\d\\d\\d|W\\d\\d\\d|W\\d\\d|\\d\\d\\d|\\d\\d|))(?:(T| )(\\d\\d(?:\\d\\d(?:\\d\\d(?:[.,]\\d+)?)?)?)([+-]\\d\\d(?::?\\d\\d)?|\\s*Z)?)?$/,\n tzRegex = /Z|[+-]\\d\\d(?::?\\d\\d)?/,\n isoDates = [\n ['YYYYYY-MM-DD', /[+-]\\d{6}-\\d\\d-\\d\\d/],\n ['YYYY-MM-DD', /\\d{4}-\\d\\d-\\d\\d/],\n ['GGGG-[W]WW-E', /\\d{4}-W\\d\\d-\\d/],\n ['GGGG-[W]WW', /\\d{4}-W\\d\\d/, false],\n ['YYYY-DDD', /\\d{4}-\\d{3}/],\n ['YYYY-MM', /\\d{4}-\\d\\d/, false],\n ['YYYYYYMMDD', /[+-]\\d{10}/],\n ['YYYYMMDD', /\\d{8}/],\n ['GGGG[W]WWE', /\\d{4}W\\d{3}/],\n ['GGGG[W]WW', /\\d{4}W\\d{2}/, false],\n ['YYYYDDD', /\\d{7}/],\n ['YYYYMM', /\\d{6}/, false],\n ['YYYY', /\\d{4}/, false],\n ],\n // iso time formats and regexes\n isoTimes = [\n ['HH:mm:ss.SSSS', /\\d\\d:\\d\\d:\\d\\d\\.\\d+/],\n ['HH:mm:ss,SSSS', /\\d\\d:\\d\\d:\\d\\d,\\d+/],\n ['HH:mm:ss', /\\d\\d:\\d\\d:\\d\\d/],\n ['HH:mm', /\\d\\d:\\d\\d/],\n ['HHmmss.SSSS', /\\d\\d\\d\\d\\d\\d\\.\\d+/],\n ['HHmmss,SSSS', /\\d\\d\\d\\d\\d\\d,\\d+/],\n ['HHmmss', /\\d\\d\\d\\d\\d\\d/],\n ['HHmm', /\\d\\d\\d\\d/],\n ['HH', /\\d\\d/],\n ],\n aspNetJsonRegex = /^\\/?Date\\((-?\\d+)/i,\n // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3\n rfc2822 =\n /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\\s)?(\\d{1,2})\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s(\\d{2,4})\\s(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\\d{4}))$/,\n obsOffsets = {\n UT: 0,\n GMT: 0,\n EDT: -4 * 60,\n EST: -5 * 60,\n CDT: -5 * 60,\n CST: -6 * 60,\n MDT: -6 * 60,\n MST: -7 * 60,\n PDT: -7 * 60,\n PST: -8 * 60,\n };\n\n // date from iso format\n function configFromISO(config) {\n var i,\n l,\n string = config._i,\n match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),\n allowTime,\n dateFormat,\n timeFormat,\n tzFormat,\n isoDatesLen = isoDates.length,\n isoTimesLen = isoTimes.length;\n\n if (match) {\n getParsingFlags(config).iso = true;\n for (i = 0, l = isoDatesLen; i < l; i++) {\n if (isoDates[i][1].exec(match[1])) {\n dateFormat = isoDates[i][0];\n allowTime = isoDates[i][2] !== false;\n break;\n }\n }\n if (dateFormat == null) {\n config._isValid = false;\n return;\n }\n if (match[3]) {\n for (i = 0, l = isoTimesLen; i < l; i++) {\n if (isoTimes[i][1].exec(match[3])) {\n // match[2] should be 'T' or space\n timeFormat = (match[2] || ' ') + isoTimes[i][0];\n break;\n }\n }\n if (timeFormat == null) {\n config._isValid = false;\n return;\n }\n }\n if (!allowTime && timeFormat != null) {\n config._isValid = false;\n return;\n }\n if (match[4]) {\n if (tzRegex.exec(match[4])) {\n tzFormat = 'Z';\n } else {\n config._isValid = false;\n return;\n }\n }\n config._f = dateFormat + (timeFormat || '') + (tzFormat || '');\n configFromStringAndFormat(config);\n } else {\n config._isValid = false;\n }\n }\n\n function extractFromRFC2822Strings(\n yearStr,\n monthStr,\n dayStr,\n hourStr,\n minuteStr,\n secondStr\n ) {\n var result = [\n untruncateYear(yearStr),\n defaultLocaleMonthsShort.indexOf(monthStr),\n parseInt(dayStr, 10),\n parseInt(hourStr, 10),\n parseInt(minuteStr, 10),\n ];\n\n if (secondStr) {\n result.push(parseInt(secondStr, 10));\n }\n\n return result;\n }\n\n function untruncateYear(yearStr) {\n var year = parseInt(yearStr, 10);\n if (year <= 49) {\n return 2000 + year;\n } else if (year <= 999) {\n return 1900 + year;\n }\n return year;\n }\n\n function preprocessRFC2822(s) {\n // Remove comments and folding whitespace and replace multiple-spaces with a single space\n return s\n .replace(/\\([^()]*\\)|[\\n\\t]/g, ' ')\n .replace(/(\\s\\s+)/g, ' ')\n .replace(/^\\s\\s*/, '')\n .replace(/\\s\\s*$/, '');\n }\n\n function checkWeekday(weekdayStr, parsedInput, config) {\n if (weekdayStr) {\n // TODO: Replace the vanilla JS Date object with an independent day-of-week check.\n var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),\n weekdayActual = new Date(\n parsedInput[0],\n parsedInput[1],\n parsedInput[2]\n ).getDay();\n if (weekdayProvided !== weekdayActual) {\n getParsingFlags(config).weekdayMismatch = true;\n config._isValid = false;\n return false;\n }\n }\n return true;\n }\n\n function calculateOffset(obsOffset, militaryOffset, numOffset) {\n if (obsOffset) {\n return obsOffsets[obsOffset];\n } else if (militaryOffset) {\n // the only allowed military tz is Z\n return 0;\n } else {\n var hm = parseInt(numOffset, 10),\n m = hm % 100,\n h = (hm - m) / 100;\n return h * 60 + m;\n }\n }\n\n // date and time from ref 2822 format\n function configFromRFC2822(config) {\n var match = rfc2822.exec(preprocessRFC2822(config._i)),\n parsedArray;\n if (match) {\n parsedArray = extractFromRFC2822Strings(\n match[4],\n match[3],\n match[2],\n match[5],\n match[6],\n match[7]\n );\n if (!checkWeekday(match[1], parsedArray, config)) {\n return;\n }\n\n config._a = parsedArray;\n config._tzm = calculateOffset(match[8], match[9], match[10]);\n\n config._d = createUTCDate.apply(null, config._a);\n config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n\n getParsingFlags(config).rfc2822 = true;\n } else {\n config._isValid = false;\n }\n }\n\n // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict\n function configFromString(config) {\n var matched = aspNetJsonRegex.exec(config._i);\n if (matched !== null) {\n config._d = new Date(+matched[1]);\n return;\n }\n\n configFromISO(config);\n if (config._isValid === false) {\n delete config._isValid;\n } else {\n return;\n }\n\n configFromRFC2822(config);\n if (config._isValid === false) {\n delete config._isValid;\n } else {\n return;\n }\n\n if (config._strict) {\n config._isValid = false;\n } else {\n // Final attempt, use Input Fallback\n hooks.createFromInputFallback(config);\n }\n }\n\n hooks.createFromInputFallback = deprecate(\n 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +\n 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +\n 'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',\n function (config) {\n config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));\n }\n );\n\n // Pick the first defined of two or three arguments.\n function defaults(a, b, c) {\n if (a != null) {\n return a;\n }\n if (b != null) {\n return b;\n }\n return c;\n }\n\n function currentDateArray(config) {\n // hooks is actually the exported moment object\n var nowValue = new Date(hooks.now());\n if (config._useUTC) {\n return [\n nowValue.getUTCFullYear(),\n nowValue.getUTCMonth(),\n nowValue.getUTCDate(),\n ];\n }\n return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];\n }\n\n // convert an array to a date.\n // the array should mirror the parameters below\n // note: all values past the year are optional and will default to the lowest possible value.\n // [year, month, day , hour, minute, second, millisecond]\n function configFromArray(config) {\n var i,\n date,\n input = [],\n currentDate,\n expectedWeekday,\n yearToUse;\n\n if (config._d) {\n return;\n }\n\n currentDate = currentDateArray(config);\n\n //compute day of the year from weeks and weekdays\n if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {\n dayOfYearFromWeekInfo(config);\n }\n\n //if the day of the year is set, figure out what it is\n if (config._dayOfYear != null) {\n yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);\n\n if (\n config._dayOfYear > daysInYear(yearToUse) ||\n config._dayOfYear === 0\n ) {\n getParsingFlags(config)._overflowDayOfYear = true;\n }\n\n date = createUTCDate(yearToUse, 0, config._dayOfYear);\n config._a[MONTH] = date.getUTCMonth();\n config._a[DATE] = date.getUTCDate();\n }\n\n // Default to current date.\n // * if no year, month, day of month are given, default to today\n // * if day of month is given, default month and year\n // * if month is given, default only year\n // * if year is given, don't default anything\n for (i = 0; i < 3 && config._a[i] == null; ++i) {\n config._a[i] = input[i] = currentDate[i];\n }\n\n // Zero out whatever was not defaulted, including time\n for (; i < 7; i++) {\n config._a[i] = input[i] =\n config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];\n }\n\n // Check for 24:00:00.000\n if (\n config._a[HOUR] === 24 &&\n config._a[MINUTE] === 0 &&\n config._a[SECOND] === 0 &&\n config._a[MILLISECOND] === 0\n ) {\n config._nextDay = true;\n config._a[HOUR] = 0;\n }\n\n config._d = (config._useUTC ? createUTCDate : createDate).apply(\n null,\n input\n );\n expectedWeekday = config._useUTC\n ? config._d.getUTCDay()\n : config._d.getDay();\n\n // Apply timezone offset from input. The actual utcOffset can be changed\n // with parseZone.\n if (config._tzm != null) {\n config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);\n }\n\n if (config._nextDay) {\n config._a[HOUR] = 24;\n }\n\n // check for mismatching day of week\n if (\n config._w &&\n typeof config._w.d !== 'undefined' &&\n config._w.d !== expectedWeekday\n ) {\n getParsingFlags(config).weekdayMismatch = true;\n }\n }\n\n function dayOfYearFromWeekInfo(config) {\n var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;\n\n w = config._w;\n if (w.GG != null || w.W != null || w.E != null) {\n dow = 1;\n doy = 4;\n\n // TODO: We need to take the current isoWeekYear, but that depends on\n // how we interpret now (local, utc, fixed offset). So create\n // a now version of current config (take local/utc/offset flags, and\n // create now).\n weekYear = defaults(\n w.GG,\n config._a[YEAR],\n weekOfYear(createLocal(), 1, 4).year\n );\n week = defaults(w.W, 1);\n weekday = defaults(w.E, 1);\n if (weekday < 1 || weekday > 7) {\n weekdayOverflow = true;\n }\n } else {\n dow = config._locale._week.dow;\n doy = config._locale._week.doy;\n\n curWeek = weekOfYear(createLocal(), dow, doy);\n\n weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);\n\n // Default to current week.\n week = defaults(w.w, curWeek.week);\n\n if (w.d != null) {\n // weekday -- low day numbers are considered next week\n weekday = w.d;\n if (weekday < 0 || weekday > 6) {\n weekdayOverflow = true;\n }\n } else if (w.e != null) {\n // local weekday -- counting starts from beginning of week\n weekday = w.e + dow;\n if (w.e < 0 || w.e > 6) {\n weekdayOverflow = true;\n }\n } else {\n // default to beginning of week\n weekday = dow;\n }\n }\n if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {\n getParsingFlags(config)._overflowWeeks = true;\n } else if (weekdayOverflow != null) {\n getParsingFlags(config)._overflowWeekday = true;\n } else {\n temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);\n config._a[YEAR] = temp.year;\n config._dayOfYear = temp.dayOfYear;\n }\n }\n\n // constant that refers to the ISO standard\n hooks.ISO_8601 = function () {};\n\n // constant that refers to the RFC 2822 form\n hooks.RFC_2822 = function () {};\n\n // date from string and format string\n function configFromStringAndFormat(config) {\n // TODO: Move this to another part of the creation flow to prevent circular deps\n if (config._f === hooks.ISO_8601) {\n configFromISO(config);\n return;\n }\n if (config._f === hooks.RFC_2822) {\n configFromRFC2822(config);\n return;\n }\n config._a = [];\n getParsingFlags(config).empty = true;\n\n // This array is used to make a Date, either with `new Date` or `Date.UTC`\n var string = '' + config._i,\n i,\n parsedInput,\n tokens,\n token,\n skipped,\n stringLength = string.length,\n totalParsedInputLength = 0,\n era,\n tokenLen;\n\n tokens =\n expandFormat(config._f, config._locale).match(formattingTokens) || [];\n tokenLen = tokens.length;\n for (i = 0; i < tokenLen; i++) {\n token = tokens[i];\n parsedInput = (string.match(getParseRegexForToken(token, config)) ||\n [])[0];\n if (parsedInput) {\n skipped = string.substr(0, string.indexOf(parsedInput));\n if (skipped.length > 0) {\n getParsingFlags(config).unusedInput.push(skipped);\n }\n string = string.slice(\n string.indexOf(parsedInput) + parsedInput.length\n );\n totalParsedInputLength += parsedInput.length;\n }\n // don't parse if it's not a known token\n if (formatTokenFunctions[token]) {\n if (parsedInput) {\n getParsingFlags(config).empty = false;\n } else {\n getParsingFlags(config).unusedTokens.push(token);\n }\n addTimeToArrayFromToken(token, parsedInput, config);\n } else if (config._strict && !parsedInput) {\n getParsingFlags(config).unusedTokens.push(token);\n }\n }\n\n // add remaining unparsed input length to the string\n getParsingFlags(config).charsLeftOver =\n stringLength - totalParsedInputLength;\n if (string.length > 0) {\n getParsingFlags(config).unusedInput.push(string);\n }\n\n // clear _12h flag if hour is <= 12\n if (\n config._a[HOUR] <= 12 &&\n getParsingFlags(config).bigHour === true &&\n config._a[HOUR] > 0\n ) {\n getParsingFlags(config).bigHour = undefined;\n }\n\n getParsingFlags(config).parsedDateParts = config._a.slice(0);\n getParsingFlags(config).meridiem = config._meridiem;\n // handle meridiem\n config._a[HOUR] = meridiemFixWrap(\n config._locale,\n config._a[HOUR],\n config._meridiem\n );\n\n // handle era\n era = getParsingFlags(config).era;\n if (era !== null) {\n config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);\n }\n\n configFromArray(config);\n checkOverflow(config);\n }\n\n function meridiemFixWrap(locale, hour, meridiem) {\n var isPm;\n\n if (meridiem == null) {\n // nothing to do\n return hour;\n }\n if (locale.meridiemHour != null) {\n return locale.meridiemHour(hour, meridiem);\n } else if (locale.isPM != null) {\n // Fallback\n isPm = locale.isPM(meridiem);\n if (isPm && hour < 12) {\n hour += 12;\n }\n if (!isPm && hour === 12) {\n hour = 0;\n }\n return hour;\n } else {\n // this is not supposed to happen\n return hour;\n }\n }\n\n // date from string and array of format strings\n function configFromStringAndArray(config) {\n var tempConfig,\n bestMoment,\n scoreToBeat,\n i,\n currentScore,\n validFormatFound,\n bestFormatIsValid = false,\n configfLen = config._f.length;\n\n if (configfLen === 0) {\n getParsingFlags(config).invalidFormat = true;\n config._d = new Date(NaN);\n return;\n }\n\n for (i = 0; i < configfLen; i++) {\n currentScore = 0;\n validFormatFound = false;\n tempConfig = copyConfig({}, config);\n if (config._useUTC != null) {\n tempConfig._useUTC = config._useUTC;\n }\n tempConfig._f = config._f[i];\n configFromStringAndFormat(tempConfig);\n\n if (isValid(tempConfig)) {\n validFormatFound = true;\n }\n\n // if there is any input that was not parsed add a penalty for that format\n currentScore += getParsingFlags(tempConfig).charsLeftOver;\n\n //or tokens\n currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;\n\n getParsingFlags(tempConfig).score = currentScore;\n\n if (!bestFormatIsValid) {\n if (\n scoreToBeat == null ||\n currentScore < scoreToBeat ||\n validFormatFound\n ) {\n scoreToBeat = currentScore;\n bestMoment = tempConfig;\n if (validFormatFound) {\n bestFormatIsValid = true;\n }\n }\n } else {\n if (currentScore < scoreToBeat) {\n scoreToBeat = currentScore;\n bestMoment = tempConfig;\n }\n }\n }\n\n extend(config, bestMoment || tempConfig);\n }\n\n function configFromObject(config) {\n if (config._d) {\n return;\n }\n\n var i = normalizeObjectUnits(config._i),\n dayOrDate = i.day === undefined ? i.date : i.day;\n config._a = map(\n [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],\n function (obj) {\n return obj && parseInt(obj, 10);\n }\n );\n\n configFromArray(config);\n }\n\n function createFromConfig(config) {\n var res = new Moment(checkOverflow(prepareConfig(config)));\n if (res._nextDay) {\n // Adding is smart enough around DST\n res.add(1, 'd');\n res._nextDay = undefined;\n }\n\n return res;\n }\n\n function prepareConfig(config) {\n var input = config._i,\n format = config._f;\n\n config._locale = config._locale || getLocale(config._l);\n\n if (input === null || (format === undefined && input === '')) {\n return createInvalid({ nullInput: true });\n }\n\n if (typeof input === 'string') {\n config._i = input = config._locale.preparse(input);\n }\n\n if (isMoment(input)) {\n return new Moment(checkOverflow(input));\n } else if (isDate(input)) {\n config._d = input;\n } else if (isArray(format)) {\n configFromStringAndArray(config);\n } else if (format) {\n configFromStringAndFormat(config);\n } else {\n configFromInput(config);\n }\n\n if (!isValid(config)) {\n config._d = null;\n }\n\n return config;\n }\n\n function configFromInput(config) {\n var input = config._i;\n if (isUndefined(input)) {\n config._d = new Date(hooks.now());\n } else if (isDate(input)) {\n config._d = new Date(input.valueOf());\n } else if (typeof input === 'string') {\n configFromString(config);\n } else if (isArray(input)) {\n config._a = map(input.slice(0), function (obj) {\n return parseInt(obj, 10);\n });\n configFromArray(config);\n } else if (isObject(input)) {\n configFromObject(config);\n } else if (isNumber(input)) {\n // from milliseconds\n config._d = new Date(input);\n } else {\n hooks.createFromInputFallback(config);\n }\n }\n\n function createLocalOrUTC(input, format, locale, strict, isUTC) {\n var c = {};\n\n if (format === true || format === false) {\n strict = format;\n format = undefined;\n }\n\n if (locale === true || locale === false) {\n strict = locale;\n locale = undefined;\n }\n\n if (\n (isObject(input) && isObjectEmpty(input)) ||\n (isArray(input) && input.length === 0)\n ) {\n input = undefined;\n }\n // object construction must be done this way.\n // https://github.com/moment/moment/issues/1423\n c._isAMomentObject = true;\n c._useUTC = c._isUTC = isUTC;\n c._l = locale;\n c._i = input;\n c._f = format;\n c._strict = strict;\n\n return createFromConfig(c);\n }\n\n function createLocal(input, format, locale, strict) {\n return createLocalOrUTC(input, format, locale, strict, false);\n }\n\n var prototypeMin = deprecate(\n 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',\n function () {\n var other = createLocal.apply(null, arguments);\n if (this.isValid() && other.isValid()) {\n return other < this ? this : other;\n } else {\n return createInvalid();\n }\n }\n ),\n prototypeMax = deprecate(\n 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',\n function () {\n var other = createLocal.apply(null, arguments);\n if (this.isValid() && other.isValid()) {\n return other > this ? this : other;\n } else {\n return createInvalid();\n }\n }\n );\n\n // Pick a moment m from moments so that m[fn](other) is true for all\n // other. This relies on the function fn to be transitive.\n //\n // moments should either be an array of moment objects or an array, whose\n // first element is an array of moment objects.\n function pickBy(fn, moments) {\n var res, i;\n if (moments.length === 1 && isArray(moments[0])) {\n moments = moments[0];\n }\n if (!moments.length) {\n return createLocal();\n }\n res = moments[0];\n for (i = 1; i < moments.length; ++i) {\n if (!moments[i].isValid() || moments[i][fn](res)) {\n res = moments[i];\n }\n }\n return res;\n }\n\n // TODO: Use [].sort instead?\n function min() {\n var args = [].slice.call(arguments, 0);\n\n return pickBy('isBefore', args);\n }\n\n function max() {\n var args = [].slice.call(arguments, 0);\n\n return pickBy('isAfter', args);\n }\n\n var now = function () {\n return Date.now ? Date.now() : +new Date();\n };\n\n var ordering = [\n 'year',\n 'quarter',\n 'month',\n 'week',\n 'day',\n 'hour',\n 'minute',\n 'second',\n 'millisecond',\n ];\n\n function isDurationValid(m) {\n var key,\n unitHasDecimal = false,\n i,\n orderLen = ordering.length;\n for (key in m) {\n if (\n hasOwnProp(m, key) &&\n !(\n indexOf.call(ordering, key) !== -1 &&\n (m[key] == null || !isNaN(m[key]))\n )\n ) {\n return false;\n }\n }\n\n for (i = 0; i < orderLen; ++i) {\n if (m[ordering[i]]) {\n if (unitHasDecimal) {\n return false; // only allow non-integers for smallest unit\n }\n if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {\n unitHasDecimal = true;\n }\n }\n }\n\n return true;\n }\n\n function isValid$1() {\n return this._isValid;\n }\n\n function createInvalid$1() {\n return createDuration(NaN);\n }\n\n function Duration(duration) {\n var normalizedInput = normalizeObjectUnits(duration),\n years = normalizedInput.year || 0,\n quarters = normalizedInput.quarter || 0,\n months = normalizedInput.month || 0,\n weeks = normalizedInput.week || normalizedInput.isoWeek || 0,\n days = normalizedInput.day || 0,\n hours = normalizedInput.hour || 0,\n minutes = normalizedInput.minute || 0,\n seconds = normalizedInput.second || 0,\n milliseconds = normalizedInput.millisecond || 0;\n\n this._isValid = isDurationValid(normalizedInput);\n\n // representation for dateAddRemove\n this._milliseconds =\n +milliseconds +\n seconds * 1e3 + // 1000\n minutes * 6e4 + // 1000 * 60\n hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978\n // Because of dateAddRemove treats 24 hours as different from a\n // day when working around DST, we need to store them separately\n this._days = +days + weeks * 7;\n // It is impossible to translate months into days without knowing\n // which months you are are talking about, so we have to store\n // it separately.\n this._months = +months + quarters * 3 + years * 12;\n\n this._data = {};\n\n this._locale = getLocale();\n\n this._bubble();\n }\n\n function isDuration(obj) {\n return obj instanceof Duration;\n }\n\n function absRound(number) {\n if (number < 0) {\n return Math.round(-1 * number) * -1;\n } else {\n return Math.round(number);\n }\n }\n\n // compare two arrays, return the number of differences\n function compareArrays(array1, array2, dontConvert) {\n var len = Math.min(array1.length, array2.length),\n lengthDiff = Math.abs(array1.length - array2.length),\n diffs = 0,\n i;\n for (i = 0; i < len; i++) {\n if (\n (dontConvert && array1[i] !== array2[i]) ||\n (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))\n ) {\n diffs++;\n }\n }\n return diffs + lengthDiff;\n }\n\n // FORMATTING\n\n function offset(token, separator) {\n addFormatToken(token, 0, 0, function () {\n var offset = this.utcOffset(),\n sign = '+';\n if (offset < 0) {\n offset = -offset;\n sign = '-';\n }\n return (\n sign +\n zeroFill(~~(offset / 60), 2) +\n separator +\n zeroFill(~~offset % 60, 2)\n );\n });\n }\n\n offset('Z', ':');\n offset('ZZ', '');\n\n // PARSING\n\n addRegexToken('Z', matchShortOffset);\n addRegexToken('ZZ', matchShortOffset);\n addParseToken(['Z', 'ZZ'], function (input, array, config) {\n config._useUTC = true;\n config._tzm = offsetFromString(matchShortOffset, input);\n });\n\n // HELPERS\n\n // timezone chunker\n // '+10:00' > ['10', '00']\n // '-1530' > ['-15', '30']\n var chunkOffset = /([\\+\\-]|\\d\\d)/gi;\n\n function offsetFromString(matcher, string) {\n var matches = (string || '').match(matcher),\n chunk,\n parts,\n minutes;\n\n if (matches === null) {\n return null;\n }\n\n chunk = matches[matches.length - 1] || [];\n parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];\n minutes = +(parts[1] * 60) + toInt(parts[2]);\n\n return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;\n }\n\n // Return a moment from input, that is local/utc/zone equivalent to model.\n function cloneWithOffset(input, model) {\n var res, diff;\n if (model._isUTC) {\n res = model.clone();\n diff =\n (isMoment(input) || isDate(input)\n ? input.valueOf()\n : createLocal(input).valueOf()) - res.valueOf();\n // Use low-level api, because this fn is low-level api.\n res._d.setTime(res._d.valueOf() + diff);\n hooks.updateOffset(res, false);\n return res;\n } else {\n return createLocal(input).local();\n }\n }\n\n function getDateOffset(m) {\n // On Firefox.24 Date#getTimezoneOffset returns a floating point.\n // https://github.com/moment/moment/pull/1871\n return -Math.round(m._d.getTimezoneOffset());\n }\n\n // HOOKS\n\n // This function will be called whenever a moment is mutated.\n // It is intended to keep the offset in sync with the timezone.\n hooks.updateOffset = function () {};\n\n // MOMENTS\n\n // keepLocalTime = true means only change the timezone, without\n // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->\n // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset\n // +0200, so we adjust the time as needed, to be valid.\n //\n // Keeping the time actually adds/subtracts (one hour)\n // from the actual represented time. That is why we call updateOffset\n // a second time. In case it wants us to change the offset again\n // _changeInProgress == true case, then we have to adjust, because\n // there is no such time in the given timezone.\n function getSetOffset(input, keepLocalTime, keepMinutes) {\n var offset = this._offset || 0,\n localAdjust;\n if (!this.isValid()) {\n return input != null ? this : NaN;\n }\n if (input != null) {\n if (typeof input === 'string') {\n input = offsetFromString(matchShortOffset, input);\n if (input === null) {\n return this;\n }\n } else if (Math.abs(input) < 16 && !keepMinutes) {\n input = input * 60;\n }\n if (!this._isUTC && keepLocalTime) {\n localAdjust = getDateOffset(this);\n }\n this._offset = input;\n this._isUTC = true;\n if (localAdjust != null) {\n this.add(localAdjust, 'm');\n }\n if (offset !== input) {\n if (!keepLocalTime || this._changeInProgress) {\n addSubtract(\n this,\n createDuration(input - offset, 'm'),\n 1,\n false\n );\n } else if (!this._changeInProgress) {\n this._changeInProgress = true;\n hooks.updateOffset(this, true);\n this._changeInProgress = null;\n }\n }\n return this;\n } else {\n return this._isUTC ? offset : getDateOffset(this);\n }\n }\n\n function getSetZone(input, keepLocalTime) {\n if (input != null) {\n if (typeof input !== 'string') {\n input = -input;\n }\n\n this.utcOffset(input, keepLocalTime);\n\n return this;\n } else {\n return -this.utcOffset();\n }\n }\n\n function setOffsetToUTC(keepLocalTime) {\n return this.utcOffset(0, keepLocalTime);\n }\n\n function setOffsetToLocal(keepLocalTime) {\n if (this._isUTC) {\n this.utcOffset(0, keepLocalTime);\n this._isUTC = false;\n\n if (keepLocalTime) {\n this.subtract(getDateOffset(this), 'm');\n }\n }\n return this;\n }\n\n function setOffsetToParsedOffset() {\n if (this._tzm != null) {\n this.utcOffset(this._tzm, false, true);\n } else if (typeof this._i === 'string') {\n var tZone = offsetFromString(matchOffset, this._i);\n if (tZone != null) {\n this.utcOffset(tZone);\n } else {\n this.utcOffset(0, true);\n }\n }\n return this;\n }\n\n function hasAlignedHourOffset(input) {\n if (!this.isValid()) {\n return false;\n }\n input = input ? createLocal(input).utcOffset() : 0;\n\n return (this.utcOffset() - input) % 60 === 0;\n }\n\n function isDaylightSavingTime() {\n return (\n this.utcOffset() > this.clone().month(0).utcOffset() ||\n this.utcOffset() > this.clone().month(5).utcOffset()\n );\n }\n\n function isDaylightSavingTimeShifted() {\n if (!isUndefined(this._isDSTShifted)) {\n return this._isDSTShifted;\n }\n\n var c = {},\n other;\n\n copyConfig(c, this);\n c = prepareConfig(c);\n\n if (c._a) {\n other = c._isUTC ? createUTC(c._a) : createLocal(c._a);\n this._isDSTShifted =\n this.isValid() && compareArrays(c._a, other.toArray()) > 0;\n } else {\n this._isDSTShifted = false;\n }\n\n return this._isDSTShifted;\n }\n\n function isLocal() {\n return this.isValid() ? !this._isUTC : false;\n }\n\n function isUtcOffset() {\n return this.isValid() ? this._isUTC : false;\n }\n\n function isUtc() {\n return this.isValid() ? this._isUTC && this._offset === 0 : false;\n }\n\n // ASP.NET json date format regex\n var aspNetRegex = /^(-|\\+)?(?:(\\d*)[. ])?(\\d+):(\\d+)(?::(\\d+)(\\.\\d*)?)?$/,\n // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html\n // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere\n // and further modified to allow for strings containing both week and day\n isoRegex =\n /^(-|\\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;\n\n function createDuration(input, key) {\n var duration = input,\n // matching against regexp is expensive, do it on demand\n match = null,\n sign,\n ret,\n diffRes;\n\n if (isDuration(input)) {\n duration = {\n ms: input._milliseconds,\n d: input._days,\n M: input._months,\n };\n } else if (isNumber(input) || !isNaN(+input)) {\n duration = {};\n if (key) {\n duration[key] = +input;\n } else {\n duration.milliseconds = +input;\n }\n } else if ((match = aspNetRegex.exec(input))) {\n sign = match[1] === '-' ? -1 : 1;\n duration = {\n y: 0,\n d: toInt(match[DATE]) * sign,\n h: toInt(match[HOUR]) * sign,\n m: toInt(match[MINUTE]) * sign,\n s: toInt(match[SECOND]) * sign,\n ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match\n };\n } else if ((match = isoRegex.exec(input))) {\n sign = match[1] === '-' ? -1 : 1;\n duration = {\n y: parseIso(match[2], sign),\n M: parseIso(match[3], sign),\n w: parseIso(match[4], sign),\n d: parseIso(match[5], sign),\n h: parseIso(match[6], sign),\n m: parseIso(match[7], sign),\n s: parseIso(match[8], sign),\n };\n } else if (duration == null) {\n // checks for null or undefined\n duration = {};\n } else if (\n typeof duration === 'object' &&\n ('from' in duration || 'to' in duration)\n ) {\n diffRes = momentsDifference(\n createLocal(duration.from),\n createLocal(duration.to)\n );\n\n duration = {};\n duration.ms = diffRes.milliseconds;\n duration.M = diffRes.months;\n }\n\n ret = new Duration(duration);\n\n if (isDuration(input) && hasOwnProp(input, '_locale')) {\n ret._locale = input._locale;\n }\n\n if (isDuration(input) && hasOwnProp(input, '_isValid')) {\n ret._isValid = input._isValid;\n }\n\n return ret;\n }\n\n createDuration.fn = Duration.prototype;\n createDuration.invalid = createInvalid$1;\n\n function parseIso(inp, sign) {\n // We'd normally use ~~inp for this, but unfortunately it also\n // converts floats to ints.\n // inp may be undefined, so careful calling replace on it.\n var res = inp && parseFloat(inp.replace(',', '.'));\n // apply sign while we're at it\n return (isNaN(res) ? 0 : res) * sign;\n }\n\n function positiveMomentsDifference(base, other) {\n var res = {};\n\n res.months =\n other.month() - base.month() + (other.year() - base.year()) * 12;\n if (base.clone().add(res.months, 'M').isAfter(other)) {\n --res.months;\n }\n\n res.milliseconds = +other - +base.clone().add(res.months, 'M');\n\n return res;\n }\n\n function momentsDifference(base, other) {\n var res;\n if (!(base.isValid() && other.isValid())) {\n return { milliseconds: 0, months: 0 };\n }\n\n other = cloneWithOffset(other, base);\n if (base.isBefore(other)) {\n res = positiveMomentsDifference(base, other);\n } else {\n res = positiveMomentsDifference(other, base);\n res.milliseconds = -res.milliseconds;\n res.months = -res.months;\n }\n\n return res;\n }\n\n // TODO: remove 'name' arg after deprecation is removed\n function createAdder(direction, name) {\n return function (val, period) {\n var dur, tmp;\n //invert the arguments, but complain about it\n if (period !== null && !isNaN(+period)) {\n deprecateSimple(\n name,\n 'moment().' +\n name +\n '(period, number) is deprecated. Please use moment().' +\n name +\n '(number, period). ' +\n 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'\n );\n tmp = val;\n val = period;\n period = tmp;\n }\n\n dur = createDuration(val, period);\n addSubtract(this, dur, direction);\n return this;\n };\n }\n\n function addSubtract(mom, duration, isAdding, updateOffset) {\n var milliseconds = duration._milliseconds,\n days = absRound(duration._days),\n months = absRound(duration._months);\n\n if (!mom.isValid()) {\n // No op\n return;\n }\n\n updateOffset = updateOffset == null ? true : updateOffset;\n\n if (months) {\n setMonth(mom, get(mom, 'Month') + months * isAdding);\n }\n if (days) {\n set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);\n }\n if (milliseconds) {\n mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);\n }\n if (updateOffset) {\n hooks.updateOffset(mom, days || months);\n }\n }\n\n var add = createAdder(1, 'add'),\n subtract = createAdder(-1, 'subtract');\n\n function isString(input) {\n return typeof input === 'string' || input instanceof String;\n }\n\n // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined\n function isMomentInput(input) {\n return (\n isMoment(input) ||\n isDate(input) ||\n isString(input) ||\n isNumber(input) ||\n isNumberOrStringArray(input) ||\n isMomentInputObject(input) ||\n input === null ||\n input === undefined\n );\n }\n\n function isMomentInputObject(input) {\n var objectTest = isObject(input) && !isObjectEmpty(input),\n propertyTest = false,\n properties = [\n 'years',\n 'year',\n 'y',\n 'months',\n 'month',\n 'M',\n 'days',\n 'day',\n 'd',\n 'dates',\n 'date',\n 'D',\n 'hours',\n 'hour',\n 'h',\n 'minutes',\n 'minute',\n 'm',\n 'seconds',\n 'second',\n 's',\n 'milliseconds',\n 'millisecond',\n 'ms',\n ],\n i,\n property,\n propertyLen = properties.length;\n\n for (i = 0; i < propertyLen; i += 1) {\n property = properties[i];\n propertyTest = propertyTest || hasOwnProp(input, property);\n }\n\n return objectTest && propertyTest;\n }\n\n function isNumberOrStringArray(input) {\n var arrayTest = isArray(input),\n dataTypeTest = false;\n if (arrayTest) {\n dataTypeTest =\n input.filter(function (item) {\n return !isNumber(item) && isString(input);\n }).length === 0;\n }\n return arrayTest && dataTypeTest;\n }\n\n function isCalendarSpec(input) {\n var objectTest = isObject(input) && !isObjectEmpty(input),\n propertyTest = false,\n properties = [\n 'sameDay',\n 'nextDay',\n 'lastDay',\n 'nextWeek',\n 'lastWeek',\n 'sameElse',\n ],\n i,\n property;\n\n for (i = 0; i < properties.length; i += 1) {\n property = properties[i];\n propertyTest = propertyTest || hasOwnProp(input, property);\n }\n\n return objectTest && propertyTest;\n }\n\n function getCalendarFormat(myMoment, now) {\n var diff = myMoment.diff(now, 'days', true);\n return diff < -6\n ? 'sameElse'\n : diff < -1\n ? 'lastWeek'\n : diff < 0\n ? 'lastDay'\n : diff < 1\n ? 'sameDay'\n : diff < 2\n ? 'nextDay'\n : diff < 7\n ? 'nextWeek'\n : 'sameElse';\n }\n\n function calendar$1(time, formats) {\n // Support for single parameter, formats only overload to the calendar function\n if (arguments.length === 1) {\n if (!arguments[0]) {\n time = undefined;\n formats = undefined;\n } else if (isMomentInput(arguments[0])) {\n time = arguments[0];\n formats = undefined;\n } else if (isCalendarSpec(arguments[0])) {\n formats = arguments[0];\n time = undefined;\n }\n }\n // We want to compare the start of today, vs this.\n // Getting start-of-today depends on whether we're local/utc/offset or not.\n var now = time || createLocal(),\n sod = cloneWithOffset(now, this).startOf('day'),\n format = hooks.calendarFormat(this, sod) || 'sameElse',\n output =\n formats &&\n (isFunction(formats[format])\n ? formats[format].call(this, now)\n : formats[format]);\n\n return this.format(\n output || this.localeData().calendar(format, this, createLocal(now))\n );\n }\n\n function clone() {\n return new Moment(this);\n }\n\n function isAfter(input, units) {\n var localInput = isMoment(input) ? input : createLocal(input);\n if (!(this.isValid() && localInput.isValid())) {\n return false;\n }\n units = normalizeUnits(units) || 'millisecond';\n if (units === 'millisecond') {\n return this.valueOf() > localInput.valueOf();\n } else {\n return localInput.valueOf() < this.clone().startOf(units).valueOf();\n }\n }\n\n function isBefore(input, units) {\n var localInput = isMoment(input) ? input : createLocal(input);\n if (!(this.isValid() && localInput.isValid())) {\n return false;\n }\n units = normalizeUnits(units) || 'millisecond';\n if (units === 'millisecond') {\n return this.valueOf() < localInput.valueOf();\n } else {\n return this.clone().endOf(units).valueOf() < localInput.valueOf();\n }\n }\n\n function isBetween(from, to, units, inclusivity) {\n var localFrom = isMoment(from) ? from : createLocal(from),\n localTo = isMoment(to) ? to : createLocal(to);\n if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {\n return false;\n }\n inclusivity = inclusivity || '()';\n return (\n (inclusivity[0] === '('\n ? this.isAfter(localFrom, units)\n : !this.isBefore(localFrom, units)) &&\n (inclusivity[1] === ')'\n ? this.isBefore(localTo, units)\n : !this.isAfter(localTo, units))\n );\n }\n\n function isSame(input, units) {\n var localInput = isMoment(input) ? input : createLocal(input),\n inputMs;\n if (!(this.isValid() && localInput.isValid())) {\n return false;\n }\n units = normalizeUnits(units) || 'millisecond';\n if (units === 'millisecond') {\n return this.valueOf() === localInput.valueOf();\n } else {\n inputMs = localInput.valueOf();\n return (\n this.clone().startOf(units).valueOf() <= inputMs &&\n inputMs <= this.clone().endOf(units).valueOf()\n );\n }\n }\n\n function isSameOrAfter(input, units) {\n return this.isSame(input, units) || this.isAfter(input, units);\n }\n\n function isSameOrBefore(input, units) {\n return this.isSame(input, units) || this.isBefore(input, units);\n }\n\n function diff(input, units, asFloat) {\n var that, zoneDelta, output;\n\n if (!this.isValid()) {\n return NaN;\n }\n\n that = cloneWithOffset(input, this);\n\n if (!that.isValid()) {\n return NaN;\n }\n\n zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;\n\n units = normalizeUnits(units);\n\n switch (units) {\n case 'year':\n output = monthDiff(this, that) / 12;\n break;\n case 'month':\n output = monthDiff(this, that);\n break;\n case 'quarter':\n output = monthDiff(this, that) / 3;\n break;\n case 'second':\n output = (this - that) / 1e3;\n break; // 1000\n case 'minute':\n output = (this - that) / 6e4;\n break; // 1000 * 60\n case 'hour':\n output = (this - that) / 36e5;\n break; // 1000 * 60 * 60\n case 'day':\n output = (this - that - zoneDelta) / 864e5;\n break; // 1000 * 60 * 60 * 24, negate dst\n case 'week':\n output = (this - that - zoneDelta) / 6048e5;\n break; // 1000 * 60 * 60 * 24 * 7, negate dst\n default:\n output = this - that;\n }\n\n return asFloat ? output : absFloor(output);\n }\n\n function monthDiff(a, b) {\n if (a.date() < b.date()) {\n // end-of-month calculations work correct when the start month has more\n // days than the end month.\n return -monthDiff(b, a);\n }\n // difference in months\n var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),\n // b is in (anchor - 1 month, anchor + 1 month)\n anchor = a.clone().add(wholeMonthDiff, 'months'),\n anchor2,\n adjust;\n\n if (b - anchor < 0) {\n anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');\n // linear across the month\n adjust = (b - anchor) / (anchor - anchor2);\n } else {\n anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');\n // linear across the month\n adjust = (b - anchor) / (anchor2 - anchor);\n }\n\n //check for negative zero, return zero if negative zero\n return -(wholeMonthDiff + adjust) || 0;\n }\n\n hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';\n hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';\n\n function toString() {\n return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');\n }\n\n function toISOString(keepOffset) {\n if (!this.isValid()) {\n return null;\n }\n var utc = keepOffset !== true,\n m = utc ? this.clone().utc() : this;\n if (m.year() < 0 || m.year() > 9999) {\n return formatMoment(\n m,\n utc\n ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'\n : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'\n );\n }\n if (isFunction(Date.prototype.toISOString)) {\n // native implementation is ~50x faster, use it when we can\n if (utc) {\n return this.toDate().toISOString();\n } else {\n return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)\n .toISOString()\n .replace('Z', formatMoment(m, 'Z'));\n }\n }\n return formatMoment(\n m,\n utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'\n );\n }\n\n /**\n * Return a human readable representation of a moment that can\n * also be evaluated to get a new moment which is the same\n *\n * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects\n */\n function inspect() {\n if (!this.isValid()) {\n return 'moment.invalid(/* ' + this._i + ' */)';\n }\n var func = 'moment',\n zone = '',\n prefix,\n year,\n datetime,\n suffix;\n if (!this.isLocal()) {\n func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';\n zone = 'Z';\n }\n prefix = '[' + func + '(\"]';\n year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';\n datetime = '-MM-DD[T]HH:mm:ss.SSS';\n suffix = zone + '[\")]';\n\n return this.format(prefix + year + datetime + suffix);\n }\n\n function format(inputString) {\n if (!inputString) {\n inputString = this.isUtc()\n ? hooks.defaultFormatUtc\n : hooks.defaultFormat;\n }\n var output = formatMoment(this, inputString);\n return this.localeData().postformat(output);\n }\n\n function from(time, withoutSuffix) {\n if (\n this.isValid() &&\n ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n ) {\n return createDuration({ to: this, from: time })\n .locale(this.locale())\n .humanize(!withoutSuffix);\n } else {\n return this.localeData().invalidDate();\n }\n }\n\n function fromNow(withoutSuffix) {\n return this.from(createLocal(), withoutSuffix);\n }\n\n function to(time, withoutSuffix) {\n if (\n this.isValid() &&\n ((isMoment(time) && time.isValid()) || createLocal(time).isValid())\n ) {\n return createDuration({ from: this, to: time })\n .locale(this.locale())\n .humanize(!withoutSuffix);\n } else {\n return this.localeData().invalidDate();\n }\n }\n\n function toNow(withoutSuffix) {\n return this.to(createLocal(), withoutSuffix);\n }\n\n // If passed a locale key, it will set the locale for this\n // instance. Otherwise, it will return the locale configuration\n // variables for this instance.\n function locale(key) {\n var newLocaleData;\n\n if (key === undefined) {\n return this._locale._abbr;\n } else {\n newLocaleData = getLocale(key);\n if (newLocaleData != null) {\n this._locale = newLocaleData;\n }\n return this;\n }\n }\n\n var lang = deprecate(\n 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',\n function (key) {\n if (key === undefined) {\n return this.localeData();\n } else {\n return this.locale(key);\n }\n }\n );\n\n function localeData() {\n return this._locale;\n }\n\n var MS_PER_SECOND = 1000,\n MS_PER_MINUTE = 60 * MS_PER_SECOND,\n MS_PER_HOUR = 60 * MS_PER_MINUTE,\n MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;\n\n // actual modulo - handles negative numbers (for dates before 1970):\n function mod$1(dividend, divisor) {\n return ((dividend % divisor) + divisor) % divisor;\n }\n\n function localStartOfDate(y, m, d) {\n // the date constructor remaps years 0-99 to 1900-1999\n if (y < 100 && y >= 0) {\n // preserve leap years using a full 400 year cycle, then reset\n return new Date(y + 400, m, d) - MS_PER_400_YEARS;\n } else {\n return new Date(y, m, d).valueOf();\n }\n }\n\n function utcStartOfDate(y, m, d) {\n // Date.UTC remaps years 0-99 to 1900-1999\n if (y < 100 && y >= 0) {\n // preserve leap years using a full 400 year cycle, then reset\n return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;\n } else {\n return Date.UTC(y, m, d);\n }\n }\n\n function startOf(units) {\n var time, startOfDate;\n units = normalizeUnits(units);\n if (units === undefined || units === 'millisecond' || !this.isValid()) {\n return this;\n }\n\n startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n switch (units) {\n case 'year':\n time = startOfDate(this.year(), 0, 1);\n break;\n case 'quarter':\n time = startOfDate(\n this.year(),\n this.month() - (this.month() % 3),\n 1\n );\n break;\n case 'month':\n time = startOfDate(this.year(), this.month(), 1);\n break;\n case 'week':\n time = startOfDate(\n this.year(),\n this.month(),\n this.date() - this.weekday()\n );\n break;\n case 'isoWeek':\n time = startOfDate(\n this.year(),\n this.month(),\n this.date() - (this.isoWeekday() - 1)\n );\n break;\n case 'day':\n case 'date':\n time = startOfDate(this.year(), this.month(), this.date());\n break;\n case 'hour':\n time = this._d.valueOf();\n time -= mod$1(\n time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n MS_PER_HOUR\n );\n break;\n case 'minute':\n time = this._d.valueOf();\n time -= mod$1(time, MS_PER_MINUTE);\n break;\n case 'second':\n time = this._d.valueOf();\n time -= mod$1(time, MS_PER_SECOND);\n break;\n }\n\n this._d.setTime(time);\n hooks.updateOffset(this, true);\n return this;\n }\n\n function endOf(units) {\n var time, startOfDate;\n units = normalizeUnits(units);\n if (units === undefined || units === 'millisecond' || !this.isValid()) {\n return this;\n }\n\n startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;\n\n switch (units) {\n case 'year':\n time = startOfDate(this.year() + 1, 0, 1) - 1;\n break;\n case 'quarter':\n time =\n startOfDate(\n this.year(),\n this.month() - (this.month() % 3) + 3,\n 1\n ) - 1;\n break;\n case 'month':\n time = startOfDate(this.year(), this.month() + 1, 1) - 1;\n break;\n case 'week':\n time =\n startOfDate(\n this.year(),\n this.month(),\n this.date() - this.weekday() + 7\n ) - 1;\n break;\n case 'isoWeek':\n time =\n startOfDate(\n this.year(),\n this.month(),\n this.date() - (this.isoWeekday() - 1) + 7\n ) - 1;\n break;\n case 'day':\n case 'date':\n time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;\n break;\n case 'hour':\n time = this._d.valueOf();\n time +=\n MS_PER_HOUR -\n mod$1(\n time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),\n MS_PER_HOUR\n ) -\n 1;\n break;\n case 'minute':\n time = this._d.valueOf();\n time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;\n break;\n case 'second':\n time = this._d.valueOf();\n time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;\n break;\n }\n\n this._d.setTime(time);\n hooks.updateOffset(this, true);\n return this;\n }\n\n function valueOf() {\n return this._d.valueOf() - (this._offset || 0) * 60000;\n }\n\n function unix() {\n return Math.floor(this.valueOf() / 1000);\n }\n\n function toDate() {\n return new Date(this.valueOf());\n }\n\n function toArray() {\n var m = this;\n return [\n m.year(),\n m.month(),\n m.date(),\n m.hour(),\n m.minute(),\n m.second(),\n m.millisecond(),\n ];\n }\n\n function toObject() {\n var m = this;\n return {\n years: m.year(),\n months: m.month(),\n date: m.date(),\n hours: m.hours(),\n minutes: m.minutes(),\n seconds: m.seconds(),\n milliseconds: m.milliseconds(),\n };\n }\n\n function toJSON() {\n // new Date(NaN).toJSON() === null\n return this.isValid() ? this.toISOString() : null;\n }\n\n function isValid$2() {\n return isValid(this);\n }\n\n function parsingFlags() {\n return extend({}, getParsingFlags(this));\n }\n\n function invalidAt() {\n return getParsingFlags(this).overflow;\n }\n\n function creationData() {\n return {\n input: this._i,\n format: this._f,\n locale: this._locale,\n isUTC: this._isUTC,\n strict: this._strict,\n };\n }\n\n addFormatToken('N', 0, 0, 'eraAbbr');\n addFormatToken('NN', 0, 0, 'eraAbbr');\n addFormatToken('NNN', 0, 0, 'eraAbbr');\n addFormatToken('NNNN', 0, 0, 'eraName');\n addFormatToken('NNNNN', 0, 0, 'eraNarrow');\n\n addFormatToken('y', ['y', 1], 'yo', 'eraYear');\n addFormatToken('y', ['yy', 2], 0, 'eraYear');\n addFormatToken('y', ['yyy', 3], 0, 'eraYear');\n addFormatToken('y', ['yyyy', 4], 0, 'eraYear');\n\n addRegexToken('N', matchEraAbbr);\n addRegexToken('NN', matchEraAbbr);\n addRegexToken('NNN', matchEraAbbr);\n addRegexToken('NNNN', matchEraName);\n addRegexToken('NNNNN', matchEraNarrow);\n\n addParseToken(\n ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'],\n function (input, array, config, token) {\n var era = config._locale.erasParse(input, token, config._strict);\n if (era) {\n getParsingFlags(config).era = era;\n } else {\n getParsingFlags(config).invalidEra = input;\n }\n }\n );\n\n addRegexToken('y', matchUnsigned);\n addRegexToken('yy', matchUnsigned);\n addRegexToken('yyy', matchUnsigned);\n addRegexToken('yyyy', matchUnsigned);\n addRegexToken('yo', matchEraYearOrdinal);\n\n addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);\n addParseToken(['yo'], function (input, array, config, token) {\n var match;\n if (config._locale._eraYearOrdinalRegex) {\n match = input.match(config._locale._eraYearOrdinalRegex);\n }\n\n if (config._locale.eraYearOrdinalParse) {\n array[YEAR] = config._locale.eraYearOrdinalParse(input, match);\n } else {\n array[YEAR] = parseInt(input, 10);\n }\n });\n\n function localeEras(m, format) {\n var i,\n l,\n date,\n eras = this._eras || getLocale('en')._eras;\n for (i = 0, l = eras.length; i < l; ++i) {\n switch (typeof eras[i].since) {\n case 'string':\n // truncate time\n date = hooks(eras[i].since).startOf('day');\n eras[i].since = date.valueOf();\n break;\n }\n\n switch (typeof eras[i].until) {\n case 'undefined':\n eras[i].until = +Infinity;\n break;\n case 'string':\n // truncate time\n date = hooks(eras[i].until).startOf('day').valueOf();\n eras[i].until = date.valueOf();\n break;\n }\n }\n return eras;\n }\n\n function localeErasParse(eraName, format, strict) {\n var i,\n l,\n eras = this.eras(),\n name,\n abbr,\n narrow;\n eraName = eraName.toUpperCase();\n\n for (i = 0, l = eras.length; i < l; ++i) {\n name = eras[i].name.toUpperCase();\n abbr = eras[i].abbr.toUpperCase();\n narrow = eras[i].narrow.toUpperCase();\n\n if (strict) {\n switch (format) {\n case 'N':\n case 'NN':\n case 'NNN':\n if (abbr === eraName) {\n return eras[i];\n }\n break;\n\n case 'NNNN':\n if (name === eraName) {\n return eras[i];\n }\n break;\n\n case 'NNNNN':\n if (narrow === eraName) {\n return eras[i];\n }\n break;\n }\n } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {\n return eras[i];\n }\n }\n }\n\n function localeErasConvertYear(era, year) {\n var dir = era.since <= era.until ? +1 : -1;\n if (year === undefined) {\n return hooks(era.since).year();\n } else {\n return hooks(era.since).year() + (year - era.offset) * dir;\n }\n }\n\n function getEraName() {\n var i,\n l,\n val,\n eras = this.localeData().eras();\n for (i = 0, l = eras.length; i < l; ++i) {\n // truncate time\n val = this.clone().startOf('day').valueOf();\n\n if (eras[i].since <= val && val <= eras[i].until) {\n return eras[i].name;\n }\n if (eras[i].until <= val && val <= eras[i].since) {\n return eras[i].name;\n }\n }\n\n return '';\n }\n\n function getEraNarrow() {\n var i,\n l,\n val,\n eras = this.localeData().eras();\n for (i = 0, l = eras.length; i < l; ++i) {\n // truncate time\n val = this.clone().startOf('day').valueOf();\n\n if (eras[i].since <= val && val <= eras[i].until) {\n return eras[i].narrow;\n }\n if (eras[i].until <= val && val <= eras[i].since) {\n return eras[i].narrow;\n }\n }\n\n return '';\n }\n\n function getEraAbbr() {\n var i,\n l,\n val,\n eras = this.localeData().eras();\n for (i = 0, l = eras.length; i < l; ++i) {\n // truncate time\n val = this.clone().startOf('day').valueOf();\n\n if (eras[i].since <= val && val <= eras[i].until) {\n return eras[i].abbr;\n }\n if (eras[i].until <= val && val <= eras[i].since) {\n return eras[i].abbr;\n }\n }\n\n return '';\n }\n\n function getEraYear() {\n var i,\n l,\n dir,\n val,\n eras = this.localeData().eras();\n for (i = 0, l = eras.length; i < l; ++i) {\n dir = eras[i].since <= eras[i].until ? +1 : -1;\n\n // truncate time\n val = this.clone().startOf('day').valueOf();\n\n if (\n (eras[i].since <= val && val <= eras[i].until) ||\n (eras[i].until <= val && val <= eras[i].since)\n ) {\n return (\n (this.year() - hooks(eras[i].since).year()) * dir +\n eras[i].offset\n );\n }\n }\n\n return this.year();\n }\n\n function erasNameRegex(isStrict) {\n if (!hasOwnProp(this, '_erasNameRegex')) {\n computeErasParse.call(this);\n }\n return isStrict ? this._erasNameRegex : this._erasRegex;\n }\n\n function erasAbbrRegex(isStrict) {\n if (!hasOwnProp(this, '_erasAbbrRegex')) {\n computeErasParse.call(this);\n }\n return isStrict ? this._erasAbbrRegex : this._erasRegex;\n }\n\n function erasNarrowRegex(isStrict) {\n if (!hasOwnProp(this, '_erasNarrowRegex')) {\n computeErasParse.call(this);\n }\n return isStrict ? this._erasNarrowRegex : this._erasRegex;\n }\n\n function matchEraAbbr(isStrict, locale) {\n return locale.erasAbbrRegex(isStrict);\n }\n\n function matchEraName(isStrict, locale) {\n return locale.erasNameRegex(isStrict);\n }\n\n function matchEraNarrow(isStrict, locale) {\n return locale.erasNarrowRegex(isStrict);\n }\n\n function matchEraYearOrdinal(isStrict, locale) {\n return locale._eraYearOrdinalRegex || matchUnsigned;\n }\n\n function computeErasParse() {\n var abbrPieces = [],\n namePieces = [],\n narrowPieces = [],\n mixedPieces = [],\n i,\n l,\n eras = this.eras();\n\n for (i = 0, l = eras.length; i < l; ++i) {\n namePieces.push(regexEscape(eras[i].name));\n abbrPieces.push(regexEscape(eras[i].abbr));\n narrowPieces.push(regexEscape(eras[i].narrow));\n\n mixedPieces.push(regexEscape(eras[i].name));\n mixedPieces.push(regexEscape(eras[i].abbr));\n mixedPieces.push(regexEscape(eras[i].narrow));\n }\n\n this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');\n this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');\n this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');\n this._erasNarrowRegex = new RegExp(\n '^(' + narrowPieces.join('|') + ')',\n 'i'\n );\n }\n\n // FORMATTING\n\n addFormatToken(0, ['gg', 2], 0, function () {\n return this.weekYear() % 100;\n });\n\n addFormatToken(0, ['GG', 2], 0, function () {\n return this.isoWeekYear() % 100;\n });\n\n function addWeekYearFormatToken(token, getter) {\n addFormatToken(0, [token, token.length], 0, getter);\n }\n\n addWeekYearFormatToken('gggg', 'weekYear');\n addWeekYearFormatToken('ggggg', 'weekYear');\n addWeekYearFormatToken('GGGG', 'isoWeekYear');\n addWeekYearFormatToken('GGGGG', 'isoWeekYear');\n\n // ALIASES\n\n addUnitAlias('weekYear', 'gg');\n addUnitAlias('isoWeekYear', 'GG');\n\n // PRIORITY\n\n addUnitPriority('weekYear', 1);\n addUnitPriority('isoWeekYear', 1);\n\n // PARSING\n\n addRegexToken('G', matchSigned);\n addRegexToken('g', matchSigned);\n addRegexToken('GG', match1to2, match2);\n addRegexToken('gg', match1to2, match2);\n addRegexToken('GGGG', match1to4, match4);\n addRegexToken('gggg', match1to4, match4);\n addRegexToken('GGGGG', match1to6, match6);\n addRegexToken('ggggg', match1to6, match6);\n\n addWeekParseToken(\n ['gggg', 'ggggg', 'GGGG', 'GGGGG'],\n function (input, week, config, token) {\n week[token.substr(0, 2)] = toInt(input);\n }\n );\n\n addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {\n week[token] = hooks.parseTwoDigitYear(input);\n });\n\n // MOMENTS\n\n function getSetWeekYear(input) {\n return getSetWeekYearHelper.call(\n this,\n input,\n this.week(),\n this.weekday(),\n this.localeData()._week.dow,\n this.localeData()._week.doy\n );\n }\n\n function getSetISOWeekYear(input) {\n return getSetWeekYearHelper.call(\n this,\n input,\n this.isoWeek(),\n this.isoWeekday(),\n 1,\n 4\n );\n }\n\n function getISOWeeksInYear() {\n return weeksInYear(this.year(), 1, 4);\n }\n\n function getISOWeeksInISOWeekYear() {\n return weeksInYear(this.isoWeekYear(), 1, 4);\n }\n\n function getWeeksInYear() {\n var weekInfo = this.localeData()._week;\n return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);\n }\n\n function getWeeksInWeekYear() {\n var weekInfo = this.localeData()._week;\n return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);\n }\n\n function getSetWeekYearHelper(input, week, weekday, dow, doy) {\n var weeksTarget;\n if (input == null) {\n return weekOfYear(this, dow, doy).year;\n } else {\n weeksTarget = weeksInYear(input, dow, doy);\n if (week > weeksTarget) {\n week = weeksTarget;\n }\n return setWeekAll.call(this, input, week, weekday, dow, doy);\n }\n }\n\n function setWeekAll(weekYear, week, weekday, dow, doy) {\n var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),\n date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);\n\n this.year(date.getUTCFullYear());\n this.month(date.getUTCMonth());\n this.date(date.getUTCDate());\n return this;\n }\n\n // FORMATTING\n\n addFormatToken('Q', 0, 'Qo', 'quarter');\n\n // ALIASES\n\n addUnitAlias('quarter', 'Q');\n\n // PRIORITY\n\n addUnitPriority('quarter', 7);\n\n // PARSING\n\n addRegexToken('Q', match1);\n addParseToken('Q', function (input, array) {\n array[MONTH] = (toInt(input) - 1) * 3;\n });\n\n // MOMENTS\n\n function getSetQuarter(input) {\n return input == null\n ? Math.ceil((this.month() + 1) / 3)\n : this.month((input - 1) * 3 + (this.month() % 3));\n }\n\n // FORMATTING\n\n addFormatToken('D', ['DD', 2], 'Do', 'date');\n\n // ALIASES\n\n addUnitAlias('date', 'D');\n\n // PRIORITY\n addUnitPriority('date', 9);\n\n // PARSING\n\n addRegexToken('D', match1to2);\n addRegexToken('DD', match1to2, match2);\n addRegexToken('Do', function (isStrict, locale) {\n // TODO: Remove \"ordinalParse\" fallback in next major release.\n return isStrict\n ? locale._dayOfMonthOrdinalParse || locale._ordinalParse\n : locale._dayOfMonthOrdinalParseLenient;\n });\n\n addParseToken(['D', 'DD'], DATE);\n addParseToken('Do', function (input, array) {\n array[DATE] = toInt(input.match(match1to2)[0]);\n });\n\n // MOMENTS\n\n var getSetDayOfMonth = makeGetSet('Date', true);\n\n // FORMATTING\n\n addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');\n\n // ALIASES\n\n addUnitAlias('dayOfYear', 'DDD');\n\n // PRIORITY\n addUnitPriority('dayOfYear', 4);\n\n // PARSING\n\n addRegexToken('DDD', match1to3);\n addRegexToken('DDDD', match3);\n addParseToken(['DDD', 'DDDD'], function (input, array, config) {\n config._dayOfYear = toInt(input);\n });\n\n // HELPERS\n\n // MOMENTS\n\n function getSetDayOfYear(input) {\n var dayOfYear =\n Math.round(\n (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5\n ) + 1;\n return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');\n }\n\n // FORMATTING\n\n addFormatToken('m', ['mm', 2], 0, 'minute');\n\n // ALIASES\n\n addUnitAlias('minute', 'm');\n\n // PRIORITY\n\n addUnitPriority('minute', 14);\n\n // PARSING\n\n addRegexToken('m', match1to2);\n addRegexToken('mm', match1to2, match2);\n addParseToken(['m', 'mm'], MINUTE);\n\n // MOMENTS\n\n var getSetMinute = makeGetSet('Minutes', false);\n\n // FORMATTING\n\n addFormatToken('s', ['ss', 2], 0, 'second');\n\n // ALIASES\n\n addUnitAlias('second', 's');\n\n // PRIORITY\n\n addUnitPriority('second', 15);\n\n // PARSING\n\n addRegexToken('s', match1to2);\n addRegexToken('ss', match1to2, match2);\n addParseToken(['s', 'ss'], SECOND);\n\n // MOMENTS\n\n var getSetSecond = makeGetSet('Seconds', false);\n\n // FORMATTING\n\n addFormatToken('S', 0, 0, function () {\n return ~~(this.millisecond() / 100);\n });\n\n addFormatToken(0, ['SS', 2], 0, function () {\n return ~~(this.millisecond() / 10);\n });\n\n addFormatToken(0, ['SSS', 3], 0, 'millisecond');\n addFormatToken(0, ['SSSS', 4], 0, function () {\n return this.millisecond() * 10;\n });\n addFormatToken(0, ['SSSSS', 5], 0, function () {\n return this.millisecond() * 100;\n });\n addFormatToken(0, ['SSSSSS', 6], 0, function () {\n return this.millisecond() * 1000;\n });\n addFormatToken(0, ['SSSSSSS', 7], 0, function () {\n return this.millisecond() * 10000;\n });\n addFormatToken(0, ['SSSSSSSS', 8], 0, function () {\n return this.millisecond() * 100000;\n });\n addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {\n return this.millisecond() * 1000000;\n });\n\n // ALIASES\n\n addUnitAlias('millisecond', 'ms');\n\n // PRIORITY\n\n addUnitPriority('millisecond', 16);\n\n // PARSING\n\n addRegexToken('S', match1to3, match1);\n addRegexToken('SS', match1to3, match2);\n addRegexToken('SSS', match1to3, match3);\n\n var token, getSetMillisecond;\n for (token = 'SSSS'; token.length <= 9; token += 'S') {\n addRegexToken(token, matchUnsigned);\n }\n\n function parseMs(input, array) {\n array[MILLISECOND] = toInt(('0.' + input) * 1000);\n }\n\n for (token = 'S'; token.length <= 9; token += 'S') {\n addParseToken(token, parseMs);\n }\n\n getSetMillisecond = makeGetSet('Milliseconds', false);\n\n // FORMATTING\n\n addFormatToken('z', 0, 0, 'zoneAbbr');\n addFormatToken('zz', 0, 0, 'zoneName');\n\n // MOMENTS\n\n function getZoneAbbr() {\n return this._isUTC ? 'UTC' : '';\n }\n\n function getZoneName() {\n return this._isUTC ? 'Coordinated Universal Time' : '';\n }\n\n var proto = Moment.prototype;\n\n proto.add = add;\n proto.calendar = calendar$1;\n proto.clone = clone;\n proto.diff = diff;\n proto.endOf = endOf;\n proto.format = format;\n proto.from = from;\n proto.fromNow = fromNow;\n proto.to = to;\n proto.toNow = toNow;\n proto.get = stringGet;\n proto.invalidAt = invalidAt;\n proto.isAfter = isAfter;\n proto.isBefore = isBefore;\n proto.isBetween = isBetween;\n proto.isSame = isSame;\n proto.isSameOrAfter = isSameOrAfter;\n proto.isSameOrBefore = isSameOrBefore;\n proto.isValid = isValid$2;\n proto.lang = lang;\n proto.locale = locale;\n proto.localeData = localeData;\n proto.max = prototypeMax;\n proto.min = prototypeMin;\n proto.parsingFlags = parsingFlags;\n proto.set = stringSet;\n proto.startOf = startOf;\n proto.subtract = subtract;\n proto.toArray = toArray;\n proto.toObject = toObject;\n proto.toDate = toDate;\n proto.toISOString = toISOString;\n proto.inspect = inspect;\n if (typeof Symbol !== 'undefined' && Symbol.for != null) {\n proto[Symbol.for('nodejs.util.inspect.custom')] = function () {\n return 'Moment<' + this.format() + '>';\n };\n }\n proto.toJSON = toJSON;\n proto.toString = toString;\n proto.unix = unix;\n proto.valueOf = valueOf;\n proto.creationData = creationData;\n proto.eraName = getEraName;\n proto.eraNarrow = getEraNarrow;\n proto.eraAbbr = getEraAbbr;\n proto.eraYear = getEraYear;\n proto.year = getSetYear;\n proto.isLeapYear = getIsLeapYear;\n proto.weekYear = getSetWeekYear;\n proto.isoWeekYear = getSetISOWeekYear;\n proto.quarter = proto.quarters = getSetQuarter;\n proto.month = getSetMonth;\n proto.daysInMonth = getDaysInMonth;\n proto.week = proto.weeks = getSetWeek;\n proto.isoWeek = proto.isoWeeks = getSetISOWeek;\n proto.weeksInYear = getWeeksInYear;\n proto.weeksInWeekYear = getWeeksInWeekYear;\n proto.isoWeeksInYear = getISOWeeksInYear;\n proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;\n proto.date = getSetDayOfMonth;\n proto.day = proto.days = getSetDayOfWeek;\n proto.weekday = getSetLocaleDayOfWeek;\n proto.isoWeekday = getSetISODayOfWeek;\n proto.dayOfYear = getSetDayOfYear;\n proto.hour = proto.hours = getSetHour;\n proto.minute = proto.minutes = getSetMinute;\n proto.second = proto.seconds = getSetSecond;\n proto.millisecond = proto.milliseconds = getSetMillisecond;\n proto.utcOffset = getSetOffset;\n proto.utc = setOffsetToUTC;\n proto.local = setOffsetToLocal;\n proto.parseZone = setOffsetToParsedOffset;\n proto.hasAlignedHourOffset = hasAlignedHourOffset;\n proto.isDST = isDaylightSavingTime;\n proto.isLocal = isLocal;\n proto.isUtcOffset = isUtcOffset;\n proto.isUtc = isUtc;\n proto.isUTC = isUtc;\n proto.zoneAbbr = getZoneAbbr;\n proto.zoneName = getZoneName;\n proto.dates = deprecate(\n 'dates accessor is deprecated. Use date instead.',\n getSetDayOfMonth\n );\n proto.months = deprecate(\n 'months accessor is deprecated. Use month instead',\n getSetMonth\n );\n proto.years = deprecate(\n 'years accessor is deprecated. Use year instead',\n getSetYear\n );\n proto.zone = deprecate(\n 'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',\n getSetZone\n );\n proto.isDSTShifted = deprecate(\n 'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',\n isDaylightSavingTimeShifted\n );\n\n function createUnix(input) {\n return createLocal(input * 1000);\n }\n\n function createInZone() {\n return createLocal.apply(null, arguments).parseZone();\n }\n\n function preParsePostFormat(string) {\n return string;\n }\n\n var proto$1 = Locale.prototype;\n\n proto$1.calendar = calendar;\n proto$1.longDateFormat = longDateFormat;\n proto$1.invalidDate = invalidDate;\n proto$1.ordinal = ordinal;\n proto$1.preparse = preParsePostFormat;\n proto$1.postformat = preParsePostFormat;\n proto$1.relativeTime = relativeTime;\n proto$1.pastFuture = pastFuture;\n proto$1.set = set;\n proto$1.eras = localeEras;\n proto$1.erasParse = localeErasParse;\n proto$1.erasConvertYear = localeErasConvertYear;\n proto$1.erasAbbrRegex = erasAbbrRegex;\n proto$1.erasNameRegex = erasNameRegex;\n proto$1.erasNarrowRegex = erasNarrowRegex;\n\n proto$1.months = localeMonths;\n proto$1.monthsShort = localeMonthsShort;\n proto$1.monthsParse = localeMonthsParse;\n proto$1.monthsRegex = monthsRegex;\n proto$1.monthsShortRegex = monthsShortRegex;\n proto$1.week = localeWeek;\n proto$1.firstDayOfYear = localeFirstDayOfYear;\n proto$1.firstDayOfWeek = localeFirstDayOfWeek;\n\n proto$1.weekdays = localeWeekdays;\n proto$1.weekdaysMin = localeWeekdaysMin;\n proto$1.weekdaysShort = localeWeekdaysShort;\n proto$1.weekdaysParse = localeWeekdaysParse;\n\n proto$1.weekdaysRegex = weekdaysRegex;\n proto$1.weekdaysShortRegex = weekdaysShortRegex;\n proto$1.weekdaysMinRegex = weekdaysMinRegex;\n\n proto$1.isPM = localeIsPM;\n proto$1.meridiem = localeMeridiem;\n\n function get$1(format, index, field, setter) {\n var locale = getLocale(),\n utc = createUTC().set(setter, index);\n return locale[field](utc, format);\n }\n\n function listMonthsImpl(format, index, field) {\n if (isNumber(format)) {\n index = format;\n format = undefined;\n }\n\n format = format || '';\n\n if (index != null) {\n return get$1(format, index, field, 'month');\n }\n\n var i,\n out = [];\n for (i = 0; i < 12; i++) {\n out[i] = get$1(format, i, field, 'month');\n }\n return out;\n }\n\n // ()\n // (5)\n // (fmt, 5)\n // (fmt)\n // (true)\n // (true, 5)\n // (true, fmt, 5)\n // (true, fmt)\n function listWeekdaysImpl(localeSorted, format, index, field) {\n if (typeof localeSorted === 'boolean') {\n if (isNumber(format)) {\n index = format;\n format = undefined;\n }\n\n format = format || '';\n } else {\n format = localeSorted;\n index = format;\n localeSorted = false;\n\n if (isNumber(format)) {\n index = format;\n format = undefined;\n }\n\n format = format || '';\n }\n\n var locale = getLocale(),\n shift = localeSorted ? locale._week.dow : 0,\n i,\n out = [];\n\n if (index != null) {\n return get$1(format, (index + shift) % 7, field, 'day');\n }\n\n for (i = 0; i < 7; i++) {\n out[i] = get$1(format, (i + shift) % 7, field, 'day');\n }\n return out;\n }\n\n function listMonths(format, index) {\n return listMonthsImpl(format, index, 'months');\n }\n\n function listMonthsShort(format, index) {\n return listMonthsImpl(format, index, 'monthsShort');\n }\n\n function listWeekdays(localeSorted, format, index) {\n return listWeekdaysImpl(localeSorted, format, index, 'weekdays');\n }\n\n function listWeekdaysShort(localeSorted, format, index) {\n return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');\n }\n\n function listWeekdaysMin(localeSorted, format, index) {\n return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');\n }\n\n getSetGlobalLocale('en', {\n eras: [\n {\n since: '0001-01-01',\n until: +Infinity,\n offset: 1,\n name: 'Anno Domini',\n narrow: 'AD',\n abbr: 'AD',\n },\n {\n since: '0000-12-31',\n until: -Infinity,\n offset: 1,\n name: 'Before Christ',\n narrow: 'BC',\n abbr: 'BC',\n },\n ],\n dayOfMonthOrdinalParse: /\\d{1,2}(th|st|nd|rd)/,\n ordinal: function (number) {\n var b = number % 10,\n output =\n toInt((number % 100) / 10) === 1\n ? 'th'\n : b === 1\n ? 'st'\n : b === 2\n ? 'nd'\n : b === 3\n ? 'rd'\n : 'th';\n return number + output;\n },\n });\n\n // Side effect imports\n\n hooks.lang = deprecate(\n 'moment.lang is deprecated. Use moment.locale instead.',\n getSetGlobalLocale\n );\n hooks.langData = deprecate(\n 'moment.langData is deprecated. Use moment.localeData instead.',\n getLocale\n );\n\n var mathAbs = Math.abs;\n\n function abs() {\n var data = this._data;\n\n this._milliseconds = mathAbs(this._milliseconds);\n this._days = mathAbs(this._days);\n this._months = mathAbs(this._months);\n\n data.milliseconds = mathAbs(data.milliseconds);\n data.seconds = mathAbs(data.seconds);\n data.minutes = mathAbs(data.minutes);\n data.hours = mathAbs(data.hours);\n data.months = mathAbs(data.months);\n data.years = mathAbs(data.years);\n\n return this;\n }\n\n function addSubtract$1(duration, input, value, direction) {\n var other = createDuration(input, value);\n\n duration._milliseconds += direction * other._milliseconds;\n duration._days += direction * other._days;\n duration._months += direction * other._months;\n\n return duration._bubble();\n }\n\n // supports only 2.0-style add(1, 's') or add(duration)\n function add$1(input, value) {\n return addSubtract$1(this, input, value, 1);\n }\n\n // supports only 2.0-style subtract(1, 's') or subtract(duration)\n function subtract$1(input, value) {\n return addSubtract$1(this, input, value, -1);\n }\n\n function absCeil(number) {\n if (number < 0) {\n return Math.floor(number);\n } else {\n return Math.ceil(number);\n }\n }\n\n function bubble() {\n var milliseconds = this._milliseconds,\n days = this._days,\n months = this._months,\n data = this._data,\n seconds,\n minutes,\n hours,\n years,\n monthsFromDays;\n\n // if we have a mix of positive and negative values, bubble down first\n // check: https://github.com/moment/moment/issues/2166\n if (\n !(\n (milliseconds >= 0 && days >= 0 && months >= 0) ||\n (milliseconds <= 0 && days <= 0 && months <= 0)\n )\n ) {\n milliseconds += absCeil(monthsToDays(months) + days) * 864e5;\n days = 0;\n months = 0;\n }\n\n // The following code bubbles up values, see the tests for\n // examples of what that means.\n data.milliseconds = milliseconds % 1000;\n\n seconds = absFloor(milliseconds / 1000);\n data.seconds = seconds % 60;\n\n minutes = absFloor(seconds / 60);\n data.minutes = minutes % 60;\n\n hours = absFloor(minutes / 60);\n data.hours = hours % 24;\n\n days += absFloor(hours / 24);\n\n // convert days to months\n monthsFromDays = absFloor(daysToMonths(days));\n months += monthsFromDays;\n days -= absCeil(monthsToDays(monthsFromDays));\n\n // 12 months -> 1 year\n years = absFloor(months / 12);\n months %= 12;\n\n data.days = days;\n data.months = months;\n data.years = years;\n\n return this;\n }\n\n function daysToMonths(days) {\n // 400 years have 146097 days (taking into account leap year rules)\n // 400 years have 12 months === 4800\n return (days * 4800) / 146097;\n }\n\n function monthsToDays(months) {\n // the reverse of daysToMonths\n return (months * 146097) / 4800;\n }\n\n function as(units) {\n if (!this.isValid()) {\n return NaN;\n }\n var days,\n months,\n milliseconds = this._milliseconds;\n\n units = normalizeUnits(units);\n\n if (units === 'month' || units === 'quarter' || units === 'year') {\n days = this._days + milliseconds / 864e5;\n months = this._months + daysToMonths(days);\n switch (units) {\n case 'month':\n return months;\n case 'quarter':\n return months / 3;\n case 'year':\n return months / 12;\n }\n } else {\n // handle milliseconds separately because of floating point math errors (issue #1867)\n days = this._days + Math.round(monthsToDays(this._months));\n switch (units) {\n case 'week':\n return days / 7 + milliseconds / 6048e5;\n case 'day':\n return days + milliseconds / 864e5;\n case 'hour':\n return days * 24 + milliseconds / 36e5;\n case 'minute':\n return days * 1440 + milliseconds / 6e4;\n case 'second':\n return days * 86400 + milliseconds / 1000;\n // Math.floor prevents floating point math errors here\n case 'millisecond':\n return Math.floor(days * 864e5) + milliseconds;\n default:\n throw new Error('Unknown unit ' + units);\n }\n }\n }\n\n // TODO: Use this.as('ms')?\n function valueOf$1() {\n if (!this.isValid()) {\n return NaN;\n }\n return (\n this._milliseconds +\n this._days * 864e5 +\n (this._months % 12) * 2592e6 +\n toInt(this._months / 12) * 31536e6\n );\n }\n\n function makeAs(alias) {\n return function () {\n return this.as(alias);\n };\n }\n\n var asMilliseconds = makeAs('ms'),\n asSeconds = makeAs('s'),\n asMinutes = makeAs('m'),\n asHours = makeAs('h'),\n asDays = makeAs('d'),\n asWeeks = makeAs('w'),\n asMonths = makeAs('M'),\n asQuarters = makeAs('Q'),\n asYears = makeAs('y');\n\n function clone$1() {\n return createDuration(this);\n }\n\n function get$2(units) {\n units = normalizeUnits(units);\n return this.isValid() ? this[units + 's']() : NaN;\n }\n\n function makeGetter(name) {\n return function () {\n return this.isValid() ? this._data[name] : NaN;\n };\n }\n\n var milliseconds = makeGetter('milliseconds'),\n seconds = makeGetter('seconds'),\n minutes = makeGetter('minutes'),\n hours = makeGetter('hours'),\n days = makeGetter('days'),\n months = makeGetter('months'),\n years = makeGetter('years');\n\n function weeks() {\n return absFloor(this.days() / 7);\n }\n\n var round = Math.round,\n thresholds = {\n ss: 44, // a few seconds to seconds\n s: 45, // seconds to minute\n m: 45, // minutes to hour\n h: 22, // hours to day\n d: 26, // days to month/week\n w: null, // weeks to month\n M: 11, // months to year\n };\n\n // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize\n function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {\n return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);\n }\n\n function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {\n var duration = createDuration(posNegDuration).abs(),\n seconds = round(duration.as('s')),\n minutes = round(duration.as('m')),\n hours = round(duration.as('h')),\n days = round(duration.as('d')),\n months = round(duration.as('M')),\n weeks = round(duration.as('w')),\n years = round(duration.as('y')),\n a =\n (seconds <= thresholds.ss && ['s', seconds]) ||\n (seconds < thresholds.s && ['ss', seconds]) ||\n (minutes <= 1 && ['m']) ||\n (minutes < thresholds.m && ['mm', minutes]) ||\n (hours <= 1 && ['h']) ||\n (hours < thresholds.h && ['hh', hours]) ||\n (days <= 1 && ['d']) ||\n (days < thresholds.d && ['dd', days]);\n\n if (thresholds.w != null) {\n a =\n a ||\n (weeks <= 1 && ['w']) ||\n (weeks < thresholds.w && ['ww', weeks]);\n }\n a = a ||\n (months <= 1 && ['M']) ||\n (months < thresholds.M && ['MM', months]) ||\n (years <= 1 && ['y']) || ['yy', years];\n\n a[2] = withoutSuffix;\n a[3] = +posNegDuration > 0;\n a[4] = locale;\n return substituteTimeAgo.apply(null, a);\n }\n\n // This function allows you to set the rounding function for relative time strings\n function getSetRelativeTimeRounding(roundingFunction) {\n if (roundingFunction === undefined) {\n return round;\n }\n if (typeof roundingFunction === 'function') {\n round = roundingFunction;\n return true;\n }\n return false;\n }\n\n // This function allows you to set a threshold for relative time strings\n function getSetRelativeTimeThreshold(threshold, limit) {\n if (thresholds[threshold] === undefined) {\n return false;\n }\n if (limit === undefined) {\n return thresholds[threshold];\n }\n thresholds[threshold] = limit;\n if (threshold === 's') {\n thresholds.ss = limit - 1;\n }\n return true;\n }\n\n function humanize(argWithSuffix, argThresholds) {\n if (!this.isValid()) {\n return this.localeData().invalidDate();\n }\n\n var withSuffix = false,\n th = thresholds,\n locale,\n output;\n\n if (typeof argWithSuffix === 'object') {\n argThresholds = argWithSuffix;\n argWithSuffix = false;\n }\n if (typeof argWithSuffix === 'boolean') {\n withSuffix = argWithSuffix;\n }\n if (typeof argThresholds === 'object') {\n th = Object.assign({}, thresholds, argThresholds);\n if (argThresholds.s != null && argThresholds.ss == null) {\n th.ss = argThresholds.s - 1;\n }\n }\n\n locale = this.localeData();\n output = relativeTime$1(this, !withSuffix, th, locale);\n\n if (withSuffix) {\n output = locale.pastFuture(+this, output);\n }\n\n return locale.postformat(output);\n }\n\n var abs$1 = Math.abs;\n\n function sign(x) {\n return (x > 0) - (x < 0) || +x;\n }\n\n function toISOString$1() {\n // for ISO strings we do not use the normal bubbling rules:\n // * milliseconds bubble up until they become hours\n // * days do not bubble at all\n // * months bubble up until they become years\n // This is because there is no context-free conversion between hours and days\n // (think of clock changes)\n // and also not between days and months (28-31 days per month)\n if (!this.isValid()) {\n return this.localeData().invalidDate();\n }\n\n var seconds = abs$1(this._milliseconds) / 1000,\n days = abs$1(this._days),\n months = abs$1(this._months),\n minutes,\n hours,\n years,\n s,\n total = this.asSeconds(),\n totalSign,\n ymSign,\n daysSign,\n hmsSign;\n\n if (!total) {\n // this is the same as C#'s (Noda) and python (isodate)...\n // but not other JS (goog.date)\n return 'P0D';\n }\n\n // 3600 seconds -> 60 minutes -> 1 hour\n minutes = absFloor(seconds / 60);\n hours = absFloor(minutes / 60);\n seconds %= 60;\n minutes %= 60;\n\n // 12 months -> 1 year\n years = absFloor(months / 12);\n months %= 12;\n\n // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js\n s = seconds ? seconds.toFixed(3).replace(/\\.?0+$/, '') : '';\n\n totalSign = total < 0 ? '-' : '';\n ymSign = sign(this._months) !== sign(total) ? '-' : '';\n daysSign = sign(this._days) !== sign(total) ? '-' : '';\n hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';\n\n return (\n totalSign +\n 'P' +\n (years ? ymSign + years + 'Y' : '') +\n (months ? ymSign + months + 'M' : '') +\n (days ? daysSign + days + 'D' : '') +\n (hours || minutes || seconds ? 'T' : '') +\n (hours ? hmsSign + hours + 'H' : '') +\n (minutes ? hmsSign + minutes + 'M' : '') +\n (seconds ? hmsSign + s + 'S' : '')\n );\n }\n\n var proto$2 = Duration.prototype;\n\n proto$2.isValid = isValid$1;\n proto$2.abs = abs;\n proto$2.add = add$1;\n proto$2.subtract = subtract$1;\n proto$2.as = as;\n proto$2.asMilliseconds = asMilliseconds;\n proto$2.asSeconds = asSeconds;\n proto$2.asMinutes = asMinutes;\n proto$2.asHours = asHours;\n proto$2.asDays = asDays;\n proto$2.asWeeks = asWeeks;\n proto$2.asMonths = asMonths;\n proto$2.asQuarters = asQuarters;\n proto$2.asYears = asYears;\n proto$2.valueOf = valueOf$1;\n proto$2._bubble = bubble;\n proto$2.clone = clone$1;\n proto$2.get = get$2;\n proto$2.milliseconds = milliseconds;\n proto$2.seconds = seconds;\n proto$2.minutes = minutes;\n proto$2.hours = hours;\n proto$2.days = days;\n proto$2.weeks = weeks;\n proto$2.months = months;\n proto$2.years = years;\n proto$2.humanize = humanize;\n proto$2.toISOString = toISOString$1;\n proto$2.toString = toISOString$1;\n proto$2.toJSON = toISOString$1;\n proto$2.locale = locale;\n proto$2.localeData = localeData;\n\n proto$2.toIsoString = deprecate(\n 'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',\n toISOString$1\n );\n proto$2.lang = lang;\n\n // FORMATTING\n\n addFormatToken('X', 0, 0, 'unix');\n addFormatToken('x', 0, 0, 'valueOf');\n\n // PARSING\n\n addRegexToken('x', matchSigned);\n addRegexToken('X', matchTimestamp);\n addParseToken('X', function (input, array, config) {\n config._d = new Date(parseFloat(input) * 1000);\n });\n addParseToken('x', function (input, array, config) {\n config._d = new Date(toInt(input));\n });\n\n //! moment.js\n\n hooks.version = '2.29.4';\n\n setHookCallback(createLocal);\n\n hooks.fn = proto;\n hooks.min = min;\n hooks.max = max;\n hooks.now = now;\n hooks.utc = createUTC;\n hooks.unix = createUnix;\n hooks.months = listMonths;\n hooks.isDate = isDate;\n hooks.locale = getSetGlobalLocale;\n hooks.invalid = createInvalid;\n hooks.duration = createDuration;\n hooks.isMoment = isMoment;\n hooks.weekdays = listWeekdays;\n hooks.parseZone = createInZone;\n hooks.localeData = getLocale;\n hooks.isDuration = isDuration;\n hooks.monthsShort = listMonthsShort;\n hooks.weekdaysMin = listWeekdaysMin;\n hooks.defineLocale = defineLocale;\n hooks.updateLocale = updateLocale;\n hooks.locales = listLocales;\n hooks.weekdaysShort = listWeekdaysShort;\n hooks.normalizeUnits = normalizeUnits;\n hooks.relativeTimeRounding = getSetRelativeTimeRounding;\n hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;\n hooks.calendarFormat = getCalendarFormat;\n hooks.prototype = proto;\n\n // currently HTML5 input type only supports 24-hour formats\n hooks.HTML5_FMT = {\n DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // \n DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // \n DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // \n DATE: 'YYYY-MM-DD', // \n TIME: 'HH:mm', // \n TIME_SECONDS: 'HH:mm:ss', // \n TIME_MS: 'HH:mm:ss.SSS', // \n WEEK: 'GGGG-[W]WW', // \n MONTH: 'YYYY-MM', // \n };\n\n return hooks;\n\n})));\n", "'use strict';\n\nmodule.exports = (function(scope) {\n const { location, URL } = scope;\n if ([location, URL].some(api => !api)) {\n return function dynamicImportNotSupported(module) {\n return Promise.reject(new Error(`Failed to import: ${module}: dynamicImport is not supported`));\n };\n }\n scope.__twilioVideoImportedModules = {\n // Imported module map.\n };\n return function dynamicImport(module) {\n if (module in scope.__twilioVideoImportedModules) {\n return Promise.resolve(scope.__twilioVideoImportedModules[module]);\n }\n // NOTE(mmalavalli): Calling import() directly can cause build issues in TypeScript and Webpack\n // (and probably other frameworks). So, we create a Function that calls import() in its body.\n // eslint-disable-next-line no-new-func\n return new Function('scope', `return import('${new URL(module, location)}').then(m => scope.__twilioVideoImportedModules['${module}'] = m);`)(scope);\n };\n}(globalThis));\n", "/**\n * Copyright (c) 2013 Tim Perry\n * Licensed under the MIT license.\n *\n * Copied from https://github.com/pimterry/loglevel (1.7.0)\n * and modified to remove browser and AMD module support, while keeping CommonJS.\n * It was causing a conflict when this is bundled using CommonJS, and then loaded via RequireJS.\n * The proper way to fix this module is to have a build that outputs CommonJS and AMD separately\n * which needs to be submitted to the original module's repo.\n */\n\n/* istanbul ignore file */\n/* eslint-disable */\n// Slightly dubious tricks to cut down minimized file size\nvar noop = function() {};\nvar undefinedType = \"undefined\";\nvar isIE = (typeof window !== undefinedType) && (typeof window.navigator !== undefinedType) && (\n /Trident\\/|MSIE /.test(window.navigator.userAgent)\n);\n\nvar logMethods = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\"\n];\n\n// Cross-browser bind equivalent that works at least back to IE6\nfunction bindMethod(obj, methodName) {\n var method = obj[methodName];\n if (typeof method.bind === 'function') {\n return method.bind(obj);\n } else {\n try {\n return Function.prototype.bind.call(method, obj);\n } catch (e) {\n // Missing bind shim or IE8 + Modernizr, fallback to wrapping\n return function() {\n return Function.prototype.apply.apply(method, [obj, arguments]);\n };\n }\n }\n}\n\n// Trace() doesn't print the message in IE, so for that case we need to wrap it\nfunction traceForIE() {\n if (console.log) {\n if (console.log.apply) {\n console.log.apply(console, arguments);\n } else {\n // In old IE, native console methods themselves don't have apply().\n Function.prototype.apply.apply(console.log, [console, arguments]);\n }\n }\n if (console.trace) console.trace();\n}\n\n// Build the best logging method possible for this env\n// Wherever possible we want to bind, not wrap, to preserve stack traces\nfunction realMethod(methodName) {\n if (methodName === 'debug') {\n methodName = 'log';\n }\n\n if (typeof console === undefinedType) {\n return false; // No method possible, for now - fixed later by enableLoggingWhenConsoleArrives\n } else if (methodName === 'trace' && isIE) {\n return traceForIE;\n } else if (console[methodName] !== undefined) {\n return bindMethod(console, methodName);\n } else if (console.log !== undefined) {\n return bindMethod(console, 'log');\n } else {\n return noop;\n }\n}\n\n// These private functions always need `this` to be set properly\n\nfunction replaceLoggingMethods(level, loggerName) {\n /*jshint validthis:true */\n for (var i = 0; i < logMethods.length; i++) {\n var methodName = logMethods[i];\n this[methodName] = (i < level) ?\n noop :\n this.methodFactory(methodName, level, loggerName);\n }\n\n // Define log.log as an alias for log.debug\n this.log = this.debug;\n}\n\n// In old IE versions, the console isn't present until you first open it.\n// We build realMethod() replacements here that regenerate logging methods\nfunction enableLoggingWhenConsoleArrives(methodName, level, loggerName) {\n return function () {\n if (typeof console !== undefinedType) {\n replaceLoggingMethods.call(this, level, loggerName);\n this[methodName].apply(this, arguments);\n }\n };\n}\n\n// By default, we use closely bound real methods wherever possible, and\n// otherwise we wait for a console to appear, and then try again.\nfunction defaultMethodFactory(methodName, level, loggerName) {\n /*jshint validthis:true */\n return realMethod(methodName) ||\n enableLoggingWhenConsoleArrives.apply(this, arguments);\n}\n\nfunction Logger(name, defaultLevel, factory) {\n var self = this;\n var currentLevel;\n\n var storageKey = \"loglevel\";\n if (typeof name === \"string\") {\n storageKey += \":\" + name;\n } else if (typeof name === \"symbol\") {\n storageKey = undefined;\n }\n\n function persistLevelIfPossible(levelNum) {\n var levelName = (logMethods[levelNum] || 'silent').toUpperCase();\n\n if (typeof window === undefinedType || !storageKey) return;\n\n // Use localStorage if available\n try {\n window.localStorage[storageKey] = levelName;\n return;\n } catch (ignore) {}\n\n // Use session cookie as fallback\n try {\n window.document.cookie =\n encodeURIComponent(storageKey) + \"=\" + levelName + \";\";\n } catch (ignore) {}\n }\n\n function getPersistedLevel() {\n var storedLevel;\n\n if (typeof window === undefinedType || !storageKey) return;\n\n try {\n storedLevel = window.localStorage[storageKey];\n } catch (ignore) {}\n\n // Fallback to cookies if local storage gives us nothing\n if (typeof storedLevel === undefinedType) {\n try {\n var cookie = window.document.cookie;\n var location = cookie.indexOf(\n encodeURIComponent(storageKey) + \"=\");\n if (location !== -1) {\n storedLevel = /^([^;]+)/.exec(cookie.slice(location))[1];\n }\n } catch (ignore) {}\n }\n\n // If the stored level is not valid, treat it as if nothing was stored.\n if (self.levels[storedLevel] === undefined) {\n storedLevel = undefined;\n }\n\n return storedLevel;\n }\n\n /*\n *\n * Public logger API - see https://github.com/pimterry/loglevel for details\n *\n */\n\n self.name = name;\n\n self.levels = { \"TRACE\": 0, \"DEBUG\": 1, \"INFO\": 2, \"WARN\": 3,\n \"ERROR\": 4, \"SILENT\": 5};\n\n self.methodFactory = factory || defaultMethodFactory;\n\n self.getLevel = function () {\n return currentLevel;\n };\n\n self.setLevel = function (level, persist) {\n if (typeof level === \"string\" && self.levels[level.toUpperCase()] !== undefined) {\n level = self.levels[level.toUpperCase()];\n }\n if (typeof level === \"number\" && level >= 0 && level <= self.levels.SILENT) {\n currentLevel = level;\n if (persist !== false) { // defaults to true\n persistLevelIfPossible(level);\n }\n replaceLoggingMethods.call(self, level, name);\n if (typeof console === undefinedType && level < self.levels.SILENT) {\n return \"No console available for logging\";\n }\n } else {\n throw \"log.setLevel() called with invalid level: \" + level;\n }\n };\n\n self.setDefaultLevel = function (level) {\n if (!getPersistedLevel()) {\n self.setLevel(level, false);\n }\n };\n\n self.enableAll = function(persist) {\n self.setLevel(self.levels.TRACE, persist);\n };\n\n self.disableAll = function(persist) {\n self.setLevel(self.levels.SILENT, persist);\n };\n\n // Initialize with the right level\n var initialLevel = getPersistedLevel();\n if (initialLevel == null) {\n initialLevel = defaultLevel == null ? \"WARN\" : defaultLevel;\n }\n self.setLevel(initialLevel, false);\n}\n\n/*\n *\n * Top-level API\n *\n */\n\nvar defaultLogger = new Logger();\n\nvar _loggersByName = {};\ndefaultLogger.getLogger = function getLogger(name) {\n if ((typeof name !== \"symbol\" && typeof name !== \"string\") || name === \"\") {\n throw new TypeError(\"You must supply a name when creating a logger.\");\n }\n\n var logger = _loggersByName[name];\n if (!logger) {\n logger = _loggersByName[name] = new Logger(\n name, defaultLogger.getLevel(), defaultLogger.methodFactory);\n }\n return logger;\n};\n\n// Grab the current global log variable in case of overwrite\nvar _log = (typeof window !== undefinedType) ? window.log : undefined;\ndefaultLogger.noConflict = function() {\n if (typeof window !== undefinedType &&\n window.log === defaultLogger) {\n window.log = _log;\n }\n\n return defaultLogger;\n};\n\ndefaultLogger.getLoggers = function getLoggers() {\n return _loggersByName;\n};\n\n// ES6 default export, for compatibility\ndefaultLogger['default'] = defaultLogger;\n\nmodule.exports = defaultLogger;\n", "{\n \"name\": \"twilio-video\",\n \"title\": \"Twilio Video\",\n \"description\": \"Twilio Video JavaScript Library\",\n \"version\": \"2.26.2\",\n \"homepage\": \"https://twilio.com\",\n \"author\": \"Mark Andrus Roberts \",\n \"contributors\": [\n \"Ryan Rowland \",\n \"Manjesh Malavalli \",\n \"Makarand Patwardhan \"\n ],\n \"keywords\": [\n \"twilio\",\n \"webrtc\",\n \"library\",\n \"javascript\",\n \"video\",\n \"rooms\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/twilio/twilio-video.js.git\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.14.2\",\n \"@babel/preset-env\": \"^7.14.2\",\n \"@babel/preset-typescript\": \"^7.13.0\",\n \"@types/express\": \"^4.11.0\",\n \"@types/node\": \"^8.5.1\",\n \"@types/selenium-webdriver\": \"^3.0.8\",\n \"@types/ws\": \"^3.2.1\",\n \"@typescript-eslint/eslint-plugin\": \"^4.13.0\",\n \"@typescript-eslint/parser\": \"^4.0.0\",\n \"babel-cli\": \"^6.26.0\",\n \"babel-preset-es2015\": \"^6.24.1\",\n \"browserify\": \"^17.0.0\",\n \"cheerio\": \"^0.22.0\",\n \"cors\": \"^2.8.5\",\n \"electron\": \"^17.2.0\",\n \"envify\": \"^4.0.0\",\n \"eslint\": \"^6.2.1\",\n \"eslint-config-standard\": \"^14.0.0\",\n \"eslint-plugin-import\": \"^2.18.2\",\n \"eslint-plugin-node\": \"^9.1.0\",\n \"eslint-plugin-promise\": \"^4.2.1\",\n \"eslint-plugin-standard\": \"^4.0.1\",\n \"express\": \"^4.16.2\",\n \"glob\": \"^7.1.7\",\n \"ink-docstrap\": \"^1.3.2\",\n \"inquirer\": \"^7.0.0\",\n \"is-docker\": \"^2.0.0\",\n \"jsdoc\": \"^3.5.5\",\n \"jsdoc-babel\": \"^0.5.0\",\n \"json-loader\": \"^0.5.7\",\n \"karma\": \"6.4.1\",\n \"karma-browserify\": \"^8.0.0\",\n \"karma-chrome-launcher\": \"^2.0.0\",\n \"karma-edgium-launcher\": \"^4.0.0-0\",\n \"karma-electron\": \"^6.1.0\",\n \"karma-firefox-launcher\": \"^1.3.0\",\n \"karma-htmlfile-reporter\": \"^0.3.8\",\n \"karma-junit-reporter\": \"^1.2.0\",\n \"karma-mocha\": \"^1.3.0\",\n \"karma-safari-launcher\": \"^1.0.0\",\n \"karma-spec-reporter\": \"0.0.32\",\n \"karma-typescript\": \"^5.5.1\",\n \"karma-typescript-es6-transform\": \"^5.5.1\",\n \"mocha\": \"^3.2.0\",\n \"mock-require\": \"^3.0.3\",\n \"ncp\": \"^2.0.0\",\n \"node-http-server\": \"^8.1.2\",\n \"npm-run-all\": \"^4.0.2\",\n \"nyc\": \"^15.1.0\",\n \"requirejs\": \"^2.3.3\",\n \"rimraf\": \"^2.6.1\",\n \"simple-git\": \"^1.126.0\",\n \"sinon\": \"^4.0.1\",\n \"ts-node\": \"4.0.1\",\n \"tslint\": \"5.8.0\",\n \"twilio\": \"^3.49.0\",\n \"twilio-release-tool\": \"^1.0.2\",\n \"typescript\": \"4.2.2\",\n \"uglify-js\": \"^2.8.22\",\n \"vinyl-fs\": \"^2.4.4\",\n \"vinyl-source-stream\": \"^1.1.0\",\n \"watchify\": \"^3.11.1\",\n \"webrtc-adapter\": \"^7.7.1\"\n },\n \"engines\": {\n \"node\": \">=0.12\"\n },\n \"license\": \"BSD-3-Clause\",\n \"main\": \"./es5/index.js\",\n \"types\": \"./tsdef/index.d.ts\",\n \"scripts\": {\n \"lint:js\": \"eslint ./lib ./test/*.js ./docker/**/*.js ./test/framework/*.js ./test/lib/*.js ./test/integration/** ./test/unit/** \",\n \"lint:ts\": \"eslint ./tsdef/*.ts ./lib/**/*.ts\",\n \"lint\": \"npm-run-all lint:js lint:ts\",\n \"printVersion\": \"node --version && npm --version\",\n \"test:unit\": \"npm-run-all printVersion build:es5 && nyc --report-dir=./coverage --include=lib/**/* --reporter=html --reporter=lcov --reporter=text mocha -r ts-node/register ./test/unit/*\",\n \"test:unit:quick\": \"nyc --report-dir=./coverage --include=lib/**/* --reporter=html --reporter=lcov mocha -r ts-node/register\",\n \"test:serversiderender\": \"mocha ./test/serversiderender/index.js\",\n \"test:integration:adapter\": \"node ./scripts/karma.js karma/integration.adapter.conf.js\",\n \"test:integration\": \"npm run build:es5 && node ./scripts/karma.js karma/integration.conf.js\",\n \"test:umd:install\": \"npm install puppeteer@19.2.2\",\n \"test:umd\": \"mocha ./test/umd/index.js\",\n \"test:crossbrowser:build:clean\": \"rimraf ./test/crossbrowser/lib ./test/crossbrowser/src/browser/index.js\",\n \"test:crossbrowser:build:lint\": \"cd ./test/crossbrowser && tslint --project tsconfig.json\",\n \"test:crossbrowser:build:tsc\": \"cd ./test/crossbrowser && tsc\",\n \"test:crossbrowser:build:browser\": \"cd ./test/crossbrowser && browserify lib/crossbrowser/src/browser/index.js > src/browser/index.js\",\n \"test:crossbrowser:build\": \"npm-run-all test:crossbrowser:build:*\",\n \"test:crossbrowser:test\": \"cd ./test/crossbrowser && mocha --compilers ts:ts-node/register test/integration/spec/**/*.ts\",\n \"test:crossbrowser\": \"npm-run-all test:crossbrowser:*\",\n \"test:sdkdriver:build:clean\": \"rimraf ./test/lib/sdkdriver/lib ./test/lib/sdkdriver/test/integration/browser/index.js\",\n \"test:sdkdriver:build:lint\": \"cd ./test/lib/sdkdriver && tslint --project tsconfig.json\",\n \"test:sdkdriver:build:tsc\": \"cd ./test/lib/sdkdriver && tsc --rootDir src\",\n \"test:sdkdriver:build\": \"npm-run-all test:sdkdriver:build:*\",\n \"test:sdkdriver:test:unit\": \"cd ./test/lib/sdkdriver && mocha --compilers ts:ts-node/register test/unit/spec/**/*.ts\",\n \"test:sdkdriver:test:integration:browser\": \"cd ./test/lib/sdkdriver/test/integration && browserify browser/browser.js > browser/index.js\",\n \"test:sdkdriver:test:integration:run\": \"cd ./test/lib/sdkdriver && mocha --compilers ts:ts-node/register test/integration/spec/**/*.ts\",\n \"test:sdkdriver:test:integration\": \"npm-run-all test:sdkdriver:test:integration:*\",\n \"test:sdkdriver:test\": \"npm-run-all test:sdkdriver:test:*\",\n \"test:sdkdriver\": \"npm-run-all test:sdkdriver:*\",\n \"test:framework:angular:install\": \"cd ./test/framework/twilio-video-angular && rimraf ./node_modules package-lock.json && npm install\",\n \"test:framework:angular:run\": \"mocha ./test/framework/twilio-video-angular.js\",\n \"test:framework:angular\": \"npm-run-all test:framework:angular:*\",\n \"test:framework:no-framework:run\": \"mocha ./test/framework/twilio-video-no-framework.js\",\n \"test:framework:no-framework\": \"npm-run-all test:framework:no-framework:*\",\n \"test:framework:react:install\": \"cd ./test/framework/twilio-video-react && rimraf ./node_modules package-lock.json && npm install\",\n \"test:framework:react:test\": \"node ./scripts/framework.js twilio-video-react\",\n \"test:framework:react:build\": \"cd ./test/framework/twilio-video-react && npm run build\",\n \"test:framework:react:run\": \"mocha ./test/framework/twilio-video-react.js\",\n \"test:framework:react\": \"npm-run-all test:framework:react:*\",\n \"test:framework:install\": \"npm install chromedriver && npm install selenium-webdriver && npm install geckodriver && npm install puppeteer\",\n \"test:framework\": \"npm-run-all test:framework:install test:framework:no-framework test:framework:react\",\n \"test\": \"npm-run-all test:unit test:integration\",\n \"build:es5\": \"rimraf ./es5 && mkdir -p es5 && tsc tsdef/twilio-video-tests.ts --noEmit --lib es2018,dom && tsc\",\n \"build:js\": \"node ./scripts/build.js ./src/twilio-video.js ./LICENSE.md ./dist/twilio-video.js\",\n \"build:min.js\": \"uglifyjs ./dist/twilio-video.js -o ./dist/twilio-video.min.js --comments \\\"/^! twilio-video.js/\\\" -b beautify=false,ascii_only=true\",\n \"build\": \"npm-run-all clean lint docs test:unit test:integration build:es5 build:js build:min.js test:umd\",\n \"build:quick\": \"npm-run-all clean lint docs build:es5 build:js build:min.js\",\n \"docs\": \"node ./scripts/docs.js ./dist/docs\",\n \"watch\": \"tsc -w\",\n \"clean\": \"rimraf ./coverage ./es5 ./dist\"\n },\n \"dependencies\": {\n \"events\": \"^3.3.0\",\n \"util\": \"^0.12.4\",\n \"ws\": \"^7.4.6\",\n \"xmlhttprequest\": \"^1.8.0\"\n },\n \"browser\": {\n \"ws\": \"./src/ws.js\",\n \"xmlhttprequest\": \"./src/xmlhttprequest.js\"\n }\n}\n", "'use strict';\n/* eslint-disable camelcase */\nconst packageInfo = require('../../package.json');\nmodule.exports.SDK_NAME = `${packageInfo.name}.js`;\nmodule.exports.SDK_VERSION = packageInfo.version;\nmodule.exports.SDP_FORMAT = 'unified';\nmodule.exports.hardwareDevicePublisheriPad = {\n hwDeviceManufacturer: 'Apple',\n hwDeviceModel: 'iPad',\n hwDeviceType: 'tablet',\n platformName: 'iOS'\n};\n\nmodule.exports.hardwareDevicePublisheriPhone = {\n hwDeviceManufacturer: 'Apple',\n hwDeviceModel: 'iPhone',\n hwDeviceType: 'mobile',\n platformName: 'iOS'\n};\n\nmodule.exports.DEFAULT_ENVIRONMENT = 'prod';\nmodule.exports.DEFAULT_REALM = 'us1';\nmodule.exports.DEFAULT_REGION = 'gll';\nmodule.exports.DEFAULT_LOG_LEVEL = 'warn';\nmodule.exports.DEFAULT_LOGGER_NAME = 'twilio-video';\nmodule.exports.WS_SERVER = (environment, region) => {\n region = region === 'gll' ? 'global' : encodeURIComponent(region);\n return environment === 'prod'\n ? `wss://${region}.vss.twilio.com/signaling`\n : `wss://${region}.vss.${environment}.twilio.com/signaling`;\n};\nmodule.exports.PUBLISH_MAX_ATTEMPTS = 5;\nmodule.exports.PUBLISH_BACKOFF_JITTER = 10;\nmodule.exports.PUBLISH_BACKOFF_MS = 20;\n\n/**\n * Returns the appropriate indefinite article (\"a\" | \"an\").\n * @param {string} word - The word which determines whether \"a\" | \"an\" is returned\n * @returns {string} \"a\" if word's first letter is a vowel, \"an\" otherwise\n */\nfunction article(word) {\n // NOTE(mmalavalli): This will not be accurate for words like \"hour\",\n // which have consonants as their first character, but are pronounced like\n // vowels. We can address this issue if the need arises.\n return ['a', 'e', 'i', 'o', 'u'].includes(word.toLowerCase()[0]) ? 'an' : 'a';\n}\n\nmodule.exports.typeErrors = {\n ILLEGAL_INVOKE(name, context) {\n return new TypeError(`Illegal call to ${name}: ${context}`);\n },\n INVALID_TYPE(name, type) {\n return new TypeError(`${name} must be ${article(type)} ${type}`);\n },\n INVALID_VALUE(name, values) {\n return new RangeError(`${name} must be one of ${values.join(', ')}`);\n },\n REQUIRED_ARGUMENT(name) {\n return new TypeError(`${name} must be specified`);\n }\n};\n\nmodule.exports.DEFAULT_FRAME_RATE = 24;\nmodule.exports.DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS = 10000;\n\nmodule.exports.DEFAULT_ICE_GATHERING_TIMEOUT_MS = 15000;\nmodule.exports.DEFAULT_SESSION_TIMEOUT_SEC = 30;\n\nmodule.exports.DEFAULT_NQ_LEVEL_LOCAL = 1;\nmodule.exports.DEFAULT_NQ_LEVEL_REMOTE = 0;\nmodule.exports.MAX_NQ_LEVEL = 3;\n\nmodule.exports.ICE_ACTIVITY_CHECK_PERIOD_MS = 1000;\nmodule.exports.ICE_INACTIVITY_THRESHOLD_MS = 3000;\n\nmodule.exports.iceRestartBackoffConfig = {\n factor: 1.1,\n min: 1,\n max: module.exports.DEFAULT_SESSION_TIMEOUT_SEC * 1000,\n jitter: 1\n};\n\nmodule.exports.reconnectBackoffConfig = {\n factor: 1.5,\n min: 80,\n jitter: 1\n};\n\nmodule.exports.subscriptionMode = {\n MODE_COLLABORATION: 'collaboration',\n MODE_GRID: 'grid',\n MODE_PRESENTATION: 'presentation'\n};\n\nmodule.exports.trackSwitchOffMode = {\n MODE_DISABLED: 'disabled',\n MODE_DETECTED: 'detected',\n MODE_PREDICTED: 'predicted'\n};\n\nmodule.exports.trackPriority = {\n PRIORITY_HIGH: 'high',\n PRIORITY_LOW: 'low',\n PRIORITY_STANDARD: 'standard'\n};\n\nmodule.exports.clientTrackSwitchOffControl = {\n MODE_AUTO: 'auto',\n MODE_MANUAL: 'manual'\n};\n\nmodule.exports.videoContentPreferencesMode = {\n MODE_AUTO: 'auto',\n MODE_MANUAL: 'manual'\n};\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst defaultGetLogger = require('../vendor/loglevel').getLogger;\nconst constants = require('./constants');\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME } = constants;\nconst E = require('./constants').typeErrors;\n\nlet deprecationWarningsByComponentConstructor;\n\nfunction getDeprecationWarnings(componentConstructor) {\n deprecationWarningsByComponentConstructor = deprecationWarningsByComponentConstructor || new Map();\n if (deprecationWarningsByComponentConstructor.has(componentConstructor)) {\n return deprecationWarningsByComponentConstructor.get(componentConstructor);\n }\n const deprecationWarnings = new Set();\n deprecationWarningsByComponentConstructor.set(componentConstructor, deprecationWarnings);\n return deprecationWarnings;\n}\n\n/**\n * Selectively outputs messages to console based on specified minimum module\n * specific log levels.\n *\n * NOTE: The values in the logLevels object passed to the constructor is changed\n * by subsequent calls to {@link Log#setLevels}.\n */\nclass Log {\n /**\n * Construct a new {@link Log} object.\n * @param {String} moduleName - Name of the logging module (webrtc/media/signaling)\n * @param {object} component - Component owning this instance of {@link Log}\n * @param {LogLevels} logLevels - Logging levels. See {@link LogLevels}\n * @param {String} loggerName - Name of the logger instance. Used when calling getLogger from loglevel module\n * @param {Function} [getLogger] - optional method used internally.\n */\n constructor(moduleName, component, logLevels, loggerName, getLogger) {\n if (typeof moduleName !== 'string') {\n throw E.INVALID_TYPE('moduleName', 'string');\n }\n\n if (!component) {\n throw E.REQUIRED_ARGUMENT('component');\n }\n\n if (typeof logLevels !== 'object') {\n logLevels = {};\n }\n\n getLogger = getLogger || defaultGetLogger;\n\n validateLogLevels(logLevels);\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _component: {\n value: component\n },\n _logLevels: {\n value: logLevels\n },\n _warnings: {\n value: new Set()\n },\n _loggerName: {\n get: function get() {\n let name = loggerName && typeof loggerName === 'string' ? loggerName : DEFAULT_LOGGER_NAME;\n\n if (!this._logLevelsEqual) {\n name = `${name}-${moduleName}`;\n }\n return name;\n }\n },\n _logger: {\n get: function get() {\n const logger = getLogger(this._loggerName);\n let level = this._logLevels[moduleName] || DEFAULT_LOG_LEVEL;\n\n // There is no 'off' in the logger module. It uses 'silent' instead\n level = level === 'off' ? 'silent' : level;\n\n logger.setDefaultLevel(level);\n return logger;\n }\n },\n _logLevelsEqual: {\n get: function get() {\n // True if all levels are the same\n return (new Set(Object.values(this._logLevels)).size) === 1;\n }\n },\n logLevel: {\n get: function get() {\n return Log.getLevelByName(logLevels[moduleName] || DEFAULT_LOG_LEVEL);\n }\n },\n name: { get: component.toString.bind(component) }\n });\n }\n\n /**\n * Get the log level (number) by its name (string)\n * @param {String} name - Name of the log level\n * @returns {Number} Requested log level\n * @throws {TwilioError} INVALID_LOG_LEVEL (32056)\n * @public\n */\n static getLevelByName(name) {\n if (!isNaN(name)) {\n return parseInt(name, 10);\n }\n name = name.toUpperCase();\n validateLogLevel(name);\n return Log[name];\n }\n\n /**\n * Create a child {@link Log} instance with this._logLevels\n * @param moduleName - Name of the logging module\n * @param component - Component owning this instance of {@link Log}\n * @returns {Log} this\n */\n createLog(moduleName, component) {\n let name = this._loggerName;\n // Grab the original logger name\n if (!this._logLevelsEqual) {\n name = name.substring(0, name.lastIndexOf('-'));\n }\n return new Log(moduleName, component, this._logLevels, name);\n }\n\n /**\n * Set new log levels.\n * This changes the levels for all its ancestors,\n * siblings, and children and descendants instances of {@link Log}.\n * @param {LogLevels} levels - New log levels\n * @throws {TwilioError} INVALID_ARGUMENT\n * @returns {Log} this\n */\n setLevels(levels) {\n validateLogLevels(levels);\n Object.assign(this._logLevels, levels);\n return this;\n }\n\n /**\n * Log a message using the logger method appropriate for the specified logLevel\n * @param {Number} logLevel - Log level of the message being logged\n * @param {Array} messages - Message(s) to log\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n log(logLevel, messages) {\n let name = Log._levels[logLevel];\n // eslint-disable-next-line no-use-before-define\n if (!name) { throw E.INVALID_VALUE('logLevel', LOG_LEVEL_VALUES); }\n\n name = name.toLowerCase();\n const prefix = [new Date().toISOString(), name, this.name];\n\n (this._logger[name] || function noop() {})(...prefix.concat(messages));\n\n return this;\n }\n\n /**\n * Log a debug message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n debug() {\n return this.log(Log.DEBUG, [].slice.call(arguments));\n }\n\n /**\n * Log a deprecation warning. Deprecation warnings are logged as warnings and\n * they are only ever logged once.\n * @param {String} deprecationWarning - The deprecation warning\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n deprecated(deprecationWarning) {\n const deprecationWarnings = getDeprecationWarnings(this._component.constructor);\n if (deprecationWarnings.has(deprecationWarning)) {\n return this;\n }\n deprecationWarnings.add(deprecationWarning);\n return this.warn(deprecationWarning);\n }\n\n /**\n * Log an info message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n info() {\n return this.log(Log.INFO, [].slice.call(arguments));\n }\n\n /**\n * Log a warn message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n warn() {\n return this.log(Log.WARN, [].slice.call(arguments));\n }\n\n /**\n * Log a warning once.\n * @param {String} warning\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n warnOnce(warning) {\n if (this._warnings.has(warning)) {\n return this;\n }\n this._warnings.add(warning);\n return this.warn(warning);\n }\n\n /**\n * Log an error message\n * @param {...String} messages - Message(s) to pass to the logger\n * @returns {Log} This instance of {@link Log}\n * @public\n */\n error() {\n return this.log(Log.ERROR, [].slice.call(arguments));\n }\n\n /**\n * Log an error message and throw an exception\n * @param {TwilioError} error - Error to throw\n * @param {String} customMessage - Custom message for the error\n * @public\n */\n throw(error, customMessage) {\n if (error.clone) {\n error = error.clone(customMessage);\n }\n\n this.log(Log.ERROR, error);\n throw error;\n }\n}\n\n// Singleton Constants\n/* eslint key-spacing:0 */\n/* istanbul ignore next */\nObject.defineProperties(Log, {\n DEBUG: { value: 0 },\n INFO: { value: 1 },\n WARN: { value: 2 },\n ERROR: { value: 3 },\n OFF: { value: 4 },\n _levels: {\n value: [\n 'DEBUG',\n 'INFO',\n 'WARN',\n 'ERROR',\n 'OFF',\n ]\n }\n});\n\nconst LOG_LEVELS_SET = {};\nconst LOG_LEVEL_VALUES = [];\n\nconst LOG_LEVEL_NAMES = Log._levels.map((level, i) => {\n LOG_LEVELS_SET[level] = true;\n LOG_LEVEL_VALUES.push(i);\n return level;\n});\n\nfunction validateLogLevel(level) {\n if (!(level in LOG_LEVELS_SET)) {\n throw E.INVALID_VALUE('level', LOG_LEVEL_NAMES);\n }\n}\n\nfunction validateLogLevels(levels) {\n Object.keys(levels).forEach(moduleName => {\n validateLogLevel(levels[moduleName].toUpperCase());\n });\n}\n\nmodule.exports = Log;\n", "'use strict';\n\nimport { AudioProcessor } from '../tsdef/AudioProcessor';\nimport { NoiseCancellationOptions } from '../tsdef/types';\n\nconst dynamicImport = require('./util/dynamicimport');\nconst Log = require('./util/log');\n\nconst PLUGIN_CONFIG = {\n krisp: {\n supportedVersion: '1.0.0',\n pluginFile: 'krispsdk.mjs'\n },\n rnnoise: {\n supportedVersion: '0.6.0',\n pluginFile: 'rnnoise_sdk.mjs'\n }\n};\n\n// AudioProcessor assumes following interface from the Plugin\ninterface NoiseCancellationPlugin {\n init(options: { rootDir: string }): Promise;\n isInitialized(): boolean;\n isConnected(): boolean;\n isEnabled(): boolean\n connect(input: MediaStream): MediaStream;\n disconnect(): void;\n enable(): void;\n disable(): void;\n destroy(): void;\n setLogging(enable: boolean):void;\n isSupported(audioContext: AudioContext):boolean;\n getVersion(): string;\n}\n\nconst ensureVersionSupported = ({ supportedVersion, plugin, log }: {supportedVersion: string, plugin: NoiseCancellationPlugin, log: typeof Log}) : void => {\n if (!plugin.getVersion || !plugin.isSupported) {\n throw new Error('Plugin does not export getVersion/isSupported api. Are you using old version of the plugin ?');\n }\n\n const pluginVersion = plugin.getVersion();\n log.debug(`Plugin Version = ${pluginVersion}`);\n const supportedVersions = supportedVersion.split('.').map(version => Number(version));\n const pluginVersions = pluginVersion.split('.').map(version => Number(version));\n if (supportedVersions.length !== 3 || pluginVersions.length !== 3) {\n throw new Error(`Unsupported Plugin version format: ${supportedVersion}, ${pluginVersion}`);\n }\n\n if (supportedVersions[0] !== pluginVersions[0]) {\n throw new Error(`Major version mismatch: [Plugin version ${pluginVersion}], [Supported Version ${supportedVersion}]`);\n }\n\n if (pluginVersions[1] < supportedVersions[1]) {\n throw new Error(`Minor version mismatch: [Plugin version ${pluginVersion}] < [Supported Version ${supportedVersion}]`);\n }\n\n const tempContext = new AudioContext();\n const isSupported = plugin.isSupported(tempContext);\n tempContext.close();\n if (!isSupported) {\n throw new Error('Noise Cancellation plugin is not supported on your browser');\n }\n};\n\nlet audioProcessors = new Map();\nexport async function createNoiseCancellationAudioProcessor(\n noiseCancellationOptions: NoiseCancellationOptions,\n log: typeof Log\n) : Promise {\n let audioProcessor = audioProcessors.get(noiseCancellationOptions.vendor);\n if (!audioProcessor) {\n let pluginConfig = PLUGIN_CONFIG[noiseCancellationOptions.vendor];\n if (!pluginConfig) {\n throw new Error(`Unsupported NoiseCancellationOptions.vendor: ${noiseCancellationOptions.vendor}`);\n }\n\n const { supportedVersion, pluginFile } = pluginConfig;\n const rootDir = noiseCancellationOptions.sdkAssetsPath;\n const sdkFilePath = `${rootDir}/${pluginFile}`;\n\n try {\n log.debug('loading noise cancellation sdk: ', sdkFilePath);\n const dynamicModule = await dynamicImport(sdkFilePath);\n log.debug('Loaded noise cancellation sdk:', dynamicModule);\n\n const plugin = dynamicModule.default as NoiseCancellationPlugin;\n ensureVersionSupported({\n supportedVersion,\n plugin,\n log\n });\n\n if (!plugin.isInitialized()) {\n log.debug('initializing noise cancellation sdk: ', rootDir);\n await plugin.init({ rootDir });\n log.debug('noise cancellation sdk initialized!');\n }\n\n audioProcessor = {\n vendor: noiseCancellationOptions.vendor,\n isInitialized: () => plugin.isInitialized(),\n isConnected: () => plugin.isConnected(),\n isEnabled: () => plugin.isEnabled(),\n disconnect: () => plugin.disconnect(),\n enable: () => plugin.enable(),\n disable: () => plugin.disable(),\n destroy: () => plugin.destroy(),\n setLogging: (enable: boolean) => plugin.setLogging(enable),\n connect: (sourceTrack: MediaStreamTrack) => {\n log.debug('connect: ', sourceTrack.id);\n if (plugin.isConnected()) {\n plugin.disconnect();\n }\n\n const mediaStream = plugin.connect(new MediaStream([sourceTrack]));\n if (!mediaStream) {\n throw new Error('Error connecting with noise cancellation sdk');\n }\n const cleanTrack = mediaStream.getAudioTracks()[0];\n if (!cleanTrack) {\n throw new Error('Error getting clean track from noise cancellation sdk');\n }\n plugin.enable();\n return cleanTrack;\n },\n };\n audioProcessors.set(noiseCancellationOptions.vendor, audioProcessor);\n\n } catch (er) {\n log.error(`Error loading noise cancellation sdk:${sdkFilePath}`, er);\n throw er;\n }\n }\n return audioProcessor;\n}\n", "'use strict';\n\nimport { NoiseCancellation, NoiseCancellationOptions, NoiseCancellationVendor } from '../../../tsdef/types';\nimport { AudioProcessor } from '../../../tsdef/AudioProcessor';\nimport { createNoiseCancellationAudioProcessor } from '../../noisecancellationadapter';\nconst Log = require('../../util/log');\n\n/**\n * {@link NoiseCancellation} interface provides methods to control noise cancellation at runtime. This interface is exposed\n * on {@link LocalAudioTrack} property `noiseCancellation`. It is available only when {@link NoiseCancellationOptions} are\n * specified when creating a {@link LocalAudioTrack}, and the plugin is successfully loaded.\n * @alias NoiseCancellation\n * @interface\n *\n * @example\n * const { connect, createLocalAudioTrack } = require('twilio-video');\n *\n * // Create a LocalAudioTrack with Krisp noise cancellation enabled.\n * const localAudioTrack = await createLocalAudioTrack({\n * noiseCancellationOptions: {\n * sdkAssetsPath: 'path/to/hosted/twilio/krisp/audio/plugin/1.0.0/dist',\n * vendor: 'krisp'\n * }\n * });\n *\n * if (!localAudioTrack.noiseCancellation) {\n * // If the Krisp audio plugin fails to load, then a warning message will be logged\n * // in the browser console, and the \"noiseCancellation\" property will be set to null.\n * // You can still use the LocalAudioTrack to join a Room. However, it will use the\n * // browser's noise suppression instead of the Krisp noise cancellation. Make sure\n * // the \"sdkAssetsPath\" provided in \"noiseCancellationOptions\" points to the correct\n * // hosted path of the plugin assets.\n * } else {\n * // Join a Room with the LocalAudioTrack.\n * const room = await connect('token', {\n * name: 'my-cool-room',\n * tracks: [localAudioTrack]\n * });\n *\n * if (!localAudioTrack.noiseCancellation.isEnabled) {\n * // Krisp noise cancellation is permanently disabled in Peer-to-Peer and Go Rooms.\n * }\n * }\n *\n * //\n * // Enable/disable noise cancellation.\n * // @param {boolean} enable - whether noise cancellation should be enabled\n * //\n * function setNoiseCancellation(enable) {\n * const { noiseCancellation } = localAudioTrack;\n * if (noiseCancellation) {\n * if (enable) {\n * // If enabled, then the LocalAudioTrack will use the Krisp noise\n * // cancellation instead of the browser's noise suppression.\n * noiseCancellation.enable();\n * } else {\n * // If disabled, then the LocalAudioTrack will use the browser's\n * // noise suppression instead of the Krisp noise cancellation.\n * noiseCancellation.disable();\n * }\n * }\n * }\n */\nexport class NoiseCancellationImpl implements NoiseCancellation {\n private _processor: AudioProcessor;\n private _sourceTrack: MediaStreamTrack;\n private _disabledPermanent: boolean;\n\n constructor(processor: AudioProcessor, originalTrack: MediaStreamTrack) {\n this._processor = processor;\n this._sourceTrack = originalTrack;\n this._disabledPermanent = false;\n }\n\n /**\n * Name of the noise cancellation vendor.\n * @type {NoiseCancellationVendor}\n */\n get vendor(): NoiseCancellationVendor {\n return this._processor.vendor;\n }\n\n /**\n * The underlying MediaStreamTrack of the {@link LocalAudioTrack}.\n * @type {MediaStreamTrack}\n */\n get sourceTrack(): MediaStreamTrack {\n return this._sourceTrack;\n }\n\n /**\n * Whether noise cancellation is enabled.\n * @type {boolean}\n */\n get isEnabled(): boolean {\n return this._processor.isEnabled();\n }\n\n /**\n * Enable noise cancellation.\n * @returns {Promise} Promise that resolves when the operation is complete\n * @throws {Error} Throws an error if noise cancellation is disabled permanently\n * for the {@link LocalAudioTrack}\n */\n enable() : Promise {\n if (this._disabledPermanent) {\n throw new Error(`${this.vendor} noise cancellation is disabled permanently for this track`);\n }\n\n this._processor.enable();\n return Promise.resolve();\n }\n\n /**\n * Disable noise cancellation.\n * @returns {Promise} Promise that resolves when the operation is complete\n */\n disable() : Promise {\n this._processor.disable();\n return Promise.resolve();\n }\n\n /**\n * @private\n */\n async reacquireTrack(reacquire: () => Promise) : Promise {\n const processorWasEnabled = this._processor.isEnabled();\n this._processor.disconnect();\n\n const track = await reacquire();\n this._sourceTrack = track;\n\n const processedTrack = await this._processor.connect(track);\n if (processorWasEnabled) {\n this._processor.enable();\n } else {\n this._processor.disable();\n }\n return processedTrack;\n }\n\n /**\n * @private\n */\n disablePermanently(): Promise {\n this._disabledPermanent = true;\n return this.disable();\n }\n\n\n /**\n * @private\n */\n stop(): void {\n this._processor.disconnect();\n this._sourceTrack.stop();\n }\n}\n\n\nexport async function applyNoiseCancellation(\n mediaStreamTrack: MediaStreamTrack,\n noiseCancellationOptions: NoiseCancellationOptions,\n log: typeof Log\n) : Promise<{ cleanTrack: MediaStreamTrack, noiseCancellation?: NoiseCancellation }> {\n try {\n const processor = await createNoiseCancellationAudioProcessor(noiseCancellationOptions, log);\n const cleanTrack = processor.connect(mediaStreamTrack);\n const noiseCancellation = new NoiseCancellationImpl(processor, mediaStreamTrack);\n return { cleanTrack, noiseCancellation };\n } catch (ex) {\n // in case of failures to load noise cancellation library just return original media stream.\n log.warn(`Failed to create noise cancellation. Returning normal audio track: ${ex}`);\n return { cleanTrack: mediaStreamTrack };\n }\n}\n", "'use strict';\n\n/**\n * Create a {@link Deferred}.\n * @returns {Deferred}\n */\nfunction defer() {\n const deferred = {};\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve;\n deferred.reject = reject;\n });\n return deferred;\n}\n\n/**\n * Copy a method from a `source` prototype onto a `wrapper` prototype. Invoking\n * the method on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} methodName\n * @returns {undefined}\n */\nfunction delegateMethod(source, wrapper, target, methodName) {\n if (methodName in wrapper) {\n // Skip any methods already set.\n return;\n } else if (methodName.match(/^on[a-z]+$/)) {\n // Skip EventHandlers (these are handled in the constructor).\n return;\n }\n\n\n let isProperty = false;\n try {\n const propDesc = Object.getOwnPropertyDescriptor(source, methodName);\n isProperty = propDesc && !!propDesc.get;\n } catch (error) {\n // its okay to eat failure here.\n }\n\n // NOTE(mpatwardhan):skip properties. we are only interested in overriding\n // functions. we do not even want to evaluate `typeof source[methodName]` for properties\n // because getter would get invoked, and they might have side effects.\n // For example RTCPeerConnection.peerIdentity is a property that returns a promise.\n // calling typeof RTCPeerConnection.peerIdentity, would leak a promise, and in case it rejects\n // we see errors.\n if (isProperty) {\n return;\n }\n\n let type;\n try {\n type = typeof source[methodName];\n } catch (error) {\n // NOTE(mroberts): Attempting to check the type of non-function members\n // on the prototype throws an error for some types.\n }\n\n if (type !== 'function') {\n // Skip non-function members.\n return;\n }\n\n /* eslint no-loop-func:0 */\n wrapper[methodName] = function() {\n return this[target][methodName].apply(this[target], arguments);\n };\n}\n\n/**\n * Copy methods from a `source` prototype onto a `wrapper` prototype. Invoking\n * the methods on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction delegateMethods(source, wrapper, target) {\n for (const methodName in source) {\n delegateMethod(source, wrapper, target, methodName);\n }\n}\n\n/**\n * Finds the items in list1 that are not in list2.\n * @param {Array<*>|Map<*>|Set<*>} list1\n * @param {Array<*>|Map<*>|Set<*>} list2\n * @returns {Set}\n */\nfunction difference(list1, list2) {\n list1 = Array.isArray(list1) ? new Set(list1) : new Set(list1.values());\n list2 = Array.isArray(list2) ? new Set(list2) : new Set(list2.values());\n\n const difference = new Set();\n\n list1.forEach(item => {\n if (!list2.has(item)) {\n difference.add(item);\n }\n });\n\n return difference;\n}\n\n/**\n * Map a list to an array of arrays, and return the flattened result.\n * @param {Array<*>|Set<*>|Map<*>} list\n * @param {function(*): Array<*>} mapFn\n * @returns Array<*>\n */\nfunction flatMap(list, mapFn) {\n const listArray = list instanceof Map || list instanceof Set\n ? Array.from(list.values())\n : list;\n\n return listArray.reduce((flattened, item) => flattened.concat(mapFn(item)), []);\n}\n\n/**\n * Get the browser's user agent, if available.\n * @returns {?string}\n */\nfunction getUserAgent() {\n return typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string'\n ? navigator.userAgent\n : null;\n}\n\n/**\n * Guess the browser.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {?string} browser - \"chrome\", \"firefox\", \"safari\", or null\n */\nfunction guessBrowser(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n if (/Chrome|CriOS/.test(userAgent)) {\n return 'chrome';\n }\n if (/Firefox|FxiOS/.test(userAgent)) {\n return 'firefox';\n }\n if (/Safari|iPhone|iPad|iPod/.test(userAgent)) {\n return 'safari';\n }\n return null;\n}\n\n/**\n * Guess the browser version.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {?{major: number, minor: number}}\n */\nfunction guessBrowserVersion(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n const prefix = {\n chrome: 'Chrome|CriOS',\n firefox: 'Firefox|FxiOS',\n safari: 'Version'\n }[guessBrowser(userAgent)];\n\n if (!prefix) {\n return null;\n }\n const regex = new RegExp(`(${prefix})/([^\\\\s]+)`);\n const [, , match] = userAgent.match(regex) || [];\n\n if (!match) {\n return null;\n }\n const versions = match.split('.').map(Number);\n return {\n major: isNaN(versions[0]) ? null : versions[0],\n minor: isNaN(versions[1]) ? null : versions[1]\n };\n}\n\n/**\n * Check whether the current browser is iOS Chrome.\n * @param {string} [userAgent=navigator.userAgent]\n * @returns {boolean}\n */\nfunction isIOSChrome(userAgent) {\n if (typeof userAgent === 'undefined') {\n userAgent = getUserAgent();\n }\n return (/Mobi/.test(userAgent) && guessBrowser() === 'chrome' && /iPad|iPhone|iPod/.test(userAgent));\n}\n\n/**\n * Intercept an event that might otherwise be proxied on an EventTarget.\n * @param {EventTarget} target\n * @param {string} type\n * @returns {void}\n */\nfunction interceptEvent(target, type) {\n let currentListener = null;\n Object.defineProperty(target, 'on' + type, {\n get: function() {\n return currentListener;\n },\n set: function(newListener) {\n if (currentListener) {\n this.removeEventListener(type, currentListener);\n }\n\n if (typeof newListener === 'function') {\n currentListener = newListener;\n this.addEventListener(type, currentListener);\n } else {\n currentListener = null;\n }\n }\n });\n}\n\n/**\n * This is a function for turning a Promise into the kind referenced in the\n * Legacy Interface Extensions section of the WebRTC spec.\n * @param {Promise<*>} promise\n * @param {function<*>} onSuccess\n * @param {function} onFailure\n * @returns {Promise}\n */\nfunction legacyPromise(promise, onSuccess, onFailure) {\n return onSuccess\n ? promise.then(onSuccess, onFailure)\n : promise;\n}\n\n/**\n * Make a unique ID.\n * @return {string}\n */\nfunction makeUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n/**\n * For each property name on the `source` prototype, add getters and/or setters\n * to `wrapper` that proxy to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction proxyProperties(source, wrapper, target) {\n Object.getOwnPropertyNames(source).forEach(propertyName => {\n proxyProperty(source, wrapper, target, propertyName);\n });\n}\n\n/**\n * For the property name on the `source` prototype, add a getter and/or setter\n * to `wrapper` that proxies to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} propertyName\n * @returns {undefined}\n */\nfunction proxyProperty(source, wrapper, target, propertyName) {\n if (propertyName in wrapper) {\n // Skip any properties already set.\n return;\n } else if (propertyName.match(/^on[a-z]+$/)) {\n Object.defineProperty(wrapper, propertyName, {\n value: null,\n writable: true\n });\n\n target.addEventListener(\n propertyName.slice(2),\n (...args) => wrapper.dispatchEvent(...args)\n );\n\n return;\n }\n\n Object.defineProperty(wrapper, propertyName, {\n enumerable: true,\n get: function() {\n return target[propertyName];\n }\n });\n}\n\n/**\n * Check whether native WebRTC APIs are supported.\n * @returns {boolean}\n */\nfunction support() {\n return typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.getUserMedia === 'function'\n && typeof RTCPeerConnection === 'function';\n}\n\n/**\n * Create a Set of supported codecs for a certain kind of media.\n * @param {'audio'|'video'} kind\n * @returns {Promise>}\n */\nfunction createSupportedCodecsSet(kind) {\n if (typeof RTCRtpSender !== 'undefined'\n && typeof RTCRtpSender.getCapabilities === 'function') {\n return Promise.resolve(new Set(\n RTCRtpSender\n .getCapabilities(kind)\n .codecs\n .map(({ mimeType }) => mimeType.split('/')[1].toLowerCase())\n ));\n }\n if (typeof RTCPeerConnection === 'undefined'\n || typeof RTCPeerConnection.prototype === 'undefined'\n || typeof RTCPeerConnection.prototype.addTransceiver !== 'function'\n || typeof RTCPeerConnection.prototype.close !== 'function'\n || typeof RTCPeerConnection.prototype.createOffer !== 'function') {\n return Promise.resolve(new Set());\n }\n const pc = new RTCPeerConnection();\n pc.addTransceiver(kind);\n return pc.createOffer().then(({ sdp }) => {\n pc.close();\n return new Set((sdp.match(/^a=rtpmap:.+$/gm) || [])\n .map(line => line.match(/^a=rtpmap:.+ ([^/]+)/)[1].toLowerCase()));\n }, () => {\n pc.close();\n return new Set();\n });\n}\n\n// NOTE(mmalavalli): Cache the supported audio and video codecs here.\nconst supportedCodecs = new Map();\n\n/**\n * Check whether a given codec for a certain kind of media is supported.\n * @param {AudioCodec|VideoCodec} codec\n * @param {'audio'|'video'} kind\n * @returns {Promise}\n */\nfunction isCodecSupported(codec, kind) {\n const codecs = supportedCodecs.get(kind);\n if (codecs) {\n return Promise.resolve(codecs.has(codec.toLowerCase()));\n }\n return createSupportedCodecsSet(kind).then(codecs => {\n supportedCodecs.set(kind, codecs);\n return codecs.has(codec.toLowerCase());\n });\n}\n\n/**\n * Clear cached supported codecs (unit tests only).\n */\nfunction clearCachedSupportedCodecs() {\n supportedCodecs.clear();\n}\n\n/**\n * @typedef {object} Deferred\n * @property {Promise} promise\n * @property {function} reject\n * @property {function} resolve\n */\n\nexports.clearCachedSupportedCodecs = clearCachedSupportedCodecs;\nexports.defer = defer;\nexports.delegateMethods = delegateMethods;\nexports.difference = difference;\nexports.flatMap = flatMap;\nexports.guessBrowser = guessBrowser;\nexports.guessBrowserVersion = guessBrowserVersion;\nexports.isCodecSupported = isCodecSupported;\nexports.isIOSChrome = isIOSChrome;\nexports.interceptEvent = interceptEvent;\nexports.legacyPromise = legacyPromise;\nexports.makeUUID = makeUUID;\nexports.proxyProperties = proxyProperties;\nexports.support = support;\n", "const SID_CHARS = '1234567890abcdef';\nconst SID_CHAR_LENGTH = 32;\n// copied from: https://code.hq.twilio.com/flex/monkey/blob/0fdce2b6c52d6be0b17a5cdb92f0c54f119b8ea8/src/client/lib/sid.ts#L39\n\n/**\n * Generates a random sid using given prefix.\n * @param {string} prefix\n * @returns string\n */\nfunction createSID(prefix) {\n let result = '';\n for (let i = 0; i < SID_CHAR_LENGTH; i++) {\n result += SID_CHARS.charAt(Math.floor(Math.random() * SID_CHARS.length));\n }\n return `${prefix}${result}`;\n}\n\nexports.sessionSID = createSID('SS');\nexports.createSID = createSID;\n\n", "'use strict';\n\n/**\n * @private\n * Represents a warning encountered when\n * interacting with one of Twilio's services.\n */\n// eslint-disable-next-line\nconst TwilioWarning = {\n recordingMediaLost: 'recording-media-lost'\n};\n\nmodule.exports = TwilioWarning;\n", "'use strict';\n\nconst constants = require('./constants');\nconst { typeErrors: E, trackPriority } = constants;\nconst util = require('../webrtc/util');\nconst { sessionSID } = require('./sid');\nconst TwilioWarning = require('./twiliowarning');\n\n/**\n * Return the given {@link LocalTrack} or a new {@link LocalTrack} for the\n * given MediaStreamTrack.\n * @param {LocalTrack|MediaStreamTrack} track\n * @param {object} options\n * @returns {LocalTrack}\n * @throws {TypeError}\n */\nfunction asLocalTrack(track, options) {\n if (track instanceof options.LocalAudioTrack\n || track instanceof options.LocalVideoTrack\n || track instanceof options.LocalDataTrack) {\n return track;\n }\n if (track instanceof options.MediaStreamTrack) {\n return track.kind === 'audio'\n ? new options.LocalAudioTrack(track, options)\n : new options.LocalVideoTrack(track, options);\n }\n /* eslint new-cap:0 */\n throw E.INVALID_TYPE('track', 'LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n}\n\n/**\n * Create a new {@link LocalTrackPublication} for the given {@link LocalTrack}.\n * @param {LocalTrack} track\n * @param {LocalTrackPublicationSignaling} signaling\n * @param {function(track: LocalTrackPublication): void} unpublish\n * @param {object} options\n */\nfunction asLocalTrackPublication(track, signaling, unpublish, options) {\n const LocalTrackPublication = {\n audio: options.LocalAudioTrackPublication,\n video: options.LocalVideoTrackPublication,\n data: options.LocalDataTrackPublication\n }[track.kind];\n return new LocalTrackPublication(signaling, track, unpublish, options);\n}\n\n/**\n * Capitalize a word.\n * @param {string} word\n * @returns {string} capitalized\n */\nfunction capitalize(word) {\n return word[0].toUpperCase() + word.slice(1);\n}\n\n/**\n * Log deprecation warnings for the given events of an EventEmitter.\n * @param {string} name\n * @param {EventEmitter} emitter\n * @param {Map} events\n * @param {Log} log\n */\nfunction deprecateEvents(name, emitter, events, log) {\n const warningsShown = new Set();\n emitter.on('newListener', function newListener(event) {\n if (events.has(event) && !warningsShown.has(event)) {\n log.deprecated(`${name}#${event} has been deprecated and scheduled for removal in twilio-video.js@2.0.0.${events.get(event)\n ? ` Use ${name}#${events.get(event)} instead.`\n : ''}`);\n warningsShown.add(event);\n }\n if (warningsShown.size >= events.size) {\n emitter.removeListener('newListener', newListener);\n }\n });\n}\n\n/**\n * Finds the items in list1 that are not in list2.\n * @param {Array<*>|Map<*>|Set<*>} list1\n * @param {Array<*>|Map<*>|Set<*>} list2\n * @returns {Set}\n */\nfunction difference(list1, list2) {\n list1 = Array.isArray(list1) ? new Set(list1) : new Set(list1.values());\n list2 = Array.isArray(list2) ? new Set(list2) : new Set(list2.values());\n\n const difference = new Set();\n\n list1.forEach(item => {\n if (!list2.has(item)) {\n difference.add(item);\n }\n });\n\n return difference;\n}\n\n/**\n * Filter out the keys in an object with a given value.\n * @param {object} object - Object to be filtered\n * @param {*} [filterValue] - Value to be filtered out; If not specified, then\n * filters out all keys which have an explicit value of \"undefined\"\n * @returns {object} - Filtered object\n */\nfunction filterObject(object, filterValue) {\n return Object.keys(object).reduce((filtered, key) => {\n if (object[key] !== filterValue) {\n filtered[key] = object[key];\n }\n return filtered;\n }, {});\n}\n\n/**\n * Map a list to an array of arrays, and return the flattened result.\n * @param {Array<*>|Set<*>|Map<*>} list\n * @param {function(*): Array<*>} [mapFn]\n * @returns Array<*>\n */\nfunction flatMap(list, mapFn) {\n const listArray = list instanceof Map || list instanceof Set\n ? Array.from(list.values())\n : list;\n\n mapFn = mapFn || function mapFn(item) {\n return item;\n };\n\n return listArray.reduce((flattened, item) => {\n const mapped = mapFn(item);\n return flattened.concat(mapped);\n }, []);\n}\n\n/**\n * Get the user agent string, or return \"Unknown\".\n * @returns {string}\n */\nfunction getUserAgent() {\n return typeof navigator !== 'undefined' && navigator.userAgent\n ? navigator.userAgent\n : 'Unknown';\n}\n\n/**\n * Get the platform component of the user agent string.\n * Example:\n * Input - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36\n * Output - macintosh\n * @returns {string}\n */\nfunction getPlatform() {\n const userAgent = getUserAgent();\n const [, match = 'unknown'] = userAgent.match(/\\(([^)]+)\\)/) || [];\n const [platform] = match.split(';').map(entry => entry.trim());\n return platform.toLowerCase();\n}\n\n/**\n * Create a unique identifier.\n * @returns {string}\n */\nfunction makeUUID() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n/**\n * Ensure that the given function is called once per tick.\n * @param {function} fn - Function to be executed\n * @returns {function} - Schedules the given function to be called on the next tick\n */\nfunction oncePerTick(fn) {\n let timeout = null;\n\n function nextTick() {\n timeout = null;\n fn();\n }\n\n return function scheduleNextTick() {\n if (timeout) {\n clearTimeout(timeout);\n }\n timeout = setTimeout(nextTick);\n };\n}\n\nfunction promiseFromEvents(operation, eventEmitter, successEvent, failureEvent) {\n return new Promise((resolve, reject) => {\n function onSuccess() {\n const args = [].slice.call(arguments);\n if (failureEvent) {\n eventEmitter.removeListener(failureEvent, onFailure);\n }\n resolve(...args);\n }\n function onFailure() {\n const args = [].slice.call(arguments);\n eventEmitter.removeListener(successEvent, onSuccess);\n reject(...args);\n }\n eventEmitter.once(successEvent, onSuccess);\n if (failureEvent) {\n eventEmitter.once(failureEvent, onFailure);\n }\n operation();\n });\n}\n\n/**\n * Traverse down multiple nodes on an object and return null if\n * any link in the path is unavailable.\n * @param {Object} obj - Object to traverse\n * @param {String} path - Path to traverse. Period-separated.\n * @returns {Any|null}\n */\nfunction getOrNull(obj, path) {\n return path.split('.').reduce((output, step) => {\n if (!output) { return null; }\n return output[step];\n }, obj);\n}\n\n/**\n * @typedef {object} Deferred\n * @property {Promise} promise\n * @property {function} reject\n * @property {function} resolve\n */\n\n/**\n * Create a {@link Deferred}.\n * @returns {Deferred}\n */\nfunction defer() {\n const deferred = {};\n deferred.promise = new Promise((resolve, reject) => {\n deferred.resolve = resolve;\n deferred.reject = reject;\n });\n return deferred;\n}\n\n/**\n * Copy a method from a `source` prototype onto a `wrapper` prototype. Invoking\n * the method on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} methodName\n * @returns {undefined}\n */\nfunction delegateMethod(source, wrapper, target, methodName) {\n if (methodName in wrapper) {\n // Skip any methods already set.\n return;\n } else if (methodName.match(/^on[a-z]+$/)) {\n // Skip EventHandlers (these are handled in the constructor).\n return;\n }\n\n let type;\n try {\n type = typeof source[methodName];\n } catch (error) {\n // NOTE(mroberts): Attempting to check the type of non-function members\n // on the prototype throws an error for some types.\n }\n\n if (type !== 'function') {\n // Skip non-function members.\n return;\n }\n\n /* eslint no-loop-func:0 */\n wrapper[methodName] = function(...args) {\n return this[target][methodName](...args);\n };\n}\n\n/**\n * Copy methods from a `source` prototype onto a `wrapper` prototype. Invoking\n * the methods on the `wrapper` prototype will invoke the corresponding method\n * on an instance accessed by `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction delegateMethods(source, wrapper, target) {\n for (const methodName in source) {\n delegateMethod(source, wrapper, target, methodName);\n }\n}\n\n/**\n * Determine whether two values are deeply equal.\n * @param {*} val1\n * @param {*} val2\n * @returns {boolean}\n */\nfunction isDeepEqual(val1, val2) {\n if (val1 === val2) {\n return true;\n }\n if (typeof val1 !== typeof val2) {\n return false;\n }\n if (val1 === null) {\n return val2 === null;\n }\n if (val2 === null) {\n return false;\n }\n if (Array.isArray(val1)) {\n return Array.isArray(val2)\n && val1.length === val2.length\n && val1.every((val, i) => isDeepEqual(val, val2[i]));\n }\n if (typeof val1 === 'object') {\n const val1Keys = Object.keys(val1).sort();\n const val2Keys = Object.keys(val2).sort();\n return !Array.isArray(val2)\n && isDeepEqual(val1Keys, val2Keys)\n && val1Keys.every(key => isDeepEqual(val1[key], val2[key]));\n }\n return false;\n}\n\n/**\n * Whether the given argument is a non-array object.\n * @param {*} object\n * @return {boolean}\n */\nfunction isNonArrayObject(object) {\n return typeof object === 'object' && !Array.isArray(object);\n}\n\n/**\n * For each property name on the `source` prototype, add getters and/or setters\n * to `wrapper` that proxy to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @returns {undefined}\n */\nfunction proxyProperties(source, wrapper, target) {\n Object.getOwnPropertyNames(source).forEach(propertyName => {\n proxyProperty(source, wrapper, target, propertyName);\n });\n}\n\n/**\n * For the property name on the `source` prototype, add a getter and/or setter\n * to `wrapper` that proxies to `target`.\n * @param {object} source\n * @param {object} wrapper\n * @param {string} target\n * @param {string} propertyName\n * @returns {undefined}\n */\nfunction proxyProperty(source, wrapper, target, propertyName) {\n if (propertyName in wrapper) {\n // Skip any properties already set.\n return;\n } else if (propertyName.match(/^on[a-z]+$/)) {\n Object.defineProperty(wrapper, propertyName, {\n value: null,\n writable: true\n });\n\n target.addEventListener(propertyName.slice(2), function(...args) {\n wrapper.dispatchEvent(...args);\n });\n\n return;\n }\n\n Object.defineProperty(wrapper, propertyName, {\n enumerable: true,\n get() {\n return target[propertyName];\n }\n });\n}\n\n/**\n * This is a function for turning a Promise into the kind referenced in the\n * Legacy Interface Extensions section of the WebRTC spec.\n * @param {Promise<*>} promise\n * @param {function<*>} onSuccess\n * @param {function} onFailure\n * @returns {Promise}\n */\nfunction legacyPromise(promise, onSuccess, onFailure) {\n if (onSuccess) {\n return promise.then(result => {\n onSuccess(result);\n }, error => {\n onFailure(error);\n });\n }\n return promise;\n}\n\n/**\n * Build the {@link LogLevels} object.\n * @param {String|LogLevel} logLevel - Log level name or object\n * @returns {LogLevels}\n */\nfunction buildLogLevels(logLevel) {\n if (typeof logLevel === 'string') {\n return {\n default: logLevel,\n media: logLevel,\n signaling: logLevel,\n webrtc: logLevel\n };\n }\n return logLevel;\n}\n\n/**\n * Get the {@link Track}'s derived class name\n * @param {Track} track\n * @param {?boolean} [local=undefined]\n * @returns {string}\n */\nfunction trackClass(track, local) {\n local = local ? 'Local' : '';\n return `${local + (track.kind || '').replace(/\\w{1}/, m => m.toUpperCase())}Track`;\n}\n\n/**\n * Get the {@link TrackPublication}'s derived class name\n * @param {TrackPublication} publication\n * @param {?boolean} [local=undefined]\n * @returns {string}\n */\nfunction trackPublicationClass(publication, local) {\n local = local ? 'Local' : '';\n return `${local + (publication.kind || '').replace(/\\w{1}/, m => m.toUpperCase())}TrackPublication`;\n}\n\n/**\n * Sets all underscore-prefixed properties on `object` non-enumerable.\n * @param {Object} object\n * @returns {void}\n */\nfunction hidePrivateProperties(object) {\n Object.getOwnPropertyNames(object).forEach(name => {\n if (name.startsWith('_')) {\n hideProperty(object, name);\n }\n });\n}\n\n/**\n * Creates a new subclass which, in the constructor, sets all underscore-prefixed\n * properties and the given public properties non-enumerable. This is useful for\n * patching up classes like EventEmitter which may set properties like `_events`\n * and `domain`.\n * @param {Function} klass\n * @param {Array} props\n * @returns {Function} subclass\n */\nfunction hidePrivateAndCertainPublicPropertiesInClass(klass, props) {\n // NOTE(mroberts): We do this to avoid giving the class a name.\n return class extends klass {\n constructor(...args) {\n super(...args);\n hidePrivateProperties(this);\n hidePublicProperties(this, props);\n }\n };\n}\n\n/**\n * Hide a property of an object.\n * @param {object} object\n * @param {string} name\n */\nfunction hideProperty(object, name) {\n const descriptor = Object.getOwnPropertyDescriptor(object, name);\n descriptor.enumerable = false;\n Object.defineProperty(object, name, descriptor);\n}\n\n/**\n * Hide the given public properties of an object.\n * @param {object} object\n * @param {Array} [props=[]]\n */\nfunction hidePublicProperties(object, props = []) {\n props.forEach(name => {\n // eslint-disable-next-line no-prototype-builtins\n if (object.hasOwnProperty(name)) {\n hideProperty(object, name);\n }\n });\n}\n\n/**\n * Convert an Array of values to an Array of JSON values by calling\n * `valueToJSON` on each value.\n * @param {Array<*>} array\n * @returns {Array<*>}\n */\nfunction arrayToJSON(array) {\n return array.map(valueToJSON);\n}\n\n/**\n * Convert a Set of values to an Array of JSON values by calling `valueToJSON`\n * on each value.\n * @param {Set<*>} set\n * @returns {Array<*>}\n */\nfunction setToJSON(set) {\n return arrayToJSON([...set]);\n}\n\n/**\n * Convert a Map from strings to values to an object of JSON values by calling\n * `valueToJSON` on each value.\n * @param {Map} map\n * @returns {object}\n */\nfunction mapToJSON(map) {\n return [...map.entries()].reduce((json, [key, value]) => {\n return Object.assign({ [key]: valueToJSON(value) }, json);\n }, {});\n}\n\n/**\n * Convert an object to a JSON value by calling `valueToJSON` on its enumerable\n * keys.\n * @param {object} object\n * @returns {object}\n */\nfunction objectToJSON(object) {\n return Object.entries(object).reduce((json, [key, value]) => {\n return Object.assign({ [key]: valueToJSON(value) }, json);\n }, {});\n}\n\n/**\n * Convert a value into a JSON value.\n * @param {*} value\n * @returns {*}\n */\nfunction valueToJSON(value) {\n if (Array.isArray(value)) {\n return arrayToJSON(value);\n } else if (value instanceof Set) {\n return setToJSON(value);\n } else if (value instanceof Map) {\n return mapToJSON(value);\n } else if (value && typeof value === 'object') {\n return objectToJSON(value);\n }\n return value;\n}\n\nfunction createRoomConnectEventPayload(connectOptions) {\n function boolToString(val) {\n return val ? 'true' : 'false';\n }\n const payload = {\n sessionSID,\n\n // arrays props converted to lengths.\n iceServers: (connectOptions.iceServers || []).length,\n audioTracks: (connectOptions.tracks || []).filter(track => track.kind === 'audio').length,\n videoTracks: (connectOptions.tracks || []).filter(track => track.kind === 'video').length,\n dataTracks: (connectOptions.tracks || []).filter(track => track.kind === 'data').length,\n };\n\n // boolean properties.\n [['audio'], ['automaticSubscription'], ['enableDscp'], ['eventListener'], ['preflight'], ['video'], ['dominantSpeaker', 'enableDominantSpeaker']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n payload[eventProp] = boolToString(!!connectOptions[prop]);\n });\n\n // numbers properties.\n [['maxVideoBitrate'], ['maxAudioBitrate']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n if (typeof connectOptions[prop] === 'number') {\n payload[eventProp] = connectOptions[prop];\n } else if (!isNaN(Number(connectOptions[prop]))) {\n payload[eventProp] = Number(connectOptions[prop]);\n }\n });\n\n // string properties.\n [['iceTransportPolicy'], ['region'], ['name', 'roomName']].forEach(([prop, eventProp]) => {\n eventProp = eventProp || prop;\n if (typeof connectOptions[prop] === 'string') {\n payload[eventProp] = connectOptions[prop];\n } else if (typeof connectOptions[prop] === 'number' && prop === 'name') {\n payload[eventProp] = connectOptions[prop].toString();\n }\n });\n\n // array props stringified.\n ['preferredAudioCodecs', 'preferredVideoCodecs'].forEach(prop => {\n if (prop in connectOptions) {\n payload[prop] = JSON.stringify(connectOptions[prop]);\n }\n });\n\n if ('networkQuality' in connectOptions) {\n payload.networkQualityConfiguration = {};\n if (isNonArrayObject(connectOptions.networkQuality)) {\n ['local', 'remote'].forEach(prop => {\n if (typeof connectOptions.networkQuality[prop] === 'number') {\n payload.networkQualityConfiguration[prop] = connectOptions.networkQuality[prop];\n }\n });\n } else {\n payload.networkQualityConfiguration.remote = 0;\n payload.networkQualityConfiguration.local = connectOptions.networkQuality ? 1 : 0;\n }\n }\n\n if (connectOptions.bandwidthProfile && connectOptions.bandwidthProfile.video) {\n const videoBPOptions = connectOptions.bandwidthProfile.video || {};\n payload.bandwidthProfileOptions = {};\n ['mode', 'maxTracks', 'trackSwitchOffMode', 'dominantSpeakerPriority', 'maxSubscriptionBitrate', 'renderDimensions', 'contentPreferencesMode', 'clientTrackSwitchOffControl'].forEach(prop => {\n if (typeof videoBPOptions[prop] === 'number' || typeof videoBPOptions[prop] === 'string') {\n payload.bandwidthProfileOptions[prop] = videoBPOptions[prop];\n } else if (typeof videoBPOptions[prop] === 'boolean') {\n payload.bandwidthProfileOptions[prop] = boolToString(videoBPOptions[prop]);\n } else if (typeof videoBPOptions[prop] === 'object') {\n payload.bandwidthProfileOptions[prop] = JSON.stringify(videoBPOptions[prop]);\n }\n });\n }\n\n return {\n group: 'room',\n name: 'connect',\n level: 'info',\n payload\n };\n}\n\n/**\n * Create the bandwidth profile payload included in an RSP connect message.\n * @param {BandwidthProfileOptions} bandwidthProfile\n * @returns {object}\n */\nfunction createBandwidthProfilePayload(bandwidthProfile) {\n return createRSPPayload(bandwidthProfile, [\n { prop: 'video', type: 'object', transform: createBandwidthProfileVideoPayload }\n ]);\n}\n\n/**\n * Create the bandwidth profile video payload included in an RSP connect message.\n * @param {VideoBandwidthProfileOptions} bandwidthProfileVideo\n * @returns {object}\n */\nfunction createBandwidthProfileVideoPayload(bandwidthProfileVideo) {\n return createRSPPayload(bandwidthProfileVideo, [\n { prop: 'dominantSpeakerPriority', type: 'string', payloadProp: 'active_speaker_priority' },\n { prop: 'maxSubscriptionBitrate', type: 'number', payloadProp: 'max_subscription_bandwidth' },\n { prop: 'maxTracks', type: 'number', payloadProp: 'max_tracks' },\n { prop: 'mode', type: 'string' },\n { prop: 'renderDimensions', type: 'object', payloadProp: 'render_dimensions', transform: createRenderDimensionsPayload },\n { prop: 'trackSwitchOffMode', type: 'string', payloadProp: 'track_switch_off' }\n ]);\n}\n\n/**\n * Create the Media Signaling payload included in an RSP connect message.\n * @param {boolean} dominantSpeaker - whether to enable the Dominant Speaker\n * protocol or not\n * @param {boolean} networkQuality - whether to enable the Network Quality\n * protocol or not\n * @param {boolean} trackPriority - whether to enable the Track Priority\n * protocol or not\n * @param {boolean} trackSwitchOff - whether to enable the Track Switch-Off\n * protocol or not.\n * @param {boolean} renderHints - whether to enable the renderHints\n * protocol or not.\n * @returns {object}\n */\nfunction createMediaSignalingPayload(dominantSpeaker, networkQuality, trackPriority, trackSwitchOff, adaptiveSimulcast, renderHints) {\n const transports = { transports: [{ type: 'data-channel' }] };\n return Object.assign(\n dominantSpeaker\n // eslint-disable-next-line\n ? { active_speaker: transports }\n : {},\n networkQuality\n // eslint-disable-next-line\n ? { network_quality: transports }\n : {},\n renderHints\n // eslint-disable-next-line\n ? { render_hints: transports }\n : {},\n adaptiveSimulcast\n // eslint-disable-next-line\n ? { publisher_hints: transports }\n : {},\n trackPriority\n // eslint-disable-next-line\n ? { track_priority: transports }\n : {},\n trackSwitchOff\n // eslint-disable-next-line\n ? { track_switch_off: transports }\n : {}\n );\n}\n\n/**\n * Create {@link VideoTrack.Dimensions} RSP payload.\n * @param {VideoTrack.Dimensions} [dimensions]\n * @returns {object}\n */\nfunction createDimensionsPayload(dimensions) {\n return createRSPPayload(dimensions, [\n { prop: 'height', type: 'number' },\n { prop: 'width', type: 'number' }\n ]);\n}\n\n/**\n * Create {@link VideoRenderDimensions} RSP payload.\n * @param renderDimensions\n * @returns {object}\n */\nfunction createRenderDimensionsPayload(renderDimensions) {\n const { PRIORITY_HIGH, PRIORITY_LOW, PRIORITY_STANDARD } = trackPriority;\n return createRSPPayload(renderDimensions, [\n { prop: PRIORITY_HIGH, type: 'object', transform: createDimensionsPayload },\n { prop: PRIORITY_LOW, type: 'object', transform: createDimensionsPayload },\n { prop: PRIORITY_STANDARD, type: 'object', transform: createDimensionsPayload }\n ]);\n}\n\n/**\n * Create an RSP payload for the given object.\n * @param {object} object - object for which RSP payload is to be generated\n * @param {Array} propConversions - conversion rules for object properties;\n * they specify how object properties should be converted to their corresponding\n * RSP payload properties\n * @returns {object}\n */\nfunction createRSPPayload(object, propConversions) {\n return propConversions.reduce((payload, { prop, type, payloadProp = prop, transform = x => x }) => {\n return typeof object[prop] === type\n ? Object.assign({ [payloadProp]: transform(object[prop]) }, payload)\n : payload;\n }, {});\n}\n\n/**\n * Create the subscribe payload included in an RSP connect/update message.\n * @param {boolean} automaticSubscription - whether to subscribe to all RemoteTracks\n * @returns {object}\n */\nfunction createSubscribePayload(automaticSubscription) {\n return {\n rules: [{\n type: automaticSubscription ? 'include' : 'exclude',\n all: true\n }],\n revision: 1\n };\n}\n\nfunction createMediaWarningsPayload(notifyWarnings) {\n const mediaWarnings = {\n [TwilioWarning.recordingMediaLost]: 'recordings'\n };\n return notifyWarnings\n .map(twilioWarningName => mediaWarnings[twilioWarningName])\n .filter(name => !!name);\n}\n\n/**\n * Add random jitter to a given value in the range [-jitter, jitter].\n * @private\n * @param {number} value\n * @param {number} jitter\n * @returns {number} value + random(-jitter, +jitter)\n */\nfunction withJitter(value, jitter) {\n const rand = Math.random();\n return value - jitter + Math.floor(2 * jitter * rand + 0.5);\n}\n\n/**\n * Checks if the a number is in the range [min, max].\n * @private\n * @param {num} num\n * @param {number} min\n * @param {number} max\n * @return {boolean}\n */\nfunction inRange(num, min, max) {\n return min <= num && num <= max;\n}\n\n/**\n * returns true if given MediaStreamTrack is a screen share track\n * @private\n * @param {MediaStreamTrack} track\n * @returns {boolean}\n */\nfunction isChromeScreenShareTrack(track) {\n // NOTE(mpatwardhan): Chrome creates screen share tracks with label like: \"screen:69734272*\"\n // we will check for label that starts with \"screen:D\" where D being a digit.\n return util.guessBrowser() === 'chrome' && track.kind === 'video' && 'displaySurface' in track.getSettings();\n}\n\n\n/**\n * returns true if given MediaStreamTrack is a user media track\n * @private\n * @param {MediaStreamTrack} track\n * @returns {boolean}\n */\nfunction isUserMediaTrack(track) {\n // NOTE(mpatwardhan): tracks obtained from getUserMedia have a deviceId in its settings.\n return typeof track.getSettings().deviceId === 'string';\n}\n\n/**\n * Returns a promise that resolve after timeoutMS have passed.\n * @param {number} timeoutMS - time to wait in milliseconds.\n * @returns {Promise}\n */\nfunction waitForSometime(timeoutMS = 10) {\n return new Promise(resolve => setTimeout(resolve, timeoutMS));\n}\n\n/**\n * Returns a promise that resolve after event is received\n * @returns {Promise}\n */\nfunction waitForEvent(eventTarget, event) {\n return new Promise(resolve => {\n eventTarget.addEventListener(event, function onevent(e) {\n eventTarget.removeEventListener(event, onevent);\n resolve(e);\n });\n });\n}\n\nexports.constants = constants;\nexports.createBandwidthProfilePayload = createBandwidthProfilePayload;\nexports.createMediaSignalingPayload = createMediaSignalingPayload;\nexports.createMediaWarningsPayload = createMediaWarningsPayload;\nexports.createRoomConnectEventPayload = createRoomConnectEventPayload;\nexports.createSubscribePayload = createSubscribePayload;\nexports.asLocalTrack = asLocalTrack;\nexports.asLocalTrackPublication = asLocalTrackPublication;\nexports.capitalize = capitalize;\nexports.deprecateEvents = deprecateEvents;\nexports.difference = difference;\nexports.filterObject = filterObject;\nexports.flatMap = flatMap;\nexports.getPlatform = getPlatform;\nexports.getUserAgent = getUserAgent;\nexports.hidePrivateProperties = hidePrivateProperties;\nexports.hidePrivateAndCertainPublicPropertiesInClass = hidePrivateAndCertainPublicPropertiesInClass;\nexports.isDeepEqual = isDeepEqual;\nexports.isNonArrayObject = isNonArrayObject;\nexports.inRange = inRange;\nexports.makeUUID = makeUUID;\nexports.oncePerTick = oncePerTick;\nexports.promiseFromEvents = promiseFromEvents;\nexports.getOrNull = getOrNull;\nexports.defer = defer;\nexports.delegateMethods = delegateMethods;\nexports.proxyProperties = proxyProperties;\nexports.legacyPromise = legacyPromise;\nexports.buildLogLevels = buildLogLevels;\nexports.trackClass = trackClass;\nexports.trackPublicationClass = trackPublicationClass;\nexports.valueToJSON = valueToJSON;\nexports.withJitter = withJitter;\nexports.isChromeScreenShareTrack = isChromeScreenShareTrack;\nexports.isUserMediaTrack = isUserMediaTrack;\nexports.waitForSometime = waitForSometime;\nexports.waitForEvent = waitForEvent;\n", "/* globals RTCPeerConnection, RTCRtpTransceiver */\n\n'use strict';\n\nconst { flatMap, guessBrowser } = require('./');\n\n// NOTE(mmalavalli): We cache Chrome's sdpSemantics support in order to prevent\n// instantiation of more than one RTCPeerConnection.\nlet isSdpSemanticsSupported = null;\n\n/**\n * Check if Chrome supports specifying sdpSemantics for an RTCPeerConnection.\n * @return {boolean}\n */\nfunction checkIfSdpSemanticsIsSupported() {\n if (typeof isSdpSemanticsSupported === 'boolean') {\n return isSdpSemanticsSupported;\n }\n if (typeof RTCPeerConnection === 'undefined') {\n isSdpSemanticsSupported = false;\n return isSdpSemanticsSupported;\n }\n try {\n // eslint-disable-next-line no-new\n new RTCPeerConnection({ sdpSemantics: 'foo' });\n isSdpSemanticsSupported = false;\n } catch (e) {\n isSdpSemanticsSupported = true;\n }\n return isSdpSemanticsSupported;\n}\n\n// NOTE(mmalavalli): We cache Chrome's SDP format in order to prevent\n// instantiation of more than one RTCPeerConnection.\nlet chromeSdpFormat = null;\n\n/**\n * Clear cached Chrome's SDP format\n */\nfunction clearChromeCachedSdpFormat() {\n chromeSdpFormat = null;\n}\n\n/**\n * Get Chrome's default SDP format.\n * @returns {'planb'|'unified'}\n */\nfunction getChromeDefaultSdpFormat() {\n if (!chromeSdpFormat) {\n if (typeof RTCPeerConnection !== 'undefined'\n && 'addTransceiver' in RTCPeerConnection.prototype) {\n const pc = new RTCPeerConnection();\n try {\n pc.addTransceiver('audio');\n chromeSdpFormat = 'unified';\n } catch (e) {\n chromeSdpFormat = 'planb';\n }\n pc.close();\n } else {\n chromeSdpFormat = 'planb';\n }\n }\n return chromeSdpFormat;\n}\n\n/**\n * Get Chrome's SDP format.\n * @param {'plan-b'|'unified-plan'} [sdpSemantics]\n * @returns {'planb'|'unified'}\n */\nfunction getChromeSdpFormat(sdpSemantics) {\n if (!sdpSemantics || !checkIfSdpSemanticsIsSupported()) {\n return getChromeDefaultSdpFormat();\n }\n return {\n 'plan-b': 'planb',\n 'unified-plan': 'unified'\n }[sdpSemantics];\n}\n\n/**\n * Get Safari's default SDP format.\n * @returns {'planb'|'unified'}\n */\nfunction getSafariSdpFormat() {\n return typeof RTCRtpTransceiver !== 'undefined'\n && 'currentDirection' in RTCRtpTransceiver.prototype\n ? 'unified'\n : 'planb';\n}\n\n/**\n * Get the browser's default SDP format.\n * @param {'plan-b'|'unified-plan'} [sdpSemantics]\n * @returns {'planb'|'unified'}\n */\nfunction getSdpFormat(sdpSemantics) {\n return {\n chrome: getChromeSdpFormat(sdpSemantics),\n firefox: 'unified',\n safari: getSafariSdpFormat()\n }[guessBrowser()] || null;\n}\n\n/**\n * Match a pattern across lines, returning the first capture group for any\n * matches.\n * @param {string} pattern\n * @param {string} lines\n * @returns {Set} matches\n */\nfunction getMatches(pattern, lines) {\n const matches = lines.match(new RegExp(pattern, 'gm')) || [];\n return matches.reduce((results, line) => {\n const match = line.match(new RegExp(pattern));\n return match ? results.add(match[1]) : results;\n }, new Set());\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from an SDP.\n * @param {string} pattern\n * @param {string} sdp\n * @returns {Set}\n */\nfunction getTrackIds(pattern, sdp) {\n return getMatches(pattern, sdp);\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Set} trackIds\n */\nfunction getPlanBTrackIds(sdp) {\n return getTrackIds('^a=ssrc:[0-9]+ +msid:.+ +(.+) *$', sdp);\n}\n\n/**\n * Get a Set of MediaStreamTrack IDs from a Unified Plan SDP.\n * @param {string} sdp - Unified Plan SDP\n * @returns {Set} trackIds\n */\nfunction getUnifiedPlanTrackIds(sdp) {\n return getTrackIds('^a=msid:.+ +(.+) *$', sdp);\n}\n\n/**\n * Get a Set of SSRCs for a MediaStreamTrack from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @param {string} trackId - MediaStreamTrack ID\n * @returns {Set}\n */\nfunction getPlanBSSRCs(sdp, trackId) {\n const pattern = `^a=ssrc:([0-9]+) +msid:[^ ]+ +${trackId} *$`;\n return getMatches(pattern, sdp);\n}\n\n/**\n * Get the m= sections of a particular kind and direction from an sdp.\n * @param {string} sdp - sdp string\n * @param {string} [kind] - Pattern for matching kind\n * @param {string} [direction] - Pattern for matching direction\n * @returns {Array} mediaSections\n */\nfunction getMediaSections(sdp, kind = '.*', direction = '.*') {\n return sdp.split('\\r\\nm=').slice(1).map(mediaSection => `m=${mediaSection}`).filter(mediaSection => {\n const kindPattern = new RegExp(`m=${kind}`, 'gm');\n const directionPattern = new RegExp(`a=${direction}`, 'gm');\n return kindPattern.test(mediaSection) && directionPattern.test(mediaSection);\n });\n}\n\n/**\n * Get the Set of SSRCs announced in a MediaSection.\n * @param {string} mediaSection\n * @returns {Array} ssrcs\n */\nfunction getMediaSectionSSRCs(mediaSection) {\n return Array.from(getMatches('^a=ssrc:([0-9]+) +.*$', mediaSection));\n}\n\n/**\n * Get a Set of SSRCs for a MediaStreamTrack from a Unified Plan SDP.\n * @param {string} sdp - Unified Plan SDP\n * @param {string} trackId - MediaStreamTrack ID\n * @returns {Set}\n */\nfunction getUnifiedPlanSSRCs(sdp, trackId) {\n const mediaSections = getMediaSections(sdp);\n\n const msidAttrRegExp = new RegExp(`^a=msid:[^ ]+ +${trackId} *$`, 'gm');\n const matchingMediaSections = mediaSections.filter(mediaSection => mediaSection.match(msidAttrRegExp));\n\n return new Set(flatMap(matchingMediaSections, getMediaSectionSSRCs));\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from an SDP.\n * @param {function(string): Set} getTrackIds\n * @param {function(string, string): Set} getSSRCs\n * @param {string} sdp - SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getTrackIdsToSSRCs(getTrackIds, getSSRCs, sdp) {\n return new Map(Array.from(getTrackIds(sdp)).map(trackId => [trackId, getSSRCs(sdp, trackId)]));\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getPlanBTrackIdsToSSRCs(sdp) {\n return getTrackIdsToSSRCs(getPlanBTrackIds, getPlanBSSRCs, sdp);\n}\n\n/**\n * Get a Map from MediaStreamTrack IDs to SSRCs from a Plan B SDP.\n * @param {string} sdp - Plan B SDP\n * @returns {Map>} trackIdsToSSRCs\n */\nfunction getUnifiedPlanTrackIdsToSSRCs(sdp) {\n return getTrackIdsToSSRCs(getUnifiedPlanTrackIds, getUnifiedPlanSSRCs, sdp);\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method\n * ensures that SSRCs never change once announced.\n * @param {function(string): Map>} getTrackIdsToSSRCs\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - SDP\n * @returns {strinng} updatedSdp - updated SDP\n */\nfunction updateTrackIdsToSSRCs(getTrackIdsToSSRCs, trackIdsToSSRCs, sdp) {\n const newTrackIdsToSSRCs = getTrackIdsToSSRCs(sdp);\n const newSSRCsToOldSSRCs = new Map();\n\n // NOTE(mroberts): First, update a=ssrc attributes.\n newTrackIdsToSSRCs.forEach((ssrcs, trackId) => {\n if (!trackIdsToSSRCs.has(trackId)) {\n trackIdsToSSRCs.set(trackId, ssrcs);\n return;\n }\n const oldSSRCs = Array.from(trackIdsToSSRCs.get(trackId));\n const newSSRCs = Array.from(ssrcs);\n oldSSRCs.forEach((oldSSRC, i) => {\n const newSSRC = newSSRCs[i];\n newSSRCsToOldSSRCs.set(newSSRC, oldSSRC);\n const pattern = `^a=ssrc:${newSSRC} (.*)$`;\n const replacement = `a=ssrc:${oldSSRC} $1`;\n sdp = sdp.replace(new RegExp(pattern, 'gm'), replacement);\n });\n });\n\n // NOTE(mroberts): Then, update a=ssrc-group attributes.\n const pattern = '^(a=ssrc-group:[^ ]+ +)(.*)$';\n const matches = sdp.match(new RegExp(pattern, 'gm')) || [];\n matches.forEach(line => {\n const match = line.match(new RegExp(pattern));\n if (!match) {\n return;\n }\n const prefix = match[1];\n const newSSRCs = match[2];\n const oldSSRCs = newSSRCs.split(' ').map(newSSRC => {\n const oldSSRC = newSSRCsToOldSSRCs.get(newSSRC);\n return oldSSRC ? oldSSRC : newSSRC;\n }).join(' ');\n sdp = sdp.replace(match[0], prefix + oldSSRCs);\n });\n\n return sdp;\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This\n * method ensures that SSRCs never change once announced.\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - Plan B SDP\n * @returns {string} updatedSdp - updated Plan B SDP\n */\nfunction updatePlanBTrackIdsToSSRCs(trackIdsToSSRCs, sdp) {\n return updateTrackIdsToSSRCs(getPlanBTrackIdsToSSRCs, trackIdsToSSRCs, sdp);\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the Plan B SDP itself. This\n * method ensures that SSRCs never change once announced.\n * @param {Map>} trackIdsToSSRCs\n * @param {string} sdp - Plan B SDP\n * @returns {string} updatedSdp - updated Plan B SDP\n */\nfunction updateUnifiedPlanTrackIdsToSSRCs(trackIdsToSSRCs, sdp) {\n return updateTrackIdsToSSRCs(getUnifiedPlanTrackIdsToSSRCs, trackIdsToSSRCs, sdp);\n}\n\nexports.clearChromeCachedSdpFormat = clearChromeCachedSdpFormat;\nexports.getSdpFormat = getSdpFormat;\nexports.getMediaSections = getMediaSections;\nexports.getPlanBTrackIds = getPlanBTrackIds;\nexports.getUnifiedPlanTrackIds = getUnifiedPlanTrackIds;\nexports.getPlanBSSRCs = getPlanBSSRCs;\nexports.getUnifiedPlanSSRCs = getUnifiedPlanSSRCs;\nexports.updatePlanBTrackIdsToSSRCs = updatePlanBTrackIdsToSSRCs;\nexports.updateUnifiedPlanTrackIdsToSSRCs = updateUnifiedPlanTrackIdsToSSRCs;\n", "'use strict';\n\nconst { flatMap, guessBrowser, guessBrowserVersion } = require('./util');\nconst { getSdpFormat } = require('./util/sdp');\n\nconst guess = guessBrowser();\nconst guessVersion = guessBrowserVersion();\nconst isChrome = guess === 'chrome';\nconst isFirefox = guess === 'firefox';\nconst isSafari = guess === 'safari';\n\nconst chromeMajorVersion = isChrome ? guessVersion.major : null;\n\nconst CHROME_LEGACY_MAX_AUDIO_LEVEL = 32767;\n\n/**\n * Get the standardized {@link RTCPeerConnection} statistics.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options] - Used for testing\n * @returns {Promise.}\n */\nfunction getStats(peerConnection, options) {\n if (!(peerConnection && typeof peerConnection.getStats === 'function')) {\n return Promise.reject(new Error('Given PeerConnection does not support getStats'));\n }\n return _getStats(peerConnection, options);\n}\n\n/**\n * getStats() implementation.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options] - Used for testing\n * @returns {Promise.}\n */\nfunction _getStats(peerConnection, options) {\n const localAudioTracks = getTracks(peerConnection, 'audio', 'local');\n const localVideoTracks = getTracks(peerConnection, 'video', 'local');\n const remoteAudioTracks = getTracks(peerConnection, 'audio');\n const remoteVideoTracks = getTracks(peerConnection, 'video');\n\n const statsResponse = {\n activeIceCandidatePair: null,\n localAudioTrackStats: [],\n localVideoTrackStats: [],\n remoteAudioTrackStats: [],\n remoteVideoTrackStats: []\n };\n\n const trackStatsPromises = flatMap([\n [localAudioTracks, 'localAudioTrackStats', false],\n [localVideoTracks, 'localVideoTrackStats', false],\n [remoteAudioTracks, 'remoteAudioTrackStats', true],\n [remoteVideoTracks, 'remoteVideoTrackStats', true]\n ], ([tracks, statsArrayName, isRemote]) => {\n return tracks.map(track => {\n return getTrackStats(peerConnection, track, Object.assign({ isRemote }, options)).then(trackStatsArray => {\n trackStatsArray.forEach(trackStats => {\n trackStats.trackId = track.id;\n statsResponse[statsArrayName].push(trackStats);\n });\n });\n });\n });\n\n return Promise.all(trackStatsPromises).then(() => {\n return getActiveIceCandidatePairStats(peerConnection, options);\n }).then(activeIceCandidatePairStatsReport => {\n statsResponse.activeIceCandidatePair = activeIceCandidatePairStatsReport;\n return statsResponse;\n });\n}\n\n/**\n * Generate the {@link StandardizedActiveIceCandidatePairStatsReport} for the\n * {@link RTCPeerConnection}.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options]\n * @returns {Promise}\n */\nfunction getActiveIceCandidatePairStats(peerConnection, options = {}) {\n if (typeof options.testForChrome !== 'undefined' || isChrome\n || typeof options.testForSafari !== 'undefined' || isSafari) {\n return peerConnection.getStats().then(\n standardizeChromeOrSafariActiveIceCandidatePairStats);\n }\n if (typeof options.testForFirefox !== 'undefined' || isFirefox) {\n return peerConnection.getStats().then(standardizeFirefoxActiveIceCandidatePairStats);\n }\n return Promise.reject(new Error('RTCPeerConnection#getStats() not supported'));\n}\n\n/**\n * Standardize the active RTCIceCandidate pair's statistics in Chrome or Safari.\n * @param {RTCStatsReport} stats\n * @returns {?StandardizedActiveIceCandidatePairStatsReport}\n */\nfunction standardizeChromeOrSafariActiveIceCandidatePairStats(stats) {\n const activeCandidatePairStats = Array.from(stats.values()).find(\n ({ nominated, type }) => type === 'candidate-pair' && nominated\n );\n\n if (!activeCandidatePairStats) {\n return null;\n }\n\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n\n const standardizedCandidateStatsKeys = [\n { key: 'candidateType', type: 'string' },\n { key: 'ip', type: 'string' },\n { key: 'port', type: 'number' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', type: 'string' },\n { key: 'url', type: 'string' }\n ];\n\n const standardizedLocalCandidateStatsKeys = standardizedCandidateStatsKeys.concat([\n { key: 'deleted', type: 'boolean' },\n { key: 'relayProtocol', type: 'string' }\n ]);\n\n const standatdizedLocalCandidateStatsReport = activeLocalCandidateStats\n ? standardizedLocalCandidateStatsKeys.reduce((report, { key, type }) => {\n report[key] = typeof activeLocalCandidateStats[key] === type\n ? activeLocalCandidateStats[key]\n : key === 'deleted' ? false : null;\n return report;\n }, {})\n : null;\n\n const standardizedRemoteCandidateStatsReport = activeRemoteCandidateStats\n ? standardizedCandidateStatsKeys.reduce((report, { key, type }) => {\n report[key] = typeof activeRemoteCandidateStats[key] === type\n ? activeRemoteCandidateStats[key]\n : null;\n return report;\n }, {})\n : null;\n\n return [\n { key: 'availableIncomingBitrate', type: 'number' },\n { key: 'availableOutgoingBitrate', type: 'number' },\n { key: 'bytesReceived', type: 'number' },\n { key: 'bytesSent', type: 'number' },\n { key: 'consentRequestsSent', type: 'number' },\n { key: 'currentRoundTripTime', type: 'number' },\n { key: 'lastPacketReceivedTimestamp', type: 'number' },\n { key: 'lastPacketSentTimestamp', type: 'number' },\n { key: 'nominated', type: 'boolean' },\n { key: 'priority', type: 'number' },\n { key: 'readable', type: 'boolean' },\n { key: 'requestsReceived', type: 'number' },\n { key: 'requestsSent', type: 'number' },\n { key: 'responsesReceived', type: 'number' },\n { key: 'responsesSent', type: 'number' },\n { key: 'retransmissionsReceived', type: 'number' },\n { key: 'retransmissionsSent', type: 'number' },\n { key: 'state', type: 'string', fixup: state => { return state === 'inprogress' ? 'in-progress' : state; } },\n { key: 'totalRoundTripTime', type: 'number' },\n { key: 'transportId', type: 'string' },\n { key: 'writable', type: 'boolean' }\n ].reduce((report, { key, type, fixup }) => {\n report[key] = typeof activeCandidatePairStats[key] === type\n ? (fixup ? fixup(activeCandidatePairStats[key]) : activeCandidatePairStats[key])\n : null;\n return report;\n }, {\n localCandidate: standatdizedLocalCandidateStatsReport,\n remoteCandidate: standardizedRemoteCandidateStatsReport\n });\n}\n\n/**\n * Standardize the active RTCIceCandidate pair's statistics in Firefox.\n * @param {RTCStatsReport} stats\n * @returns {?StandardizedActiveIceCandidatePairStatsReport}\n */\nfunction standardizeFirefoxActiveIceCandidatePairStats(stats) {\n const activeCandidatePairStats = Array.from(stats.values()).find(\n ({ nominated, type }) => type === 'candidate-pair' && nominated\n );\n\n if (!activeCandidatePairStats) {\n return null;\n }\n\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n\n const standardizedCandidateStatsKeys = [\n { key: 'candidateType', type: 'string' },\n { key: 'ip', ffKeys: ['address', 'ipAddress'], type: 'string' },\n { key: 'port', ffKeys: ['portNumber'], type: 'number' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', ffKeys: ['transport'], type: 'string' },\n { key: 'url', type: 'string' }\n ];\n\n const standardizedLocalCandidateStatsKeys = standardizedCandidateStatsKeys.concat([\n { key: 'deleted', type: 'boolean' },\n { key: 'relayProtocol', type: 'string' }\n ]);\n\n const candidateTypes = {\n host: 'host',\n peerreflexive: 'prflx',\n relayed: 'relay',\n serverreflexive: 'srflx'\n };\n\n const standatdizedLocalCandidateStatsReport = activeLocalCandidateStats\n ? standardizedLocalCandidateStatsKeys.reduce((report, { ffKeys, key, type }) => {\n const localStatKey = ffKeys && ffKeys.find(key => key in activeLocalCandidateStats) || key;\n report[key] = typeof activeLocalCandidateStats[localStatKey] === type\n ? localStatKey === 'candidateType'\n ? candidateTypes[activeLocalCandidateStats[localStatKey]] || activeLocalCandidateStats[localStatKey]\n : activeLocalCandidateStats[localStatKey]\n : localStatKey === 'deleted' ? false : null;\n return report;\n }, {})\n : null;\n\n const standardizedRemoteCandidateStatsReport = activeRemoteCandidateStats\n ? standardizedCandidateStatsKeys.reduce((report, { ffKeys, key, type }) => {\n const remoteStatKey = ffKeys && ffKeys.find(key => key in activeRemoteCandidateStats) || key;\n report[key] = typeof activeRemoteCandidateStats[remoteStatKey] === type\n ? remoteStatKey === 'candidateType'\n ? candidateTypes[activeRemoteCandidateStats[remoteStatKey]] || activeRemoteCandidateStats[remoteStatKey]\n : activeRemoteCandidateStats[remoteStatKey]\n : null;\n return report;\n }, {})\n : null;\n\n return [\n { key: 'availableIncomingBitrate', type: 'number' },\n { key: 'availableOutgoingBitrate', type: 'number' },\n { key: 'bytesReceived', type: 'number' },\n { key: 'bytesSent', type: 'number' },\n { key: 'consentRequestsSent', type: 'number' },\n { key: 'currentRoundTripTime', type: 'number' },\n { key: 'lastPacketReceivedTimestamp', type: 'number' },\n { key: 'lastPacketSentTimestamp', type: 'number' },\n { key: 'nominated', type: 'boolean' },\n { key: 'priority', type: 'number' },\n { key: 'readable', type: 'boolean' },\n { key: 'requestsReceived', type: 'number' },\n { key: 'requestsSent', type: 'number' },\n { key: 'responsesReceived', type: 'number' },\n { key: 'responsesSent', type: 'number' },\n { key: 'retransmissionsReceived', type: 'number' },\n { key: 'retransmissionsSent', type: 'number' },\n { key: 'state', type: 'string' },\n { key: 'totalRoundTripTime', type: 'number' },\n { key: 'transportId', type: 'string' },\n { key: 'writable', type: 'boolean' }\n ].reduce((report, { key, type }) => {\n report[key] = typeof activeCandidatePairStats[key] === type\n ? activeCandidatePairStats[key]\n : null;\n return report;\n }, {\n localCandidate: standatdizedLocalCandidateStatsReport,\n remoteCandidate: standardizedRemoteCandidateStatsReport\n });\n}\n\n/**\n * Get local/remote audio/video MediaStreamTracks.\n * @param {RTCPeerConnection} peerConnection - The RTCPeerConnection\n * @param {string} kind - 'audio' or 'video'\n * @param {string} [localOrRemote] - 'local' or 'remote'\n * @returns {Array}\n */\nfunction getTracks(peerConnection, kind, localOrRemote) {\n const getSendersOrReceivers = localOrRemote === 'local' ? 'getSenders' : 'getReceivers';\n if (peerConnection[getSendersOrReceivers]) {\n return peerConnection[getSendersOrReceivers]()\n .map(({ track }) => track)\n .filter(track => track && track.kind === kind);\n }\n const getStreams = localOrRemote === 'local' ? 'getLocalStreams' : 'getRemoteStreams';\n const getTracks = kind === 'audio' ? 'getAudioTracks' : 'getVideoTracks';\n return flatMap(peerConnection[getStreams](), stream => stream[getTracks]());\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} [options] - Used for testing\n * @returns {Promise.>}\n */\nfunction getTrackStats(peerConnection, track, options = {}) {\n if (typeof options.testForChrome !== 'undefined' || isChrome) {\n return chromeOrSafariGetTrackStats(peerConnection, track, options);\n }\n if (typeof options.testForFirefox !== 'undefined' || isFirefox) {\n return firefoxGetTrackStats(peerConnection, track, options);\n }\n if (typeof options.testForSafari !== 'undefined' || isSafari) {\n if (typeof options.testForSafari !== 'undefined' || getSdpFormat() === 'unified') {\n return chromeOrSafariGetTrackStats(peerConnection, track, options);\n }\n // NOTE(syerrapragada): getStats() is not supported on\n // Safari versions where plan-b is the SDP format\n // due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601\n return Promise.reject(new Error([\n 'getStats() is not supported on this version of Safari',\n 'due to this bug: https://bugs.webkit.org/show_bug.cgi?id=192601'\n ].join(' ')));\n }\n return Promise.reject(new Error('RTCPeerConnection#getStats() not supported'));\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack in Chrome or Safari.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} options - Used for testing\n * @returns {Promise.>}\n */\nfunction chromeOrSafariGetTrackStats(peerConnection, track, options) {\n if (chromeMajorVersion && chromeMajorVersion < 67) {\n return new Promise((resolve, reject) => {\n peerConnection.getStats(response => {\n resolve([standardizeChromeLegacyStats(response, track)]);\n }, null, reject);\n });\n }\n return peerConnection.getStats(track).then(response => {\n return standardizeChromeOrSafariStats(response, options);\n });\n}\n\n/**\n * Get the standardized statistics for a particular MediaStreamTrack in Firefox.\n * @param {RTCPeerConnection} peerConnection\n * @param {MediaStreamTrack} track\n * @param {object} options\n * @returns {Promise.>}\n */\nfunction firefoxGetTrackStats(peerConnection, track, options) {\n return peerConnection.getStats(track).then(response => {\n return [standardizeFirefoxStats(response, options)];\n });\n}\n\n/**\n * Standardize the MediaStreamTrack's legacy statistics in Chrome.\n * @param {RTCStatsResponse} response\n * @param {MediaStreamTrack} track\n * @returns {StandardizedTrackStatsReport}\n */\nfunction standardizeChromeLegacyStats(response, track) {\n const ssrcReport = response.result().find(report => {\n return report.type === 'ssrc' && report.stat('googTrackId') === track.id;\n });\n\n let standardizedStats = {};\n\n if (ssrcReport) {\n standardizedStats.timestamp = Math.round(Number(ssrcReport.timestamp));\n standardizedStats = ssrcReport.names().reduce((stats, name) => {\n switch (name) {\n case 'googCodecName':\n stats.codecName = ssrcReport.stat(name);\n break;\n case 'googRtt':\n stats.roundTripTime = Number(ssrcReport.stat(name));\n break;\n case 'googJitterReceived':\n stats.jitter = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthInput':\n stats.frameWidthInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightInput':\n stats.frameHeightInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthSent':\n stats.frameWidthSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightSent':\n stats.frameHeightSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameWidthReceived':\n stats.frameWidthReceived = Number(ssrcReport.stat(name));\n break;\n case 'googFrameHeightReceived':\n stats.frameHeightReceived = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateInput':\n stats.frameRateInput = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateSent':\n stats.frameRateSent = Number(ssrcReport.stat(name));\n break;\n case 'googFrameRateReceived':\n stats.frameRateReceived = Number(ssrcReport.stat(name));\n break;\n case 'ssrc':\n stats[name] = ssrcReport.stat(name);\n break;\n case 'bytesReceived':\n case 'bytesSent':\n case 'packetsLost':\n case 'packetsReceived':\n case 'packetsSent':\n case 'audioInputLevel':\n case 'audioOutputLevel':\n stats[name] = Number(ssrcReport.stat(name));\n break;\n }\n\n return stats;\n }, standardizedStats);\n }\n\n return standardizedStats;\n}\n\n/**\n * Standardize the MediaStreamTrack's statistics in Chrome or Safari.\n * @param {RTCStatsResponse} response\n * @param {object} options - Used for testing\n * @returns {Array}\n */\nfunction standardizeChromeOrSafariStats(response, { simulateExceptionWhileStandardizingStats = false }) {\n if (simulateExceptionWhileStandardizingStats) {\n throw new Error('Error while gathering stats');\n }\n let inbound = null;\n\n // NOTE(mpatwardhan): We should expect more than one \"outbound-rtp\" stats for a\n // VP8 simulcast MediaStreamTrack.\n const outbound = [];\n\n let remoteInbound = null;\n let remoteOutbound = null;\n let track = null;\n let codec = null;\n let localMedia = null;\n\n response.forEach(stat => {\n const { type } = stat;\n switch (type) {\n case 'inbound-rtp':\n inbound = stat;\n break;\n case 'outbound-rtp':\n outbound.push(stat);\n break;\n case 'media-source':\n localMedia = stat;\n break;\n case 'track':\n track = stat;\n break;\n case 'codec':\n codec = stat;\n break;\n case 'remote-inbound-rtp':\n remoteInbound = stat;\n break;\n case 'remote-outbound-rtp':\n remoteOutbound = stat;\n break;\n }\n });\n\n const isRemote = track ? track.remoteSource : !localMedia;\n const mainSources = isRemote ? [inbound] : outbound;\n const stats = [];\n const remoteSource = isRemote ? remoteOutbound : remoteInbound; // remote rtp stats\n\n mainSources.forEach(source => {\n const standardizedStats = {};\n const statSources = [\n source, // local rtp stats\n localMedia,\n track,\n codec,\n remoteSource && remoteSource.ssrc === source.ssrc ? remoteSource : null, // remote rtp stats\n ];\n\n function getStatValue(name) {\n const sourceFound = statSources.find(statSource => {\n return statSource && typeof statSource[name] !== 'undefined';\n }) || null;\n\n return sourceFound ? sourceFound[name] : null;\n }\n\n const ssrc = getStatValue('ssrc');\n if (typeof ssrc === 'number') {\n standardizedStats.ssrc = String(ssrc);\n }\n\n const timestamp = getStatValue('timestamp');\n standardizedStats.timestamp = Math.round(timestamp);\n\n let mimeType = getStatValue('mimeType');\n if (typeof mimeType === 'string') {\n mimeType = mimeType.split('/');\n standardizedStats.codecName = mimeType[mimeType.length - 1];\n }\n\n const roundTripTime = getStatValue('roundTripTime');\n if (typeof roundTripTime === 'number') {\n standardizedStats.roundTripTime = Math.round(roundTripTime * 1000);\n }\n\n const jitter = getStatValue('jitter');\n if (typeof jitter === 'number') {\n standardizedStats.jitter = Math.round(jitter * 1000);\n }\n\n const frameWidth = getStatValue('frameWidth');\n if (typeof frameWidth === 'number') {\n if (isRemote) {\n standardizedStats.frameWidthReceived = frameWidth;\n } else {\n standardizedStats.frameWidthSent = frameWidth;\n standardizedStats.frameWidthInput = track ? track.frameWidth : localMedia.width;\n }\n }\n\n const frameHeight = getStatValue('frameHeight');\n if (typeof frameHeight === 'number') {\n if (isRemote) {\n standardizedStats.frameHeightReceived = frameHeight;\n } else {\n standardizedStats.frameHeightSent = frameHeight;\n standardizedStats.frameHeightInput = track ? track.frameHeight : localMedia.height;\n }\n }\n\n const framesPerSecond = getStatValue('framesPerSecond');\n if (typeof framesPerSecond === 'number') {\n standardizedStats[isRemote ? 'frameRateReceived' : 'frameRateSent'] = framesPerSecond;\n }\n\n const bytesReceived = getStatValue('bytesReceived');\n if (typeof bytesReceived === 'number') {\n standardizedStats.bytesReceived = bytesReceived;\n }\n\n const bytesSent = getStatValue('bytesSent');\n if (typeof bytesSent === 'number') {\n standardizedStats.bytesSent = bytesSent;\n }\n\n const packetsLost = getStatValue('packetsLost');\n if (typeof packetsLost === 'number') {\n standardizedStats.packetsLost = packetsLost;\n }\n\n const packetsReceived = getStatValue('packetsReceived');\n if (typeof packetsReceived === 'number') {\n standardizedStats.packetsReceived = packetsReceived;\n }\n\n const packetsSent = getStatValue('packetsSent');\n if (typeof packetsSent === 'number') {\n standardizedStats.packetsSent = packetsSent;\n }\n\n let audioLevel = getStatValue('audioLevel');\n if (typeof audioLevel === 'number') {\n audioLevel = Math.round(audioLevel * CHROME_LEGACY_MAX_AUDIO_LEVEL);\n if (isRemote) {\n standardizedStats.audioOutputLevel = audioLevel;\n } else {\n standardizedStats.audioInputLevel = audioLevel;\n }\n }\n\n const totalPacketSendDalay = getStatValue('totalPacketSendDelay');\n if (typeof totalPacketSendDalay === 'number') {\n standardizedStats.totalPacketSendDelay = totalPacketSendDalay;\n }\n\n const totalEncodeTime = getStatValue('totalEncodeTime');\n if (typeof totalEncodeTime === 'number') {\n standardizedStats.totalEncodeTime = totalEncodeTime;\n }\n\n const framesEncoded = getStatValue('framesEncoded');\n if (typeof framesEncoded === 'number') {\n standardizedStats.framesEncoded = framesEncoded;\n }\n\n const estimatedPlayoutTimestamp = getStatValue('estimatedPlayoutTimestamp');\n if (typeof estimatedPlayoutTimestamp === 'number') {\n standardizedStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n\n const totalDecodeTime = getStatValue('totalDecodeTime');\n if (typeof totalDecodeTime === 'number') {\n standardizedStats.totalDecodeTime = totalDecodeTime;\n }\n\n const framesDecoded = getStatValue('framesDecoded');\n if (typeof framesDecoded === 'number') {\n standardizedStats.framesDecoded = framesDecoded;\n }\n\n const jitterBufferDelay = getStatValue('jitterBufferDelay');\n if (typeof jitterBufferDelay === 'number') {\n standardizedStats.jitterBufferDelay = jitterBufferDelay;\n }\n\n const jitterBufferEmittedCount = getStatValue('jitterBufferEmittedCount');\n if (typeof jitterBufferEmittedCount === 'number') {\n standardizedStats.jitterBufferEmittedCount = jitterBufferEmittedCount;\n }\n\n stats.push(standardizedStats);\n });\n\n return stats;\n}\n\n/**\n * Standardize the MediaStreamTrack's statistics in Firefox.\n * @param {RTCStatsReport} response\n * @param {object} options - Used for testing\n * @returns {StandardizedTrackStatsReport}\n */\nfunction standardizeFirefoxStats(response = new Map(), { isRemote, simulateExceptionWhileStandardizingStats = false }) {\n if (simulateExceptionWhileStandardizingStats) {\n throw new Error('Error while gathering stats');\n }\n // NOTE(mroberts): If getStats is called on a closed RTCPeerConnection,\n // Firefox returns undefined instead of an RTCStatsReport. We workaround this\n // here. See the following bug for more details:\n //\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1377225\n //\n\n let inbound = null;\n let outbound = null;\n\n // NOTE(mmalavalli): Starting from Firefox 63, RTC{Inbound, Outbound}RTPStreamStats.isRemote\n // will be deprecated, followed by its removal in Firefox 66. Also, trying to\n // access members of the remote RTC{Inbound, Outbound}RTPStreamStats without\n // using RTCStatsReport.get(remoteId) will trigger console warnings. So, we\n // no longer depend on \"isRemote\", and we call RTCStatsReport.get(remoteId)\n // to access the remote RTC{Inbound, Outbound}RTPStreamStats.\n //\n // Source: https://blog.mozilla.org/webrtc/getstats-isremote-65/\n //\n response.forEach(stat => {\n const { isRemote, remoteId, type } = stat;\n if (isRemote) {\n return;\n }\n switch (type) {\n case 'inbound-rtp':\n inbound = stat;\n outbound = response.get(remoteId);\n break;\n case 'outbound-rtp':\n outbound = stat;\n inbound = response.get(remoteId);\n break;\n }\n });\n\n const first = isRemote ? inbound : outbound;\n const second = isRemote ? outbound : inbound;\n\n function getStatValue(name) {\n if (first && typeof first[name] !== 'undefined') {\n return first[name];\n }\n if (second && typeof second[name] !== 'undefined') {\n return second[name];\n }\n return null;\n }\n\n const standardizedStats = {};\n const timestamp = getStatValue('timestamp');\n standardizedStats.timestamp = Math.round(timestamp);\n\n const ssrc = getStatValue('ssrc');\n if (typeof ssrc === 'number') {\n standardizedStats.ssrc = String(ssrc);\n }\n\n const bytesSent = getStatValue('bytesSent');\n if (typeof bytesSent === 'number') {\n standardizedStats.bytesSent = bytesSent;\n }\n\n const packetsLost = getStatValue('packetsLost');\n if (typeof packetsLost === 'number') {\n standardizedStats.packetsLost = packetsLost;\n }\n\n const packetsSent = getStatValue('packetsSent');\n if (typeof packetsSent === 'number') {\n standardizedStats.packetsSent = packetsSent;\n }\n\n const roundTripTime = getStatValue('roundTripTime');\n if (typeof roundTripTime === 'number') {\n // roundTripTime is double - measured in seconds.\n // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteinboundrtpstreamstats-roundtriptime\n // cover it to milliseconds (and make it integer)\n standardizedStats.roundTripTime = Math.round(roundTripTime * 1000);\n }\n\n const jitter = getStatValue('jitter');\n if (typeof jitter === 'number') {\n standardizedStats.jitter = Math.round(jitter * 1000);\n }\n\n const frameRateSent = getStatValue('framerateMean');\n if (typeof frameRateSent === 'number') {\n standardizedStats.frameRateSent = Math.round(frameRateSent);\n }\n\n const bytesReceived = getStatValue('bytesReceived');\n if (typeof bytesReceived === 'number') {\n standardizedStats.bytesReceived = bytesReceived;\n }\n\n const packetsReceived = getStatValue('packetsReceived');\n if (typeof packetsReceived === 'number') {\n standardizedStats.packetsReceived = packetsReceived;\n }\n\n const frameRateReceived = getStatValue('framerateMean');\n if (typeof frameRateReceived === 'number') {\n standardizedStats.frameRateReceived = Math.round(frameRateReceived);\n }\n\n const totalPacketSendDalay = getStatValue('totalPacketSendDelay');\n if (typeof totalPacketSendDalay === 'number') {\n standardizedStats.totalPacketSendDelay = totalPacketSendDalay;\n }\n\n const totalEncodeTime = getStatValue('totalEncodeTime');\n if (typeof totalEncodeTime === 'number') {\n standardizedStats.totalEncodeTime = totalEncodeTime;\n }\n\n const framesEncoded = getStatValue('framesEncoded');\n if (typeof framesEncoded === 'number') {\n standardizedStats.framesEncoded = framesEncoded;\n }\n\n const estimatedPlayoutTimestamp = getStatValue('estimatedPlayoutTimestamp');\n if (typeof estimatedPlayoutTimestamp === 'number') {\n standardizedStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n\n const totalDecodeTime = getStatValue('totalDecodeTime');\n if (typeof totalDecodeTime === 'number') {\n standardizedStats.totalDecodeTime = totalDecodeTime;\n }\n\n const framesDecoded = getStatValue('framesDecoded');\n if (typeof framesDecoded === 'number') {\n standardizedStats.framesDecoded = framesDecoded;\n }\n\n const jitterBufferDelay = getStatValue('jitterBufferDelay');\n if (typeof jitterBufferDelay === 'number') {\n standardizedStats.jitterBufferDelay = jitterBufferDelay;\n }\n\n const jitterBufferEmittedCount = getStatValue('jitterBufferEmittedCount');\n if (typeof jitterBufferEmittedCount === 'number') {\n standardizedStats.jitterBufferEmittedCount = jitterBufferEmittedCount;\n }\n\n return standardizedStats;\n}\n\n/**\n * Standardized RTCIceCandidate statistics.\n * @typedef {object} StandardizedIceCandidateStatsReport\n * @property {'host'|'prflx'|'relay'|'srflx'} candidateType\n * @property {string} ip\n * @property {number} port\n * @property {number} priority\n * @property {'tcp'|'udp'} protocol\n * @property {string} url\n */\n\n/**\n * Standardized local RTCIceCandidate statistics.\n * @typedef {StandardizedIceCandidateStatsReport} StandardizedLocalIceCandidateStatsReport\n * @property {boolean} [deleted=false]\n * @property {'tcp'|'tls'|'udp'} relayProtocol\n */\n\n/**\n * Standardized active RTCIceCandidate pair statistics.\n * @typedef {object} StandardizedActiveIceCandidatePairStatsReport\n * @property {number} availableIncomingBitrate\n * @property {number} availableOutgoingBitrate\n * @property {number} bytesReceived\n * @property {number} bytesSent\n * @property {number} consentRequestsSent\n * @property {number} currentRoundTripTime\n * @property {number} lastPacketReceivedTimestamp\n * @property {number} lastPacketSentTimestamp\n * @property {StandardizedLocalIceCandidateStatsReport} localCandidate\n * @property {boolean} nominated\n * @property {number} priority\n * @property {boolean} readable\n * @property {StandardizedIceCandidateStatsReport} remoteCandidate\n * @property {number} requestsReceived\n * @property {number} requestsSent\n * @property {number} responsesReceived\n * @property {number} responsesSent\n * @property {number} retransmissionsReceived\n * @property {number} retransmissionsSent\n * @property {'frozen'|'waiting'|'in-progress'|'failed'|'succeeded'} state\n * @property {number} totalRoundTripTime\n * @property {string} transportId\n * @property {boolean} writable\n */\n\n/**\n * Standardized {@link RTCPeerConnection} statistics.\n * @typedef {Object} StandardizedStatsResponse\n * @property {StandardizedActiveIceCandidatePairStatsReport} activeIceCandidatePair - Stats for active ICE candidate pair\n * @property Array localAudioTrackStats - Stats for local audio MediaStreamTracks\n * @property Array localVideoTrackStats - Stats for local video MediaStreamTracks\n * @property Array remoteAudioTrackStats - Stats for remote audio MediaStreamTracks\n * @property Array remoteVideoTrackStats - Stats for remote video MediaStreamTracks\n */\n\n/**\n * Standardized MediaStreamTrack statistics.\n * @typedef {Object} StandardizedTrackStatsReport\n * @property {string} trackId - MediaStreamTrack ID\n * @property {string} ssrc - SSRC of the MediaStreamTrack\n * @property {number} timestamp - The Unix timestamp in milliseconds\n * @property {string} [codecName] - Name of the codec used to encode the MediaStreamTrack's media\n * @property {number} [roundTripTime] - Round trip time in milliseconds\n * @property {number} [jitter] - Jitter in milliseconds\n * @property {number} [frameWidthInput] - Width in pixels of the local video MediaStreamTrack's captured frame\n * @property {number} [frameHeightInput] - Height in pixels of the local video MediaStreamTrack's captured frame\n * @property {number} [frameWidthSent] - Width in pixels of the local video MediaStreamTrack's encoded frame\n * @property {number} [frameHeightSent] - Height in pixels of the local video MediaStreamTrack's encoded frame\n * @property {number} [frameWidthReceived] - Width in pixels of the remote video MediaStreamTrack's received frame\n * @property {number} [frameHeightReceived] - Height in pixels of the remote video MediaStreamTrack's received frame\n * @property {number} [frameRateInput] - Captured frames per second of the local video MediaStreamTrack\n * @property {number} [frameRateSent] - Frames per second of the local video MediaStreamTrack's encoded video\n * @property {number} [frameRateReceived] - Frames per second of the remote video MediaStreamTrack's received video\n * @property {number} [bytesReceived] - Number of bytes of the remote MediaStreamTrack's media received\n * @property {number} [bytesSent] - Number of bytes of the local MediaStreamTrack's media sent\n * @property {number} [packetsLost] - Number of packets of the MediaStreamTrack's media lost\n * @property {number} [packetsReceived] - Number of packets of the remote MediaStreamTrack's media received\n * @property {number} [packetsSent] - Number of packets of the local MediaStreamTrack's media sent\n * @property {number} [totalPacketSendDelay] - The total number of seconds that the local MediaStreamTrack's packets\n * have spent buffered locally before being sent over the network\n * @property {number} [totalEncodeTime] - The total number of seconds spent on encoding the local MediaStreamTrack's frames\n * @property {number} [framesEncoded] - The total number of frames of the local MediaStreamTrack that have been encoded sor far\n * @property {number} [estimatedPlayoutTimestamp] - The estimated playout time of the remote MediaStreamTrack\n * @property {number} [totalDecodeTime] - The total number of seconds spent on decoding the remote MediaStreamTrack's frames\n * @property {number} [framesDecoded] - The total number of frames of the remote MediaStreamTrack that have been decoded sor far\n * @property {number} [jitterBufferDelay] - The sum of the time, in seconds, each audio sample or a video frame of the remote\n * MediaStreamTrack takes from the time the first packet is received by the jitter buffer to the time it exits the jitter buffer\n * @property {number} [jitterBufferEmittedCount] - The total number of audio samples or video frames that have come out of the jitter buffer\n * @property {AudioLevel} [audioInputLevel] - The {@link AudioLevel} of the local audio MediaStreamTrack\n * @property {AudioLevel} [audioOutputLevel] - The {@link AudioLevel} of the remote video MediaStreamTrack\n */\n\nmodule.exports = getStats;\n", "/* globals navigator */\n'use strict';\n\n/**\n * This function is very similar to navigator.mediaDevices.getUserMedia\n * except that if no MediaStreamConstraints are provided, then bot audio and video\n * are requested.\n * @function getUserMedia\n * @param {MediaStreamConstraints} [constraints={audio:true,video:true}] - the\n * MediaStreamConstraints object specifying what kind of MediaStream to\n * request from the browser (by default both audio and video)\n * @returns Promise\n */\nfunction getUserMedia(constraints = { audio: true, video: true }) {\n if (typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.getUserMedia === 'function') {\n return navigator.mediaDevices.getUserMedia(constraints);\n }\n return Promise.reject(new Error('getUserMedia is not supported'));\n}\n\nmodule.exports = getUserMedia;\n", "/* globals MediaStream */\n'use strict';\n\nif (typeof MediaStream === 'function') {\n module.exports = MediaStream;\n} else {\n module.exports = function MediaStream() {\n throw new Error('MediaStream is not supported');\n };\n}\n", "/* global MediaStreamTrack */\n'use strict';\n\nif (typeof MediaStreamTrack === 'function') {\n module.exports = MediaStreamTrack;\n} else {\n module.exports = function MediaStreamTrack() {\n throw new Error('MediaStreamTrack is not supported');\n };\n}\n", "/* global RTCIceCandidate */\n'use strict';\n\nif (typeof RTCIceCandidate === 'function') {\n module.exports = RTCIceCandidate;\n} else {\n module.exports = function RTCIceCandidate() {\n throw new Error('RTCIceCandidate is not supported');\n };\n}\n", "/* globals RTCSessionDescription */\n'use strict';\n\n// This class wraps Chrome's RTCSessionDescription implementation. It provides\n// one piece of functionality not currently present in Chrome, namely\n//\n// 1. Rollback support\n// https://bugs.chromium.org/p/webrtc/issues/detail?id=4676\n//\nclass ChromeRTCSessionDescription {\n constructor(descriptionInitDict) {\n this.descriptionInitDict = descriptionInitDict;\n\n // If this constructor is called with an object with a .type property set to\n // \"rollback\", we should not call Chrome's RTCSessionDescription constructor,\n // because this would throw an RTCSdpType error.\n const description = descriptionInitDict && descriptionInitDict.type === 'rollback'\n ? null\n : new RTCSessionDescription(descriptionInitDict);\n\n Object.defineProperties(this, {\n _description: {\n get: function() {\n return description;\n }\n }\n });\n }\n\n get sdp() {\n return this._description ? this._description.sdp : this.descriptionInitDict.sdp;\n }\n\n get type() {\n return this._description ? this._description.type : this.descriptionInitDict.type;\n }\n}\n\nmodule.exports = ChromeRTCSessionDescription;\n", "// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n'use strict';\n\nvar R = typeof Reflect === 'object' ? Reflect : null\nvar ReflectApply = R && typeof R.apply === 'function'\n ? R.apply\n : function ReflectApply(target, receiver, args) {\n return Function.prototype.apply.call(target, receiver, args);\n }\n\nvar ReflectOwnKeys\nif (R && typeof R.ownKeys === 'function') {\n ReflectOwnKeys = R.ownKeys\n} else if (Object.getOwnPropertySymbols) {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target)\n .concat(Object.getOwnPropertySymbols(target));\n };\n} else {\n ReflectOwnKeys = function ReflectOwnKeys(target) {\n return Object.getOwnPropertyNames(target);\n };\n}\n\nfunction ProcessEmitWarning(warning) {\n if (console && console.warn) console.warn(warning);\n}\n\nvar NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {\n return value !== value;\n}\n\nfunction EventEmitter() {\n EventEmitter.init.call(this);\n}\nmodule.exports = EventEmitter;\nmodule.exports.once = once;\n\n// Backwards-compat with node 0.10.x\nEventEmitter.EventEmitter = EventEmitter;\n\nEventEmitter.prototype._events = undefined;\nEventEmitter.prototype._eventsCount = 0;\nEventEmitter.prototype._maxListeners = undefined;\n\n// By default EventEmitters will print a warning if more than 10 listeners are\n// added to it. This is a useful default which helps finding memory leaks.\nvar defaultMaxListeners = 10;\n\nfunction checkListener(listener) {\n if (typeof listener !== 'function') {\n throw new TypeError('The \"listener\" argument must be of type Function. Received type ' + typeof listener);\n }\n}\n\nObject.defineProperty(EventEmitter, 'defaultMaxListeners', {\n enumerable: true,\n get: function() {\n return defaultMaxListeners;\n },\n set: function(arg) {\n if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {\n throw new RangeError('The value of \"defaultMaxListeners\" is out of range. It must be a non-negative number. Received ' + arg + '.');\n }\n defaultMaxListeners = arg;\n }\n});\n\nEventEmitter.init = function() {\n\n if (this._events === undefined ||\n this._events === Object.getPrototypeOf(this)._events) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n }\n\n this._maxListeners = this._maxListeners || undefined;\n};\n\n// Obviously not all Emitters should be limited to 10. This function allows\n// that to be increased. Set to zero for unlimited.\nEventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {\n if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {\n throw new RangeError('The value of \"n\" is out of range. It must be a non-negative number. Received ' + n + '.');\n }\n this._maxListeners = n;\n return this;\n};\n\nfunction _getMaxListeners(that) {\n if (that._maxListeners === undefined)\n return EventEmitter.defaultMaxListeners;\n return that._maxListeners;\n}\n\nEventEmitter.prototype.getMaxListeners = function getMaxListeners() {\n return _getMaxListeners(this);\n};\n\nEventEmitter.prototype.emit = function emit(type) {\n var args = [];\n for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);\n var doError = (type === 'error');\n\n var events = this._events;\n if (events !== undefined)\n doError = (doError && events.error === undefined);\n else if (!doError)\n return false;\n\n // If there is no 'error' event listener then throw.\n if (doError) {\n var er;\n if (args.length > 0)\n er = args[0];\n if (er instanceof Error) {\n // Note: The comments on the `throw` lines are intentional, they show\n // up in Node's output if this results in an unhandled exception.\n throw er; // Unhandled 'error' event\n }\n // At least give some kind of context to the user\n var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));\n err.context = er;\n throw err; // Unhandled 'error' event\n }\n\n var handler = events[type];\n\n if (handler === undefined)\n return false;\n\n if (typeof handler === 'function') {\n ReflectApply(handler, this, args);\n } else {\n var len = handler.length;\n var listeners = arrayClone(handler, len);\n for (var i = 0; i < len; ++i)\n ReflectApply(listeners[i], this, args);\n }\n\n return true;\n};\n\nfunction _addListener(target, type, listener, prepend) {\n var m;\n var events;\n var existing;\n\n checkListener(listener);\n\n events = target._events;\n if (events === undefined) {\n events = target._events = Object.create(null);\n target._eventsCount = 0;\n } else {\n // To avoid recursion in the case that type === \"newListener\"! Before\n // adding it to the listeners, first emit \"newListener\".\n if (events.newListener !== undefined) {\n target.emit('newListener', type,\n listener.listener ? listener.listener : listener);\n\n // Re-assign `events` because a newListener handler could have caused the\n // this._events to be assigned to a new object\n events = target._events;\n }\n existing = events[type];\n }\n\n if (existing === undefined) {\n // Optimize the case of one listener. Don't need the extra array object.\n existing = events[type] = listener;\n ++target._eventsCount;\n } else {\n if (typeof existing === 'function') {\n // Adding the second element, need to change to array.\n existing = events[type] =\n prepend ? [listener, existing] : [existing, listener];\n // If we've already got an array, just append.\n } else if (prepend) {\n existing.unshift(listener);\n } else {\n existing.push(listener);\n }\n\n // Check for listener leak\n m = _getMaxListeners(target);\n if (m > 0 && existing.length > m && !existing.warned) {\n existing.warned = true;\n // No error code for this since it is a Warning\n // eslint-disable-next-line no-restricted-syntax\n var w = new Error('Possible EventEmitter memory leak detected. ' +\n existing.length + ' ' + String(type) + ' listeners ' +\n 'added. Use emitter.setMaxListeners() to ' +\n 'increase limit');\n w.name = 'MaxListenersExceededWarning';\n w.emitter = target;\n w.type = type;\n w.count = existing.length;\n ProcessEmitWarning(w);\n }\n }\n\n return target;\n}\n\nEventEmitter.prototype.addListener = function addListener(type, listener) {\n return _addListener(this, type, listener, false);\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.prependListener =\n function prependListener(type, listener) {\n return _addListener(this, type, listener, true);\n };\n\nfunction onceWrapper() {\n if (!this.fired) {\n this.target.removeListener(this.type, this.wrapFn);\n this.fired = true;\n if (arguments.length === 0)\n return this.listener.call(this.target);\n return this.listener.apply(this.target, arguments);\n }\n}\n\nfunction _onceWrap(target, type, listener) {\n var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };\n var wrapped = onceWrapper.bind(state);\n wrapped.listener = listener;\n state.wrapFn = wrapped;\n return wrapped;\n}\n\nEventEmitter.prototype.once = function once(type, listener) {\n checkListener(listener);\n this.on(type, _onceWrap(this, type, listener));\n return this;\n};\n\nEventEmitter.prototype.prependOnceListener =\n function prependOnceListener(type, listener) {\n checkListener(listener);\n this.prependListener(type, _onceWrap(this, type, listener));\n return this;\n };\n\n// Emits a 'removeListener' event if and only if the listener was removed.\nEventEmitter.prototype.removeListener =\n function removeListener(type, listener) {\n var list, events, position, i, originalListener;\n\n checkListener(listener);\n\n events = this._events;\n if (events === undefined)\n return this;\n\n list = events[type];\n if (list === undefined)\n return this;\n\n if (list === listener || list.listener === listener) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else {\n delete events[type];\n if (events.removeListener)\n this.emit('removeListener', type, list.listener || listener);\n }\n } else if (typeof list !== 'function') {\n position = -1;\n\n for (i = list.length - 1; i >= 0; i--) {\n if (list[i] === listener || list[i].listener === listener) {\n originalListener = list[i].listener;\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (position === 0)\n list.shift();\n else {\n spliceOne(list, position);\n }\n\n if (list.length === 1)\n events[type] = list[0];\n\n if (events.removeListener !== undefined)\n this.emit('removeListener', type, originalListener || listener);\n }\n\n return this;\n };\n\nEventEmitter.prototype.off = EventEmitter.prototype.removeListener;\n\nEventEmitter.prototype.removeAllListeners =\n function removeAllListeners(type) {\n var listeners, events, i;\n\n events = this._events;\n if (events === undefined)\n return this;\n\n // not listening for removeListener, no need to emit\n if (events.removeListener === undefined) {\n if (arguments.length === 0) {\n this._events = Object.create(null);\n this._eventsCount = 0;\n } else if (events[type] !== undefined) {\n if (--this._eventsCount === 0)\n this._events = Object.create(null);\n else\n delete events[type];\n }\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n var keys = Object.keys(events);\n var key;\n for (i = 0; i < keys.length; ++i) {\n key = keys[i];\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = Object.create(null);\n this._eventsCount = 0;\n return this;\n }\n\n listeners = events[type];\n\n if (typeof listeners === 'function') {\n this.removeListener(type, listeners);\n } else if (listeners !== undefined) {\n // LIFO order\n for (i = listeners.length - 1; i >= 0; i--) {\n this.removeListener(type, listeners[i]);\n }\n }\n\n return this;\n };\n\nfunction _listeners(target, type, unwrap) {\n var events = target._events;\n\n if (events === undefined)\n return [];\n\n var evlistener = events[type];\n if (evlistener === undefined)\n return [];\n\n if (typeof evlistener === 'function')\n return unwrap ? [evlistener.listener || evlistener] : [evlistener];\n\n return unwrap ?\n unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);\n}\n\nEventEmitter.prototype.listeners = function listeners(type) {\n return _listeners(this, type, true);\n};\n\nEventEmitter.prototype.rawListeners = function rawListeners(type) {\n return _listeners(this, type, false);\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n if (typeof emitter.listenerCount === 'function') {\n return emitter.listenerCount(type);\n } else {\n return listenerCount.call(emitter, type);\n }\n};\n\nEventEmitter.prototype.listenerCount = listenerCount;\nfunction listenerCount(type) {\n var events = this._events;\n\n if (events !== undefined) {\n var evlistener = events[type];\n\n if (typeof evlistener === 'function') {\n return 1;\n } else if (evlistener !== undefined) {\n return evlistener.length;\n }\n }\n\n return 0;\n}\n\nEventEmitter.prototype.eventNames = function eventNames() {\n return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];\n};\n\nfunction arrayClone(arr, n) {\n var copy = new Array(n);\n for (var i = 0; i < n; ++i)\n copy[i] = arr[i];\n return copy;\n}\n\nfunction spliceOne(list, index) {\n for (; index + 1 < list.length; index++)\n list[index] = list[index + 1];\n list.pop();\n}\n\nfunction unwrapListeners(arr) {\n var ret = new Array(arr.length);\n for (var i = 0; i < ret.length; ++i) {\n ret[i] = arr[i].listener || arr[i];\n }\n return ret;\n}\n\nfunction once(emitter, name) {\n return new Promise(function (resolve, reject) {\n function errorListener(err) {\n emitter.removeListener(name, resolver);\n reject(err);\n }\n\n function resolver() {\n if (typeof emitter.removeListener === 'function') {\n emitter.removeListener('error', errorListener);\n }\n resolve([].slice.call(arguments));\n };\n\n eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });\n if (name !== 'error') {\n addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });\n }\n });\n}\n\nfunction addErrorHandlerIfEventEmitter(emitter, handler, flags) {\n if (typeof emitter.on === 'function') {\n eventTargetAgnosticAddListener(emitter, 'error', handler, flags);\n }\n}\n\nfunction eventTargetAgnosticAddListener(emitter, name, listener, flags) {\n if (typeof emitter.on === 'function') {\n if (flags.once) {\n emitter.once(name, listener);\n } else {\n emitter.on(name, listener);\n }\n } else if (typeof emitter.addEventListener === 'function') {\n // EventTarget does not have `error` event semantics like Node\n // EventEmitters, we do not listen for `error` events here.\n emitter.addEventListener(name, function wrapListener(arg) {\n // IE does not have builtin `{ once: true }` support so we\n // have to do it manually.\n if (flags.once) {\n emitter.removeEventListener(name, wrapListener);\n }\n listener(arg);\n });\n } else {\n throw new TypeError('The \"emitter\" argument must be of type EventEmitter. Received type ' + typeof emitter);\n }\n}\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nclass EventTarget {\n constructor() {\n Object.defineProperties(this, {\n _eventEmitter: {\n value: new EventEmitter()\n }\n });\n }\n\n dispatchEvent(event) {\n return this._eventEmitter.emit(event.type, event);\n }\n\n addEventListener() {\n return this._eventEmitter.addListener(...arguments);\n }\n\n removeEventListener() {\n return this._eventEmitter.removeListener(...arguments);\n }\n}\n\nmodule.exports = EventTarget;\n", "'use strict';\n\nconst { defer } = require('./');\n\nconst states = {\n high: new Set(['low']),\n low: new Set(['high'])\n};\n\n/**\n * Construct a {@link Latch}.\n * @class\n * @classdesc A {@link Latch} has two states (\"high\" and \"low\") and methods for\n * transitioning between them ({@link Latch#raise} and {@link Latch#lower}).\n * @param {string} [initialState=\"low\"] - either \"high\" or \"low\"\n */\nclass Latch {\n constructor(initialState = 'low') {\n let state = initialState;\n Object.defineProperties(this, {\n _state: {\n set: function(_state) {\n if (state !== _state) {\n state = _state;\n const whenDeferreds = this._whenDeferreds.get(state);\n whenDeferreds.forEach(deferred => deferred.resolve(this));\n whenDeferreds.clear();\n }\n },\n get: function() {\n return state;\n }\n },\n _whenDeferreds: {\n value: new Map([\n ['high', new Set()],\n ['low', new Set()]\n ])\n }\n });\n }\n\n get state() {\n return this._state;\n }\n\n /**\n * Transition to \"low\".\n * @returns {this}\n * @throws {Error}\n */\n lower() {\n return this.transition('low');\n }\n\n /**\n * Transition to \"high\".\n * @returns {this}\n * @throws {Error}\n */\n raise() {\n return this.transition('high');\n }\n\n /**\n * Transition to a new state.\n * @param {string} newState\n * @returns {this}\n * @throws {Error}\n */\n transition(newState) {\n if (!states[this.state].has(newState)) {\n throw createUnreachableStateError(this.state, newState);\n }\n this._state = newState;\n return this;\n }\n\n /**\n * Return a Promise that resolves when the {@link Latch} transitions to\n * the specified state.\n * @param {string} state\n * @returns {Promise}\n */\n when(state) {\n if (this.state === state) {\n return Promise.resolve(this);\n }\n if (!states[this.state].has(state)) {\n return Promise.reject(createUnreachableStateError(this.state, state));\n }\n const deferred = defer();\n this._whenDeferreds.get(state).add(deferred);\n return deferred.promise;\n }\n}\n\n/**\n * Create an unreachable state Error.\n * @param {string} from - state to be transitioned from\n * @param {string} to - state to be transitioned to\n * @return {Error}\n */\nfunction createUnreachableStateError(from, to) {\n return new Error(`Cannot transition from \"${from}\" to \"${to}\"`);\n}\n\nmodule.exports = Latch;\n", "'use strict';\n\n/**\n * RTCRtpSender shim.\n * @param {MediaStreamTrack} track\n * @property {MediaStreamTrack} track\n */\nclass RTCRtpSenderShim {\n constructor(track) {\n Object.defineProperties(this, {\n track: {\n enumerable: true,\n value: track,\n writable: true\n }\n });\n }\n}\n\n// NOTE(mmalavalli): Because of the way we will be using this shim, there\n// are a couple of use cases that will not be covered:\n//\n// /* Case 1 */\n// const sender = pc.addTrack(track);\n// assert.equal(sender.track, track);\n// pc.removeTrack(sender);\n// assert.equal(sender.track, null); /* Error */\n//\n// /* Case 2 */\n// const sender = pc.addTrack(track);\n// const senders1 = new Set(pc.getSenders());\n// assert(senders1.has(sender));\n// pc.removeTrack(track);\n// const senders2 = new Set(pc.getSenders());\n// assert(senders2.has(sender)); /* Error */\n//\n// For now, since we only use senders for passing them to RTCPeerConnection#removeTrack(),\n// we will omit handling these use cases for now, and revisit them when we start\n// using the RTCRtpSender APIs.\n\nmodule.exports = RTCRtpSenderShim;\n", "/* globals RTCDataChannel, RTCPeerConnection, RTCSessionDescription */\n'use strict';\n\nconst ChromeRTCSessionDescription = require('../rtcsessiondescription/chrome');\nconst EventTarget = require('../../eventtarget');\nconst Latch = require('../util/latch');\nconst MediaStream = require('../mediastream');\nconst RTCRtpSenderShim = require('../rtcrtpsender');\nconst { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, isIOSChrome, legacyPromise, proxyProperties } = require('../util');\n\nconst isUnifiedPlan = getSdpFormat() === 'unified';\n\n// NOTE(mroberts): This class wraps Chrome's RTCPeerConnection implementation.\n// It provides some functionality not currently present in Chrome, namely the\n// abilities to\n//\n// 1. Rollback, per the workaround suggested here:\n// https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3\n//\n// 2. Listen for track events, per the adapter.js workaround.\n//\n// 3. Set iceTransportPolicy.\n//\nclass ChromeRTCPeerConnection extends EventTarget {\n constructor(configuration = {}, constraints) {\n super();\n\n const newConfiguration = Object.assign(configuration.iceTransportPolicy\n ? { iceTransports: configuration.iceTransportPolicy }\n : {}, configuration);\n\n interceptEvent(this, 'datachannel');\n interceptEvent(this, 'signalingstatechange');\n const sdpFormat = getSdpFormat(newConfiguration.sdpSemantics);\n const peerConnection = new RTCPeerConnection(newConfiguration, constraints);\n\n Object.defineProperties(this, {\n _appliedTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _localStream: {\n value: new MediaStream()\n },\n _peerConnection: {\n value: peerConnection\n },\n _pendingLocalOffer: {\n value: null,\n writable: true\n },\n _pendingRemoteOffer: {\n value: null,\n writable: true\n },\n _rolledBackTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _sdpFormat: {\n value: sdpFormat\n },\n _senders: {\n value: new Map()\n },\n _signalingStateLatch: {\n value: new Latch()\n },\n _tracksToSSRCs: {\n value: new Map(),\n writable: true\n }\n });\n\n peerConnection.addEventListener('datachannel', event => {\n shimDataChannel(event.channel);\n this.dispatchEvent(event);\n });\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (peerConnection.signalingState === 'stable') {\n this._appliedTracksToSSRCs = new Map(this._tracksToSSRCs);\n }\n if (!this._pendingLocalOffer && !this._pendingRemoteOffer) {\n this.dispatchEvent(...args);\n }\n });\n\n peerConnection.ontrack = () => {\n // NOTE(mroberts): adapter.js's \"track\" event shim only kicks off if we set\n // the ontrack property of the RTCPeerConnection.\n };\n\n if (typeof peerConnection.addTrack !== 'function') {\n peerConnection.addStream(this._localStream);\n }\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n }\n\n get localDescription() {\n return this._pendingLocalOffer ? this._pendingLocalOffer : this._peerConnection.localDescription;\n }\n\n get remoteDescription() {\n return this._pendingRemoteOffer ? this._pendingRemoteOffer : this._peerConnection.remoteDescription;\n }\n\n get signalingState() {\n if (this._pendingLocalOffer) {\n return 'have-local-offer';\n } else if (this._pendingRemoteOffer) {\n return 'have-remote-offer';\n }\n return this._peerConnection.signalingState;\n }\n\n // NOTE(mmalavalli): This shim supports our limited case of adding\n // all MediaStreamTracks to one MediaStream. It has been implemented this\n // keeping in mind that this is to be maintained only until \"addTrack\" is\n // supported natively in Chrome.\n addTrack(track, ...rest) {\n if (typeof this._peerConnection.addTrack === 'function') {\n return this._peerConnection.addTrack(track, ...rest);\n }\n if (this._peerConnection.signalingState === 'closed') {\n throw new Error(`Cannot add MediaStreamTrack [${track.id}, \n ${track.kind}]: RTCPeerConnection is closed`);\n }\n\n let sender = this._senders.get(track);\n if (sender && sender.track) {\n throw new Error(`Cannot add MediaStreamTrack ['${track.id}, \n ${track.kind}]: RTCPeerConnection already has it`);\n }\n this._peerConnection.removeStream(this._localStream);\n this._localStream.addTrack(track);\n this._peerConnection.addStream(this._localStream);\n sender = new RTCRtpSenderShim(track);\n this._senders.set(track, sender);\n return sender;\n }\n\n // NOTE(mmalavalli): This shim supports our limited case of removing\n // MediaStreamTracks from one MediaStream. It has been implemented this\n // keeping in mind that this is to be maintained only until \"removeTrack\" is\n // supported natively in Chrome.\n removeTrack(sender) {\n if (this._peerConnection.signalingState === 'closed') {\n throw new Error('Cannot remove MediaStreamTrack: RTCPeerConnection is closed');\n }\n if (typeof this._peerConnection.addTrack === 'function') {\n try {\n return this._peerConnection.removeTrack(sender);\n } catch (e) {\n // NOTE(mhuynh): Do nothing. In Chrome, will throw if a 'sender was not\n // created by this peer connection'. This behavior does not seem to be\n // spec compliant, so a temporary shim is introduced. A bug has been filed,\n // and is tracked here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=860853\n }\n } else {\n const { track } = sender;\n if (!track) {\n return;\n }\n sender = this._senders.get(track);\n if (sender && sender.track) {\n sender.track = null;\n this._peerConnection.removeStream(this._localStream);\n this._localStream.removeTrack(track);\n this._peerConnection.addStream(this._localStream);\n }\n }\n }\n\n getSenders() {\n if (typeof this._peerConnection.addTrack === 'function') {\n return this._peerConnection.getSenders();\n }\n return Array.from(this._senders.values());\n }\n\n addIceCandidate(candidate, ...rest) {\n let promise;\n\n if (this.signalingState === 'have-remote-offer') {\n // NOTE(mroberts): Because the ChromeRTCPeerConnection simulates the\n // \"have-remote-offer\" signalingStates, we only want to invoke the true\n // addIceCandidates method when the remote description has been applied.\n promise = this._signalingStateLatch.when('low').then(() =>\n this._peerConnection.addIceCandidate(candidate));\n } else {\n promise = this._peerConnection.addIceCandidate(candidate);\n }\n\n return rest.length > 0\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec does not specify that close should throw an\n // Error; however, in Chrome it does. We workaround this by checking the\n // signalingState manually.\n close() {\n if (this.signalingState !== 'closed') {\n this._pendingLocalOffer = null;\n this._pendingRemoteOffer = null;\n this._peerConnection.close();\n }\n }\n\n // NOTE(mroberts): Because we workaround Chrome's lack of rollback support by\n // \"faking\" setRemoteDescription, we cannot create an answer until we actually\n // apply the remote description. This means, once you call createAnswer, you\n // can no longer rollback. This is acceptable for our use case because we will\n // apply the newly-created answer almost immediately; however, this may be\n // unacceptable for other use cases.\n createAnswer(...args) {\n let promise;\n\n if (this._pendingRemoteOffer) {\n promise = this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(() => {\n // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation have converged. We\n // can unblock any pending calls to addIceCandidate now.\n this._signalingStateLatch.lower();\n return this._peerConnection.createAnswer();\n }).then(answer => {\n this._pendingRemoteOffer = null;\n\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: 'answer',\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp)\n });\n }, error => {\n this._pendingRemoteOffer = null;\n throw error;\n });\n } else {\n promise = this._peerConnection.createAnswer().then(answer => {\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: 'answer',\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, answer.sdp)\n });\n });\n }\n\n return args.length > 1\n ? legacyPromise(promise, ...args)\n : promise;\n }\n\n createOffer(...args) {\n const [arg1, arg2, arg3] = args;\n const options = arg3 || arg1 || {};\n\n if (isIOSChrome()) {\n // NOTE (joma): From SafariRTCPeerConnection in order to support iOS Chrome.\n if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) {\n delete options.offerToReceiveAudio;\n try {\n this._audioTransceiver = isUnifiedPlan\n ? this.addTransceiver('audio', { direction: 'recvonly' })\n : this.addTransceiver('audio');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) {\n delete options.offerToReceiveVideo;\n try {\n this._videoTransceiver = isUnifiedPlan\n ? this.addTransceiver('video', { direction: 'recvonly' })\n : this.addTransceiver('video');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n }\n\n const promise = this._peerConnection.createOffer(options).then(offer => {\n // NOTE(mmalavalli): If createOffer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new ChromeRTCSessionDescription({\n type: offer.type,\n sdp: updateTrackIdsToSSRCs(this._sdpFormat, this._tracksToSSRCs, offer.sdp)\n });\n });\n\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n createDataChannel(label, dataChannelDict) {\n dataChannelDict = shimDataChannelInit(dataChannelDict);\n const dataChannel = this._peerConnection.createDataChannel(label, dataChannelDict);\n shimDataChannel(dataChannel);\n return dataChannel;\n }\n\n setLocalDescription(...args) {\n const [description, arg1, arg2] = args;\n\n // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back,\n // then we need to restore the rolled back tracks to SSRCs Map.\n if (this._rolledBackTracksToSSRCs.size > 0) {\n this._tracksToSSRCs = new Map(this._rolledBackTracksToSSRCs);\n this._rolledBackTracksToSSRCs.clear();\n }\n\n const promise = setDescription(this, true, description);\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n setRemoteDescription(...args) {\n const [description, arg1, arg2] = args;\n\n // NOTE(mmalavalli): If setRemoteDescription() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n const promise = setDescription(this, false, description);\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n ChromeRTCPeerConnection.prototype,\n '_peerConnection');\n\n// NOTE(mroberts): We workaround Chrome's lack of rollback support, per the\n// workaround suggested here: https://bugs.chromium.org/p/webrtc/issues/detail?id=5738#c3\n// Namely, we \"fake\" setting the local or remote description and instead buffer\n// it. If we receive or create an answer, then we will actually apply the\n// description. Until we receive or create an answer, we will be able to\n// \"rollback\" by simply discarding the buffer description.\nfunction setDescription(peerConnection, local, description) {\n function setPendingLocalOffer(offer) {\n if (local) {\n peerConnection._pendingLocalOffer = offer;\n } else {\n peerConnection._pendingRemoteOffer = offer;\n }\n }\n\n function clearPendingLocalOffer() {\n if (local) {\n peerConnection._pendingLocalOffer = null;\n } else {\n peerConnection._pendingRemoteOffer = null;\n }\n }\n\n const pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer;\n const pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer;\n const intermediateState = local ? 'have-local-offer' : 'have-remote-offer';\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n let promise;\n\n if (!local && pendingRemoteOffer && description.type === 'answer') {\n promise = setRemoteAnswer(peerConnection, description);\n } else if (description.type === 'offer') {\n if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') {\n // NOTE(mroberts): Error message copied from Firefox.\n return Promise.reject(new Error(`Cannot set ${local ? 'local' : 'remote'} offer in state ${peerConnection.signalingState}`));\n }\n\n // We need to save this local offer in case of a rollback. We also need to\n // check to see if the signalingState between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation are about to diverge.\n // If so, we need to ensure subsequent calls to addIceCandidate will block.\n if (!pendingLocalOffer && peerConnection._signalingStateLatch.state === 'low') {\n peerConnection._signalingStateLatch.raise();\n }\n const previousSignalingState = peerConnection.signalingState;\n setPendingLocalOffer(unwrap(description));\n promise = Promise.resolve();\n\n // Only dispatch a signalingstatechange event if we transitioned.\n if (peerConnection.signalingState !== previousSignalingState) {\n promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n } else if (description.type === 'rollback') {\n if (peerConnection.signalingState !== intermediateState) {\n // NOTE(mroberts): Error message copied from Firefox.\n promise = Promise.reject(new Error(`Cannot rollback ${local ? 'local' : 'remote'} description in ${peerConnection.signalingState}`));\n } else {\n // Reset the pending offer.\n clearPendingLocalOffer();\n\n // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case\n // setLocalDescription() is called immediately after a rollback (without calling\n // createOffer() or createAnswer()), in which case this roll back is not due to a\n // glare scenario and this Map should be restored.\n peerConnection._rolledBackTracksToSSRCs = new Map(peerConnection._tracksToSSRCs);\n peerConnection._tracksToSSRCs = new Map(peerConnection._appliedTracksToSSRCs);\n\n promise = Promise.resolve();\n promise.then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n }\n\n return promise || peerConnection._peerConnection[setLocalDescription](unwrap(description));\n}\n\nfunction setRemoteAnswer(peerConnection, answer) {\n // Apply the pending local offer.\n const pendingLocalOffer = peerConnection._pendingLocalOffer;\n return peerConnection._peerConnection.setLocalDescription(pendingLocalOffer).then(() => {\n peerConnection._pendingLocalOffer = null;\n return peerConnection.setRemoteDescription(answer);\n }).then(() => {\n // NOTE(mroberts): The signalingStates between the ChromeRTCPeerConnection\n // and the underlying RTCPeerConnection implementation have converged. We\n // can unblock any pending calls to addIceCandidate now.\n peerConnection._signalingStateLatch.lower();\n });\n}\n\n/**\n * Whether a ChromeRTCPeerConnection has any RTCRtpReceivers(s) for the given\n * MediaStreamTrack kind.\n * @param {ChromeRTCPeerConnection} peerConnection\n * @param {'audio' | 'video'} kind\n * @returns {boolean}\n */\nfunction hasReceiversForTracksOfKind(peerConnection, kind) {\n return !!peerConnection.getTransceivers().find(({ receiver = {} }) => {\n const { track = {} } = receiver;\n return track.kind === kind;\n });\n}\n\nfunction unwrap(description) {\n if (description instanceof ChromeRTCSessionDescription) {\n if (description._description) {\n return description._description;\n }\n }\n return new RTCSessionDescription(description);\n}\n\n/**\n * Check whether or not we need to apply our maxPacketLifeTime shim. We are\n * pretty conservative: we'll only apply it if the legacy maxRetransmitTime\n * property is available _and_ the standard maxPacketLifeTime property is _not_\n * available (the thinking being that Chrome will land the standards-compliant\n * property).\n * @returns {boolean}\n */\nfunction needsMaxPacketLifeTimeShim() {\n return 'maxRetransmitTime' in RTCDataChannel.prototype\n && !('maxPacketLifeTime' in RTCDataChannel.prototype);\n}\n\n/**\n * Shim an RTCDataChannelInit dictionary (if necessary). This function returns\n * a copy of the original RTCDataChannelInit.\n * @param {RTCDataChannelInit} dataChannelDict\n * @returns {RTCDataChannelInit}\n */\nfunction shimDataChannelInit(dataChannelDict) {\n dataChannelDict = Object.assign({}, dataChannelDict);\n if (needsMaxPacketLifeTimeShim() && 'maxPacketLifeTime' in dataChannelDict) {\n dataChannelDict.maxRetransmitTime = dataChannelDict.maxPacketLifeTime;\n }\n return dataChannelDict;\n}\n\n/**\n * Shim an RTCDataChannel (if necessary). This function mutates the\n * RTCDataChannel.\n * @param {RTCDataChannel} dataChannel\n * @returns {RTCDataChannel}\n */\nfunction shimDataChannel(dataChannel) {\n Object.defineProperty(dataChannel, 'maxRetransmits', {\n value: dataChannel.maxRetransmits === 65535\n ? null\n : dataChannel.maxRetransmits\n });\n if (needsMaxPacketLifeTimeShim()) {\n // NOTE(mroberts): We can rename `maxRetransmitTime` to `maxPacketLifeTime`.\n //\n // https://bugs.chromium.org/p/chromium/issues/detail?id=696681\n //\n Object.defineProperty(dataChannel, 'maxPacketLifeTime', {\n value: dataChannel.maxRetransmitTime === 65535\n ? null\n : dataChannel.maxRetransmitTime\n });\n }\n return dataChannel;\n}\n\n/**\n * Update the mappings from MediaStreamTrack IDs to SSRCs as indicated by both\n * the Map from MediaStreamTrack IDs to SSRCs and the SDP itself. This method\n * ensures that SSRCs never change once announced.\n * @param {'planb'|'unified'} sdpFormat\n * @param {Map>} tracksToSSRCs\n * @param {string} sdp - an SDP whose format is determined by `sdpSemantics`\n * @returns {string} updatedSdp - updated SDP\n */\nfunction updateTrackIdsToSSRCs(sdpFormat, tracksToSSRCs, sdp) {\n return sdpFormat === 'unified'\n ? updateUnifiedPlanTrackIdsToSSRCs(tracksToSSRCs, sdp)\n : updatePlanBTrackIdsToSSRCs(tracksToSSRCs, sdp);\n}\n\nmodule.exports = ChromeRTCPeerConnection;\n", "/* globals RTCSessionDescription */\n'use strict';\n\nmodule.exports = RTCSessionDescription;\n", "/* globals RTCPeerConnection */\n'use strict';\n\nconst EventTarget = require('../../eventtarget');\nconst FirefoxRTCSessionDescription = require('../rtcsessiondescription/firefox');\nconst { updateUnifiedPlanTrackIdsToSSRCs: updateTracksToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, legacyPromise, proxyProperties } = require('../util');\n\n// NOTE(mroberts): This class wraps Firefox's RTCPeerConnection implementation.\n// It provides some functionality not currently present in Firefox, namely the\n// abilities to\n//\n// 1. Call setLocalDescription and setRemoteDescription with new offers in\n// signalingStates \"have-local-offer\" and \"have-remote-offer\",\n// respectively.\n//\n// 2. The ability to call createOffer in signalingState \"have-local-offer\".\n//\n// Both of these are implemented using rollbacks to workaround the following\n// bug:\n//\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1072388\n//\n// We also provide a workaround for a bug where Firefox may change the\n// previously-negotiated DTLS role in an answer, which breaks Chrome:\n//\n// https://bugzilla.mozilla.org/show_bug.cgi?id=1240897\n//\nclass FirefoxRTCPeerConnection extends EventTarget {\n constructor(configuration) {\n super();\n\n interceptEvent(this, 'signalingstatechange');\n\n /* eslint new-cap:0 */\n const peerConnection = new RTCPeerConnection(configuration);\n\n Object.defineProperties(this, {\n _initiallyNegotiatedDtlsRole: {\n value: null,\n writable: true\n },\n _isClosed: {\n value: false,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _rollingBack: {\n value: false,\n writable: true\n },\n _tracksToSSRCs: {\n value: new Map()\n },\n\n // NOTE(mmalavalli): Firefox throws a TypeError when the PeerConnection's\n // prototype's \"peerIdentity\" property is accessed. In order to overcome\n // this, we ignore this property while delegating methods.\n // Reference: https://bugzilla.mozilla.org/show_bug.cgi?id=1363815\n peerIdentity: {\n enumerable: true,\n value: Promise.resolve({\n idp: '',\n name: ''\n })\n }\n });\n\n let previousSignalingState;\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (!this._rollingBack && this.signalingState !== previousSignalingState) {\n previousSignalingState = this.signalingState;\n\n // NOTE(mmalavalli): In Firefox, 'signalingstatechange' event is\n // triggered synchronously in the same tick after\n // RTCPeerConnection#close() is called. So we mimic Chrome's behavior\n // by triggering 'signalingstatechange' on the next tick.\n if (this._isClosed) {\n setTimeout(() => this.dispatchEvent(...args));\n } else {\n this.dispatchEvent(...args);\n }\n }\n });\n\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n }\n\n get iceGatheringState() {\n return this._isClosed ? 'complete' : this._peerConnection.iceGatheringState;\n }\n\n get localDescription() {\n return overwriteWithInitiallyNegotiatedDtlsRole(this._peerConnection.localDescription, this._initiallyNegotiatedDtlsRole);\n }\n\n get signalingState() {\n return this._isClosed ? 'closed' : this._peerConnection.signalingState;\n }\n\n createAnswer(...args) {\n let promise;\n\n promise = this._peerConnection.createAnswer().then(answer => {\n saveInitiallyNegotiatedDtlsRole(this, answer);\n return overwriteWithInitiallyNegotiatedDtlsRole(answer, this._initiallyNegotiatedDtlsRole);\n });\n\n return typeof args[0] === 'function'\n ? legacyPromise(promise, ...args)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec allows you to call createOffer from any\n // signalingState other than \"closed\"; however, Firefox has not yet implemented\n // this (https://bugzilla.mozilla.org/show_bug.cgi?id=1072388). We workaround\n // this by rolling back if we are in state \"have-local-offer\" or\n // \"have-remote-offer\". This is acceptable for our use case because we will\n // apply the newly-created offer almost immediately; however, this may be\n // unacceptable for other use cases.\n createOffer(...args) {\n const [arg1, arg2, arg3] = args;\n const options = arg3 || arg1 || {};\n let promise;\n\n if (this.signalingState === 'have-local-offer' ||\n this.signalingState === 'have-remote-offer') {\n const local = this.signalingState === 'have-local-offer';\n promise = rollback(this, local, () => this.createOffer(options));\n } else {\n promise = this._peerConnection.createOffer(options);\n }\n\n promise = promise.then(offer => {\n return new FirefoxRTCSessionDescription({\n type: offer.type,\n sdp: updateTracksToSSRCs(this._tracksToSSRCs, offer.sdp)\n });\n });\n\n return args.length > 1\n ? legacyPromise(promise, arg1, arg2)\n : promise;\n }\n\n // NOTE(mroberts): While Firefox will reject the Promise returned by\n // setLocalDescription when called from signalingState \"have-local-offer\" with\n // an answer, it still updates the .localDescription property. We workaround\n // this by explicitly handling this case.\n setLocalDescription(...args) {\n const [description, ...rest] = args;\n let promise;\n\n if (description && description.type === 'answer' && this.signalingState === 'have-local-offer') {\n promise = Promise.reject(new Error('Cannot set local answer in state have-local-offer'));\n }\n\n if (promise) {\n return args.length > 1\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n return this._peerConnection.setLocalDescription(...args);\n }\n\n // NOTE(mroberts): The WebRTC spec allows you to call setRemoteDescription with\n // an offer multiple times in signalingState \"have-remote-offer\"; however,\n // Firefox has not yet implemented this (https://bugzilla.mozilla.org/show_bug.cgi?id=1072388).\n // We workaround this by rolling back if we are in state \"have-remote-offer\".\n // This is acceptable for our use case; however, this may be unacceptable for\n // other use cases.\n //\n // While Firefox will reject the Promise returned by setRemoteDescription when\n // called from signalingState \"have-remote-offer\" with an answer, it sill\n // updates the .remoteDescription property. We workaround this by explicitly\n // handling this case.\n setRemoteDescription(...args) {\n const [description, ...rest] = args;\n\n let promise;\n\n if (description && this.signalingState === 'have-remote-offer') {\n if (description.type === 'answer') {\n promise = Promise.reject(new Error('Cannot set remote answer in state have-remote-offer'));\n } else if (description.type === 'offer') {\n promise = rollback(this, false, () => this._peerConnection.setRemoteDescription(description));\n }\n }\n\n if (!promise) {\n promise = this._peerConnection.setRemoteDescription(description);\n }\n\n promise = promise.then(() => saveInitiallyNegotiatedDtlsRole(this, description, true));\n\n return args.length > 1\n ? legacyPromise(promise, ...rest)\n : promise;\n }\n\n // NOTE(mroberts): The WebRTC spec specifies that the PeerConnection's internal\n // isClosed slot should immediately be set to true; however, in Firefox it\n // occurs in the next tick. We workaround this by tracking isClosed manually.\n close() {\n if (this.signalingState !== 'closed') {\n this._isClosed = true;\n this._peerConnection.close();\n }\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n FirefoxRTCPeerConnection.prototype,\n '_peerConnection');\n\nfunction rollback(peerConnection, local, onceRolledBack) {\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n peerConnection._rollingBack = true;\n return peerConnection._peerConnection[setLocalDescription](new FirefoxRTCSessionDescription({\n type: 'rollback'\n })).then(onceRolledBack).then(result => {\n peerConnection._rollingBack = false;\n return result;\n }, error => {\n peerConnection._rollingBack = false;\n throw error;\n });\n}\n\n/**\n * Extract the initially negotiated DTLS role out of an RTCSessionDescription's\n * sdp property and save it on the FirefoxRTCPeerConnection if and only if\n *\n * 1. A DTLS role was not already saved on the FirefoxRTCPeerConnection, and\n * 2. The description is an answer.\n *\n * @private\n * @param {FirefoxRTCPeerConnection} peerConnection\n * @param {RTCSessionDescription} description\n * @param {boolean} [remote=false] - if true, save the inverse of the DTLS role,\n * e.g. \"active\" instead of \"passive\" and vice versa\n * @returns {undefined}\n */\nfunction saveInitiallyNegotiatedDtlsRole(peerConnection, description, remote) {\n // NOTE(mroberts): JSEP specifies that offers always offer \"actpass\" as the\n // DTLS role. We need to inspect answers to figure out the negotiated DTLS\n // role.\n if (peerConnection._initiallyNegotiatedDtlsRole || description.type === 'offer') {\n return;\n }\n\n const match = description.sdp.match(/a=setup:([a-z]+)/);\n if (!match) {\n return;\n }\n\n const dtlsRole = match[1];\n peerConnection._initiallyNegotiatedDtlsRole = remote ? {\n active: 'passive',\n passive: 'active'\n }[dtlsRole] : dtlsRole;\n}\n\n/**\n * Overwrite the DTLS role in the sdp property of an RTCSessionDescription if\n * and only if\n *\n * 1. The description is an answer, and\n * 2. A DTLS role is provided.\n *\n * @private\n * @param {RTCSessionDescription} [description]\n * @param {string} [dtlsRole] - one of \"active\" or \"passive\"\n * @returns {?RTCSessionDescription} description\n */\nfunction overwriteWithInitiallyNegotiatedDtlsRole(description, dtlsRole) {\n if (description && description.type === 'answer' && dtlsRole) {\n return new FirefoxRTCSessionDescription({\n type: description.type,\n sdp: description.sdp.replace(/a=setup:[a-z]+/g, 'a=setup:' + dtlsRole)\n });\n }\n return description;\n}\n\nmodule.exports = FirefoxRTCPeerConnection;\n", "/* globals RTCPeerConnection, RTCSessionDescription */\n'use strict';\n\nconst EventTarget = require('../../eventtarget');\nconst Latch = require('../util/latch');\nconst { getSdpFormat, updatePlanBTrackIdsToSSRCs, updateUnifiedPlanTrackIdsToSSRCs } = require('../util/sdp');\nconst { delegateMethods, interceptEvent, proxyProperties } = require('../util');\n\nconst isUnifiedPlan = getSdpFormat() === 'unified';\n\nconst updateTrackIdsToSSRCs = isUnifiedPlan\n ? updateUnifiedPlanTrackIdsToSSRCs\n : updatePlanBTrackIdsToSSRCs;\n\nclass SafariRTCPeerConnection extends EventTarget {\n constructor(configuration) {\n super();\n\n interceptEvent(this, 'datachannel');\n interceptEvent(this, 'iceconnectionstatechange');\n interceptEvent(this, 'signalingstatechange');\n interceptEvent(this, 'track');\n\n const peerConnection = new RTCPeerConnection(configuration);\n\n Object.defineProperties(this, {\n _appliedTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _audioTransceiver: {\n value: null,\n writable: true\n },\n _isClosed: {\n value: false,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _pendingLocalOffer: {\n value: null,\n writable: true\n },\n _pendingRemoteOffer: {\n value: null,\n writable: true\n },\n _rolledBackTracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _signalingStateLatch: {\n value: new Latch()\n },\n _tracksToSSRCs: {\n value: new Map(),\n writable: true\n },\n _videoTransceiver: {\n value: null,\n writable: true\n }\n });\n\n peerConnection.addEventListener('datachannel', event => {\n shimDataChannel(event.channel);\n this.dispatchEvent(event);\n });\n\n peerConnection.addEventListener('iceconnectionstatechange', (...args) => {\n if (this._isClosed) {\n return;\n }\n this.dispatchEvent(...args);\n });\n\n peerConnection.addEventListener('signalingstatechange', (...args) => {\n if (this._isClosed) {\n return;\n }\n if (peerConnection.signalingState === 'stable') {\n this._appliedTracksToSSRCs = new Map(this._tracksToSSRCs);\n }\n if (!this._pendingLocalOffer && !this._pendingRemoteOffer) {\n this.dispatchEvent(...args);\n }\n });\n\n // NOTE(syerrapragada): This ensures that SafariRTCPeerConnection's \"remoteDescription\", when accessed\n // in an RTCTrackEvent listener, will point to the underlying RTCPeerConnection's\n // \"remoteDescription\". Before this fix, this was still pointing to \"_pendingRemoteOffer\"\n // even though a new remote RTCSessionDescription had already been applied.\n peerConnection.addEventListener('track', event => {\n this._pendingRemoteOffer = null;\n this.dispatchEvent(event);\n });\n\n proxyProperties(RTCPeerConnection.prototype, this, peerConnection);\n\n }\n\n get localDescription() {\n return this._pendingLocalOffer || this._peerConnection.localDescription;\n }\n\n get iceConnectionState() {\n return this._isClosed ? 'closed' : this._peerConnection.iceConnectionState;\n }\n\n get iceGatheringState() {\n return this._isClosed ? 'complete' : this._peerConnection.iceGatheringState;\n }\n\n get remoteDescription() {\n return this._pendingRemoteOffer || this._peerConnection.remoteDescription;\n }\n\n get signalingState() {\n if (this._isClosed) {\n return 'closed';\n } else if (this._pendingLocalOffer) {\n return 'have-local-offer';\n } else if (this._pendingRemoteOffer) {\n return 'have-remote-offer';\n }\n return this._peerConnection.signalingState;\n }\n\n addIceCandidate(candidate) {\n if (this.signalingState === 'have-remote-offer') {\n return this._signalingStateLatch.when('low').then(() => this._peerConnection.addIceCandidate(candidate));\n }\n return this._peerConnection.addIceCandidate(candidate);\n }\n\n createOffer(options) {\n options = Object.assign({}, options);\n\n // NOTE(mroberts): In general, this is not the way to do this; however, it's\n // good enough for our application.\n if (options.offerToReceiveVideo && !this._audioTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'audio'))) {\n delete options.offerToReceiveAudio;\n try {\n this._audioTransceiver = isUnifiedPlan\n ? this.addTransceiver('audio', { direction: 'recvonly' })\n : this.addTransceiver('audio');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n if (options.offerToReceiveVideo && !this._videoTransceiver && !(isUnifiedPlan && hasReceiversForTracksOfKind(this, 'video'))) {\n delete options.offerToReceiveVideo;\n try {\n this._videoTransceiver = isUnifiedPlan\n ? this.addTransceiver('video', { direction: 'recvonly' })\n : this.addTransceiver('video');\n } catch (e) {\n return Promise.reject(e);\n }\n }\n\n return this._peerConnection.createOffer(options).then(offer => {\n // NOTE(mmalavalli): If createOffer() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return new RTCSessionDescription({\n type: offer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, offer.sdp)\n });\n });\n }\n\n createAnswer(options) {\n if (this._pendingRemoteOffer) {\n return this._peerConnection.setRemoteDescription(this._pendingRemoteOffer).then(() => {\n this._signalingStateLatch.lower();\n return this._peerConnection.createAnswer();\n }).then(answer => {\n this._pendingRemoteOffer = null;\n\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return isUnifiedPlan ? new RTCSessionDescription({\n type: answer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp)\n }) : answer;\n }, error => {\n this._pendingRemoteOffer = null;\n throw error;\n });\n }\n\n return this._peerConnection.createAnswer(options).then(answer => {\n // NOTE(mmalavalli): If createAnswer() is called immediately after rolling back, then we no\n // longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n\n return isUnifiedPlan ? new RTCSessionDescription({\n type: answer.type,\n sdp: updateTrackIdsToSSRCs(this._tracksToSSRCs, answer.sdp)\n }) : answer;\n });\n }\n\n createDataChannel(label, dataChannelDict) {\n const dataChannel = this._peerConnection.createDataChannel(label, dataChannelDict);\n shimDataChannel(dataChannel);\n return dataChannel;\n }\n\n removeTrack(sender) {\n sender.replaceTrack(null);\n this._peerConnection.removeTrack(sender);\n }\n\n setLocalDescription(description) {\n // NOTE(mmalavalli): If setLocalDescription() is called immediately after rolling back,\n // then we need to restore the rolled back tracks to SSRCs Map.\n if (this._rolledBackTracksToSSRCs.size > 0) {\n this._tracksToSSRCs = new Map(this._rolledBackTracksToSSRCs);\n this._rolledBackTracksToSSRCs.clear();\n }\n return setDescription(this, true, description);\n }\n\n setRemoteDescription(description) {\n // NOTE(mmalavalli): If setRemoteDescription() is called immediately after rolling back,\n // then we no longer need to retain the rolled back tracks to SSRCs Map.\n this._rolledBackTracksToSSRCs.clear();\n return setDescription(this, false, description);\n }\n\n close() {\n if (this._isClosed) {\n return;\n }\n this._isClosed = true;\n this._peerConnection.close();\n setTimeout(() => {\n this.dispatchEvent(new Event('iceconnectionstatechange'));\n this.dispatchEvent(new Event('signalingstatechange'));\n });\n }\n}\n\ndelegateMethods(\n RTCPeerConnection.prototype,\n SafariRTCPeerConnection.prototype,\n '_peerConnection');\n\nfunction setDescription(peerConnection, local, description) {\n function setPendingLocalOffer(offer) {\n if (local) {\n peerConnection._pendingLocalOffer = offer;\n } else {\n peerConnection._pendingRemoteOffer = offer;\n }\n }\n\n function clearPendingLocalOffer() {\n if (local) {\n peerConnection._pendingLocalOffer = null;\n } else {\n peerConnection._pendingRemoteOffer = null;\n }\n }\n\n const pendingLocalOffer = local ? peerConnection._pendingLocalOffer : peerConnection._pendingRemoteOffer;\n const pendingRemoteOffer = local ? peerConnection._pendingRemoteOffer : peerConnection._pendingLocalOffer;\n const intermediateState = local ? 'have-local-offer' : 'have-remote-offer';\n const setLocalDescription = local ? 'setLocalDescription' : 'setRemoteDescription';\n\n if (!local && pendingRemoteOffer && description.type === 'answer') {\n return setRemoteAnswer(peerConnection, description);\n } else if (description.type === 'offer') {\n if (peerConnection.signalingState !== intermediateState && peerConnection.signalingState !== 'stable') {\n return Promise.reject(new Error(`Cannot set ${local ? 'local' : 'remote'}\n offer in state ${peerConnection.signalingState}`));\n }\n\n if (!pendingLocalOffer && peerConnection._signalingStateLatch.state === 'low') {\n peerConnection._signalingStateLatch.raise();\n }\n const previousSignalingState = peerConnection.signalingState;\n setPendingLocalOffer(description);\n\n // Only dispatch a signalingstatechange event if we transitioned.\n if (peerConnection.signalingState !== previousSignalingState) {\n return Promise.resolve().then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n return Promise.resolve();\n } else if (description.type === 'rollback') {\n if (peerConnection.signalingState !== intermediateState) {\n return Promise.reject(new Error(`Cannot rollback \n ${local ? 'local' : 'remote'} description in ${peerConnection.signalingState}`));\n }\n clearPendingLocalOffer();\n\n // NOTE(mmalavalli): We store the rolled back tracks to SSRCs Map here in case\n // setLocalDescription() is called immediately aftera rollback (without calling\n // createOffer() or createAnswer()), in which case this roll back is not due to\n // a glare scenario and this Map should be restored.\n peerConnection._rolledBackTracksToSSRCs = new Map(peerConnection._tracksToSSRCs);\n peerConnection._tracksToSSRCs = new Map(peerConnection._appliedTracksToSSRCs);\n\n return Promise.resolve().then(() => peerConnection.dispatchEvent(new Event('signalingstatechange')));\n }\n\n return peerConnection._peerConnection[setLocalDescription](description);\n}\n\nfunction setRemoteAnswer(peerConnection, answer) {\n const pendingLocalOffer = peerConnection._pendingLocalOffer;\n return peerConnection._peerConnection.setLocalDescription(pendingLocalOffer).then(() => {\n peerConnection._pendingLocalOffer = null;\n return peerConnection.setRemoteDescription(answer);\n }).then(() => peerConnection._signalingStateLatch.lower());\n}\n\n/**\n * Whether a SafariRTCPeerConnection has any RTCRtpReceivers(s) for the given\n * MediaStreamTrack kind.\n * @param {SafariRTCPeerConnection} peerConnection\n * @param {'audio' | 'video'} kind\n * @returns {boolean}\n */\nfunction hasReceiversForTracksOfKind(peerConnection, kind) {\n return !!peerConnection.getTransceivers().find(({ receiver = {} }) => {\n const { track = {} } = receiver;\n return track.kind === kind;\n });\n}\n\n/**\n * Shim an RTCDataChannel. This function mutates the RTCDataChannel.\n * @param {RTCDataChannel} dataChannel\n * @returns {RTCDataChannel}\n */\nfunction shimDataChannel(dataChannel) {\n return Object.defineProperties(dataChannel, {\n maxPacketLifeTime: {\n value: dataChannel.maxPacketLifeTime === 65535\n ? null\n : dataChannel.maxPacketLifeTime\n },\n maxRetransmits: {\n value: dataChannel.maxRetransmits === 65535\n ? null\n : dataChannel.maxRetransmits\n }\n });\n}\n\nmodule.exports = SafariRTCPeerConnection;\n", "'use strict';\n\nif (typeof RTCPeerConnection === 'function') {\n const { guessBrowser } = require('../util');\n switch (guessBrowser()) {\n case 'chrome':\n module.exports = require('./chrome');\n break;\n case 'firefox':\n module.exports = require('./firefox');\n break;\n case 'safari':\n module.exports = require('./safari');\n break;\n default:\n module.exports = RTCPeerConnection;\n break;\n }\n} else {\n module.exports = function RTCPeerConnection() {\n throw new Error('RTCPeerConnection is not supported');\n };\n}\n", "/* globals RTCSessionDescription */\n'use strict';\n\nif (typeof RTCSessionDescription === 'function') {\n const { guessBrowser } = require('../util');\n switch (guessBrowser()) {\n case 'chrome':\n module.exports = require('./chrome');\n break;\n case 'firefox':\n module.exports = require('./firefox');\n break;\n default:\n module.exports = RTCSessionDescription;\n break;\n }\n} else {\n module.exports = function RTCSessionDescription() {\n throw new Error('RTCSessionDescription is not supported');\n };\n}\n", "'use strict';\n\nconst WebRTC = {};\n\nObject.defineProperties(WebRTC, {\n getStats: {\n enumerable: true,\n value: require('./getstats')\n },\n getUserMedia: {\n enumerable: true,\n value: require('./getusermedia')\n },\n MediaStream: {\n enumerable: true,\n value: require('./mediastream')\n },\n MediaStreamTrack: {\n enumerable: true,\n value: require('./mediastreamtrack')\n },\n RTCIceCandidate: {\n enumerable: true,\n value: require('./rtcicecandidate')\n },\n RTCPeerConnection: {\n enumerable: true,\n value: require('./rtcpeerconnection')\n },\n RTCSessionDescription: {\n enumerable: true,\n value: require('./rtcsessiondescription')\n }\n});\n\nmodule.exports = WebRTC;\n", "/**\n * Copyright (c) 2011-2022 Isaac Z. Schlueter\n * Licensed under the ISC License.\n *\n * Copied from https://github.com/isaacs/inherits (2.0.4)\n*/\n\nmodule.exports = function inherits(ctor, superCtor) {\n if (ctor && superCtor) {\n ctor.super_ = superCtor;\n if (typeof Object.create === 'function') {\n // implementation from standard node.js 'util' module\n ctor.prototype = Object.create(superCtor.prototype, {\n constructor: {\n value: ctor,\n enumerable: false,\n writable: true,\n configurable: true\n }\n });\n } else {\n // old school shim for old browsers\n class TempCtor {\n constructor() { }\n }\n TempCtor.prototype = superCtor.prototype;\n ctor.prototype = new TempCtor();\n ctor.prototype.constructor = ctor;\n }\n }\n};\n", "/* globals chrome, navigator */\n'use strict';\n\n/**\n * Check whether the current browser is an Android device.\n * @returns {boolean}\n */\nfunction isAndroid() {\n return /Android/.test(navigator.userAgent);\n}\n\n/**\n * Detects whether or not a device is an Apple touch screen device.\n * @returns {boolean}\n */\nfunction hasTouchScreen() {\n return !!(navigator && navigator.maxTouchPoints && navigator.maxTouchPoints > 2);\n}\n\n/**\n * Detects whether or not a device is an iPad.\n * @returns {boolean}\n */\nfunction isIpad() {\n return hasTouchScreen() && window.screen.width >= 744 && (/Macintosh/i.test(navigator.userAgent)\n || /iPad/.test(navigator.userAgent)\n || /iPad/.test(navigator.platform));\n}\n\n/**\n * Detects whether or not a device is an iPhone.\n * @returns {boolean}\n */\nfunction isIphone() {\n return hasTouchScreen() && window.screen.width <= 476 && (/Macintosh/i.test(navigator.userAgent)\n || /iPhone/.test(navigator.userAgent)\n || /iPhone/.test(navigator.platform));\n}\n\n/**\n * Check whether the current device is an iOS device.\n * @returns {boolean}\n */\nfunction isIOS() {\n return isIpad() || isIphone();\n}\n\n/**\n * Check whether the current browser is a mobile browser\n * @returns {boolean}\n */\nfunction isMobile() {\n return /Mobi/.test(navigator.userAgent);\n}\n\n/**\n * Check whether the current browser is non-Chromium Edge.\n * @param {string} browser\n * @returns {boolean}\n */\nfunction isNonChromiumEdge(browser) {\n return browser === 'chrome' && /Edge/.test(navigator.userAgent) && (\n typeof chrome === 'undefined' || typeof chrome.runtime === 'undefined'\n );\n}\n\n/**\n * Get the name of the rebranded Chromium browser, if any. Re-branded Chrome's user\n * agent has the following format:\n * / () / () Chrome/ [Mobile] Safari/\n * @param browser\n * @returns {?string} Name of the rebranded Chrome browser, or null if the browser\n * is either not Chrome or vanilla Chrome.\n */\nfunction rebrandedChromeBrowser(browser) {\n // If the browser is not Chrome based, then it is not a rebranded Chrome browser.\n if (browser !== 'chrome') {\n return null;\n }\n\n // Latest desktop Brave browser has a \"brave\" property in navigator.\n if ('brave' in navigator) {\n return 'brave';\n }\n\n // Remove the \"(.+)\" entries from the user agent thereby retaining only the\n // [/] entries.\n const parenthesizedSubstrings = getParenthesizedSubstrings(navigator.userAgent);\n const nameAndVersions = parenthesizedSubstrings.reduce(\n (userAgent, substring) => userAgent.replace(substring, ''),\n navigator.userAgent\n );\n\n // Extract the potential browser s by ignoring the first two names, which\n // point to and .\n const matches = nameAndVersions.match(/[^\\s]+/g) || [];\n const [/* source */, /* engine */, ...browserNames] = matches.map(nameAndVersion => {\n return nameAndVersion.split('/')[0].toLowerCase();\n });\n\n // Extract the that is not expected to be present in the vanilla Chrome\n // browser, which indicates the rebranded name (ex: \"edg[e]\", \"electron\"). If null,\n // then this is a vanilla Chrome browser.\n return browserNames.find(name => {\n return !['chrome', 'mobile', 'safari'].includes(name);\n }) || null;\n}\n\n/**\n * Get the name of the mobile webkit based browser, if any.\n * @param browser\n * @returns {?string} Name of the mobile webkit based browser, or null if the browser\n * is either not webkit based or mobile safari.\n */\nfunction mobileWebKitBrowser(browser) {\n if (browser !== 'safari') {\n return null;\n }\n if ('brave' in navigator) {\n return 'brave';\n }\n\n return ['edge', 'edg'].find(name => {\n return navigator.userAgent.toLowerCase().includes(name);\n }) || null;\n}\n\n/**\n * Get the top level parenthesized substrings within a given string. Unmatched\n * parentheses are ignored.\n * Ex: \"abc) (def) gh(ij) (kl (mn)o) (pqr\" => [\"(def)\", \"(ij)\", \"(kl (mn)o)\"]\n * @param {string} string\n * @returns {string[]}\n */\nfunction getParenthesizedSubstrings(string) {\n const openParenthesisPositions = [];\n const substrings = [];\n for (let i = 0; i < string.length; i++) {\n if (string[i] === '(') {\n openParenthesisPositions.push(i);\n } else if (string[i] === ')' && openParenthesisPositions.length > 0) {\n const openParenthesisPosition = openParenthesisPositions.pop();\n if (openParenthesisPositions.length === 0) {\n substrings.push(string.substring(openParenthesisPosition, i + 1));\n }\n }\n }\n return substrings;\n}\n\nmodule.exports = {\n isAndroid,\n isIOS,\n isIpad,\n isIphone,\n isMobile,\n isNonChromiumEdge,\n mobileWebKitBrowser,\n rebrandedChromeBrowser\n};\n", "'use strict';\n\nconst { defer } = require('./');\n\n/**\n * This is a pair of Deferreds that are set whenever local media is muted and\n * resolved whenever local media is unmuted/ended and restarted if necessary.\n */\nclass LocalMediaRestartDeferreds {\n /**\n * Constructor.\n */\n constructor() {\n Object.defineProperties(this, {\n _audio: {\n value: defer(),\n writable: true\n },\n _video: {\n value: defer(),\n writable: true\n }\n });\n\n // Initially, resolve both the Deferreds.\n this._audio.resolve();\n this._video.resolve();\n }\n\n /**\n * Resolve the Deferred for audio or video.\n * @param {'audio'|'video'} kind\n */\n resolveDeferred(kind) {\n if (kind === 'audio') {\n this._audio.resolve();\n } else {\n this._video.resolve();\n }\n }\n\n /**\n * Start the Deferred for audio or video.\n * @param {'audio' | 'video'} kind\n */\n startDeferred(kind) {\n if (kind === 'audio') {\n this._audio = defer();\n } else {\n this._video = defer();\n }\n }\n\n /**\n * Wait until the Deferred for audio or video is resolved.\n * @param {'audio'|'video'} kind\n * @returns {Promise}\n */\n whenResolved(kind) {\n return kind === 'audio' ? this._audio.promise : this._video.promise;\n }\n}\n\nmodule.exports = new LocalMediaRestartDeferreds();\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst { hidePrivateAndCertainPublicPropertiesInClass } = require('./util');\n\nmodule.exports = hidePrivateAndCertainPublicPropertiesInClass(EventEmitter, ['domain']);\n", "'use strict';\n\nconst EventEmitter = require('../../eventemitter');\nconst { buildLogLevels, valueToJSON } = require('../../util');\nconst DEFAULT_LOG_LEVEL = require('../../util/constants').DEFAULT_LOG_LEVEL;\nconst Log = require('../../util/log');\n\nlet nInstances = 0;\n\n/**\n * A {@link Track} represents a stream of audio, video, or data.\n * @extends EventEmitter\n * @property {Track.Kind} kind - The {@link Track}'s kind\n * @property {string} name - The {@link Track}'s name\n */\nclass Track extends EventEmitter {\n /**\n * Construct a {@link Track}.\n * @param {Track.ID} id - The {@link Track}'s ID\n * @param {Track.Kind} kind - The {@link Track}'s kind\n * @param {{ log: Log, name: ?string }} options\n */\n constructor(id, kind, options) {\n options = Object.assign({\n name: id,\n log: null,\n logLevel: DEFAULT_LOG_LEVEL\n }, options);\n\n super();\n\n const name = String(options.name);\n\n const logLevels = buildLogLevels(options.logLevel);\n const log = options.log\n ? options.log.createLog('media', this)\n : new Log('media', this, logLevels, options.loggerName);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: ++nInstances\n },\n _log: {\n value: log\n },\n kind: {\n enumerable: true,\n value: kind\n },\n name: {\n enumerable: true,\n value: name\n }\n });\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n}\n\n/**\n * The {@link Track} ID is a string identifier for the {@link Track}.\n * @typedef {string} Track.ID\n */\n\n/**\n * The {@link Track} kind is either \"audio\", \"video\", or \"data\".\n * @typedef {string} Track.Kind\n */\n\n/**\n * The {@link Track}'s priority can be \"low\", \"standard\", or \"high\".\n * @typedef {string} Track.Priority\n */\n\n/**\n * The {@link Track} SID is a unique string identifier for the {@link Track}\n * that is published to a {@link Room}.\n * @typedef {string} Track.SID\n */\n\n/**\n * A {@link DataTrack} is a {@link LocalDataTrack} or {@link RemoteDataTrack}.\n * @typedef {LocalDataTrack|RemoteDataTrack} DataTrack\n */\n\n/**\n * A {@link LocalTrack} is a {@link LocalAudioTrack}, {@link LocalVideoTrack},\n * or {@link LocalDataTrack}.\n * @typedef {LocalAudioTrack|LocalVideoTrack|LocalDataTrack} LocalTrack\n */\n\n/**\n * {@link LocalTrack} options\n * @typedef {object} LocalTrackOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n * @property {string} [name] - The {@link LocalTrack}'s name; by default,\n * it is set to the {@link LocalTrack}'s ID.\n */\n\n/**\n * A {@link RemoteTrack} is a {@link RemoteAudioTrack},\n * {@link RemoteVideoTrack}, or {@link RemoteDataTrack}.\n * @typedef {RemoteAudioTrack|RemoteVideoTrack|RemoteDataTrack} RemoteTrack\n */\n\nmodule.exports = Track;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst { MediaStream } = require('../../webrtc');\n\nconst { waitForEvent, waitForSometime } = require('../../util');\nconst localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds');\nconst Track = require('./');\n\n/**\n * A {@link MediaTrack} represents audio or video that can be sent to or\n * received from a {@link Room}.\n * @extends Track\n * @property {Track.ID} id - This {@link Track}'s ID\n * @property {boolean} isStarted - Whether or not the {@link MediaTrack} has\n * started\n * @property {boolean} isEnabled - Whether or not the {@link MediaTrack} is\n * enabled (i.e., whether it is paused or muted)\n * @property {Track.Kind} kind - The kind of the underlying\n * MediaStreamTrack, \"audio\" or \"video\"\n * @property {MediaStreamTrack} mediaStreamTrack - The underlying\n * MediaStreamTrack\n * @emits MediaTrack#disabled\n * @emits MediaTrack#enabled\n * @emits MediaTrack#started\n */\nclass MediaTrack extends Track {\n /**\n * Construct a {@link MediaTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n options = Object.assign({\n playPausedElementsIfNotBackgrounded: isIOS()\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string'\n }, options);\n\n super(mediaTrackTransceiver.id, mediaTrackTransceiver.kind, options);\n let isStarted = false;\n\n options = Object.assign({\n MediaStream\n }, options);\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _attachments: {\n value: new Set()\n },\n _dummyEl: {\n value: null,\n writable: true\n },\n _elShims: {\n value: new WeakMap()\n },\n _isStarted: {\n get() {\n return isStarted;\n },\n set(_isStarted) {\n isStarted = _isStarted;\n }\n },\n _playPausedElementsIfNotBackgrounded: {\n value: options.playPausedElementsIfNotBackgrounded\n },\n _shouldShimAttachedElements: {\n value: options.workaroundWebKitBug212780\n || options.playPausedElementsIfNotBackgrounded\n },\n _unprocessedTrack: {\n value: null,\n writable: true\n },\n _MediaStream: {\n value: options.MediaStream\n },\n isStarted: {\n enumerable: true,\n get() {\n return isStarted;\n }\n },\n mediaStreamTrack: {\n enumerable: true,\n get() {\n return this._unprocessedTrack || mediaTrackTransceiver.track;\n }\n },\n processedTrack: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n\n this._initialize();\n }\n\n /**\n * @private\n */\n _start() {\n this._log.debug('Started');\n this._isStarted = true;\n if (this._dummyEl) {\n this._dummyEl.oncanplay = null;\n }\n // eslint-disable-next-line no-use-before-define\n this.emit('started', this);\n }\n\n /**\n * @private\n */\n _initialize() {\n const self = this;\n\n this._log.debug('Initializing');\n this._dummyEl = this._createElement();\n\n this.mediaStreamTrack.addEventListener('ended', function onended() {\n self._end();\n self.mediaStreamTrack.removeEventListener('ended', onended);\n });\n\n if (this._dummyEl) {\n this._dummyEl.muted = true;\n this._dummyEl.oncanplay = this._start.bind(this, this._dummyEl);\n\n // NOTE(csantos): We always want to attach the original mediaStreamTrack for dummyEl\n this._attach(this._dummyEl, this.mediaStreamTrack);\n\n this._attachments.delete(this._dummyEl);\n }\n }\n\n /**\n * @private\n */\n _end() {\n this._log.debug('Ended');\n if (this._dummyEl) {\n this._dummyEl.remove();\n this._dummyEl.srcObject = null;\n this._dummyEl.oncanplay = null;\n this._dummyEl = null;\n }\n }\n\n attach(el) {\n if (typeof el === 'string') {\n el = this._selectElement(el);\n } else if (!el) {\n el = this._createElement();\n }\n this._log.debug('Attempting to attach to element:', el);\n el = this._attach(el);\n\n if (this._shouldShimAttachedElements && !this._elShims.has(el)) {\n const onUnintentionallyPaused = this._playPausedElementsIfNotBackgrounded\n ? () => playIfPausedAndNotBackgrounded(el, this._log)\n : null;\n this._elShims.set(el, shimMediaElement(el, onUnintentionallyPaused));\n }\n return el;\n }\n\n /**\n * Attach the provided MediaStreamTrack to the media element.\n * @param el - The media element to attach to\n * @param mediaStreamTrack - The MediaStreamTrack to attach. If this is\n * not provided, it uses the processedTrack if it exists\n * or it defaults to the current mediaStreamTrack\n * @private\n */\n _attach(el, mediaStreamTrack = this.processedTrack || this.mediaStreamTrack) {\n let mediaStream = el.srcObject;\n if (!(mediaStream instanceof this._MediaStream)) {\n mediaStream = new this._MediaStream();\n }\n\n const getTracks = mediaStreamTrack.kind === 'audio'\n ? 'getAudioTracks'\n : 'getVideoTracks';\n\n mediaStream[getTracks]().forEach(track => {\n mediaStream.removeTrack(track);\n });\n mediaStream.addTrack(mediaStreamTrack);\n\n // NOTE(mpatwardhan): resetting `srcObject` here, causes flicker (JSDK-2641), but it lets us\n // to sidestep the a chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1052353\n //\n el.srcObject = mediaStream;\n el.autoplay = true;\n el.playsInline = true;\n\n if (!this._attachments.has(el)) {\n this._attachments.add(el);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _selectElement(selector) {\n const el = document.querySelector(selector);\n\n if (!el) {\n throw new Error(`Selector matched no element: ${selector}`);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _updateElementsMediaStreamTrack() {\n this._log.debug('Reattaching all elements to update mediaStreamTrack');\n this._getAllAttachedElements().forEach(el => this._attach(el));\n }\n\n /**\n * @private\n */\n _createElement() {\n return typeof document !== 'undefined'\n ? document.createElement(this.kind)\n : null;\n }\n\n detach(el) {\n let els;\n\n if (typeof el === 'string') {\n els = [this._selectElement(el)];\n } else if (!el) {\n els = this._getAllAttachedElements();\n } else {\n els = [el];\n }\n\n this._log.debug('Attempting to detach from elements:', els);\n this._detachElements(els);\n return el ? els[0] : els;\n }\n\n /**\n * @private\n */\n _detachElements(elements) {\n return elements.map(this._detachElement.bind(this));\n }\n\n /**\n * @private\n */\n _detachElement(el) {\n if (!this._attachments.has(el)) {\n return el;\n }\n const mediaStream = el.srcObject;\n if (mediaStream instanceof this._MediaStream) {\n mediaStream.removeTrack(this.processedTrack || this.mediaStreamTrack);\n }\n this._attachments.delete(el);\n\n if (this._shouldShimAttachedElements && this._elShims.has(el)) {\n const shim = this._elShims.get(el);\n shim.unShim();\n this._elShims.delete(el);\n }\n\n return el;\n }\n\n /**\n * @private\n */\n _getAllAttachedElements() {\n const els = [];\n\n this._attachments.forEach(el => {\n els.push(el);\n });\n\n return els;\n }\n}\n\n/**\n * Play an HTMLMediaElement if it is paused and not backgrounded.\n * @private\n * @param {HTMLMediaElement} el\n * @param {Log} log\n * @returns {void}\n */\nfunction playIfPausedAndNotBackgrounded(el, log) {\n const tag = el.tagName.toLowerCase();\n log.warn('Unintentionally paused:', el);\n\n // NOTE(mmalavalli): When the element is unintentionally paused, we wait one\n // second for the \"onvisibilitychange\" event on the HTMLDocument to see if the\n // app will be backgrounded. If not, then the element can be safely played.\n Promise.race([\n waitForEvent(document, 'visibilitychange'),\n waitForSometime(1000)\n ]).then(() => {\n if (document.visibilityState === 'visible') {\n // NOTE(mmalavalli): We play the inadvertently paused elements only after\n // the LocalAudioTrack is unmuted to work around WebKit Bug 213853.\n //\n // Bug: https://bugs.webkit.org/show_bug.cgi?id=213853\n //\n localMediaRestartDeferreds.whenResolved('audio').then(() => {\n log.info(`Playing unintentionally paused <${tag}> element`);\n log.debug('Element:', el);\n return el.play();\n }).then(() => {\n log.info(`Successfully played unintentionally paused <${tag}> element`);\n log.debug('Element:', el);\n }).catch(error => {\n log.warn(`Error while playing unintentionally paused <${tag}> element:`, { error, el });\n });\n }\n });\n}\n\n/**\n * Shim the pause() and play() methods of the given HTMLMediaElement so that\n * we can detect if it was paused unintentionally.\n * @param {HTMLMediaElement} el\n * @param {?function} [onUnintentionallyPaused=null]\n * @returns {{pausedIntentionally: function, unShim: function}}\n */\nfunction shimMediaElement(el, onUnintentionallyPaused = null) {\n const origPause = el.pause;\n const origPlay = el.play;\n\n let pausedIntentionally = false;\n\n el.pause = () => {\n pausedIntentionally = true;\n return origPause.call(el);\n };\n\n el.play = () => {\n pausedIntentionally = false;\n return origPlay.call(el);\n };\n\n const onPause = onUnintentionallyPaused ? () => {\n if (!pausedIntentionally) {\n onUnintentionallyPaused();\n }\n } : null;\n\n if (onPause) {\n el.addEventListener('pause', onPause);\n }\n\n return {\n pausedIntentionally() {\n return pausedIntentionally;\n },\n unShim() {\n el.pause = origPause;\n el.play = origPlay;\n if (onPause) {\n el.removeEventListener('pause', onPause);\n }\n }\n };\n}\n\nmodule.exports = MediaTrack;\n", "'use strict';\n\nconst MediaTrack = require('./mediatrack');\n\n/**\n * An {@link AudioTrack} is a {@link Track} representing audio.\n * @extends Track\n * @property {boolean} isStarted - Whether or not the {@link AudioTrack} has\n * started; if the {@link AudioTrack} started, there is enough audio data to\n * begin playback\n * @property {boolean} isEnabled - Whether or not the {@link AudioTrack} is\n * enabled; if the {@link AudioTrack} is not enabled, it is \"muted\"\n * @property {Track.Kind} kind - \"audio\"\n * @property {MediaStreamTrack} mediaStreamTrack - An audio MediaStreamTrack\n * @property {?MediaStreamTrack} processedTrack - The source of processed audio samples.\n * It is always null as audio processing is not currently supported.\n * @emits AudioTrack#disabled\n * @emits AudioTrack#enabled\n * @emits AudioTrack#started\n */\nclass AudioTrack extends MediaTrack {\n /**\n * Construct an {@link AudioTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n super(mediaTrackTransceiver, options);\n }\n\n /**\n * Create an HTMLAudioElement and attach the {@link AudioTrack} to it.\n *\n * The HTMLAudioElement's srcObject will be set to a new\n * MediaStream containing the {@link AudioTrack}'s MediaStreamTrack.\n *\n * @returns {HTMLAudioElement} audioElement\n * @example\n * const Video = require('twilio-video');\n *\n * Video.createLocalAudioTrack().then(function(audioTrack) {\n * const audioElement = audioTrack.attach();\n * document.body.appendChild(audioElement);\n * });\n *//**\n * Attach the {@link AudioTrack} to an existing HTMLMediaElement. The\n * HTMLMediaElement could be an HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link AudioTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link MediaTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {HTMLMediaElement} mediaElement - The HTMLMediaElement to attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalAudioTrack().then(function(audioTrack) {\n * audioTrack.attach(videoElement);\n * });\n *//**\n * Attach the {@link AudioTrack} to an HTMLMediaElement selected by\n * document.querySelector. The HTMLMediaElement could be an\n * HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link AudioTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link AudioTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {string} selector - A query selector for the HTMLMediaElement to\n * attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * videoElement.id = 'my-video-element';\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalAudioTrack().then(function(track) {\n * track.attach('#my-video-element');\n * });\n */\n attach() {\n return super.attach.apply(this, arguments);\n }\n\n /**\n * Detach the {@link AudioTrack} from all previously attached HTMLMediaElements.\n * @returns {Array} mediaElements\n * @example\n * const mediaElements = audioTrack.detach();\n * mediaElements.forEach(mediaElement => mediaElement.remove());\n *//**\n * Detach the {@link AudioTrack} from a previously attached HTMLMediaElement.\n * @param {HTMLMediaElement} mediaElement - One of the HTMLMediaElements to\n * which the {@link AudioTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const videoElement = document.getElementById('my-video-element');\n * audioTrack.detach(videoElement).remove();\n *//**\n * Detach the {@link AudioTrack} from a previously attached HTMLMediaElement\n * specified by document.querySelector.\n * @param {string} selector - The query selector of HTMLMediaElement to which\n * the {@link AudioTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * audioTrack.detach('#my-video-element').remove();\n */\n detach() {\n return super.detach.apply(this, arguments);\n }\n}\n\n/**\n * The {@link AudioTrack} was disabled, i.e. \"muted\".\n * @param {AudioTrack} track - The {@link AudioTrack} that was disabled\n * @event AudioTrack#disabled\n */\n\n/**\n * The {@link AudioTrack} was enabled, i.e. \"unmuted\".\n * @param {AudioTrack} track - The {@link AudioTrack} that was enabled\n * @event AudioTrack#enabled\n */\n\n/**\n * The {@link AudioTrack} started. This means there is enough audio data to\n * begin playback.\n * @param {AudioTrack} track - The {@link AudioTrack} that started\n * @event AudioTrack#started\n */\n\nmodule.exports = AudioTrack;\n", "'use strict';\n\n/**\n * Return a Promise that resolves after `timeout` milliseconds.\n * @param {?number} [timeout=0]\n * @returns {Promise}\n */\nfunction delay(timeout) {\n timeout = typeof timeout === 'number' ? timeout : 0;\n return new Promise(resolve => setTimeout(resolve, timeout));\n}\n\n/**\n * Attempt to detect silence. The Promise returned by this function returns\n * false as soon as audio is detected or true after `timeout` milliseconds.\n * @param {AudioContext} audioContext\n * @param {MediaStream} stream\n * @param {?number} [timeout=250]\n * @returns {Promise}\n */\nfunction detectSilence(audioContext, stream, timeout) {\n timeout = typeof timeout === 'number' ? timeout : 250;\n\n const source = audioContext.createMediaStreamSource(stream);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 2048;\n source.connect(analyser);\n\n const samples = new Uint8Array(analyser.fftSize);\n\n let timeoutDidFire = false;\n setTimeout(() => { timeoutDidFire = true; }, timeout);\n\n /**\n * We can't use async/await yet, so I need to factor this out.\n * @returns {Promise}\n */\n function doDetectSilence() {\n if (timeoutDidFire) {\n return Promise.resolve(true);\n }\n analyser.getByteTimeDomainData(samples);\n // NOTE(mpatwardhan): An audio MediaStreamTrack can be silent either due to all samples\n // being equal to 128 or all samples being equal to 0.\n return samples.some(sample => sample !== 128 && sample !== 0)\n ? Promise.resolve(false)\n : delay().then(doDetectSilence);\n }\n\n return doDetectSilence().then(isSilent => {\n source.disconnect();\n return isSilent;\n }, error => {\n source.disconnect();\n throw error;\n });\n}\n\nmodule.exports = detectSilence;\n", "/* globals webkitAudioContext, AudioContext */\n'use strict';\n\nconst NativeAudioContext = typeof AudioContext !== 'undefined'\n ? AudioContext\n : typeof webkitAudioContext !== 'undefined'\n ? webkitAudioContext\n : null;\n\n/**\n * @interface AudioContextFactoryOptions\n * @property {AudioContext} [AudioContext] - The AudioContext constructor\n */\n\n/**\n * {@link AudioContextFactory} ensures we construct at most one AudioContext\n * at a time, and that it is eventually closed when we no longer need it.\n * @property {AudioContextFactory} AudioContextFactory - The\n * {@link AudioContextFactory} constructor\n */\nclass AudioContextFactory {\n /**\n * @param {AudioContextFactoryOptions} [options]\n */\n constructor(options) {\n options = Object.assign({\n AudioContext: NativeAudioContext\n }, options);\n Object.defineProperties(this, {\n _AudioContext: {\n value: options.AudioContext\n },\n _audioContext: {\n value: null,\n writable: true\n },\n _holders: {\n value: new Set()\n },\n AudioContextFactory: {\n enumerable: true,\n value: AudioContextFactory\n }\n });\n }\n\n /**\n * Each call to {@link AudioContextFactory#getOrCreate} should be paired with a\n * call to {@link AudioContextFactory#release}. Calling this increments an\n * internal reference count.\n * @param {*} holder - The object to hold a reference to the AudioContext\n * @returns {?AudioContext}\n */\n getOrCreate(holder) {\n if (!this._holders.has(holder)) {\n this._holders.add(holder);\n if (this._AudioContext && !this._audioContext) {\n try {\n this._audioContext = new this._AudioContext();\n } catch (error) {\n // Do nothing;\n }\n }\n }\n return this._audioContext;\n }\n\n /**\n * Decrement the internal reference count. If it reaches zero, close and destroy\n * the AudioContext.\n * @param {*} holder - The object that held a reference to the AudioContext\n * @returns {void}\n */\n release(holder) {\n if (this._holders.has(holder)) {\n this._holders.delete(holder);\n if (!this._holders.size && this._audioContext) {\n this._audioContext.close();\n this._audioContext = null;\n }\n }\n }\n}\n\nmodule.exports = new AudioContextFactory();\n", "'use strict';\n\nconst detectSilence = require('../webaudio/detectsilence');\n\nconst N_ATTEMPTS = 3;\nconst ATTEMPT_DURATION_MS = 250;\n\n/**\n * Detect whether the audio stream rendered by the given HTMLVideoElement is silent.\n * @param {HTMLAudioElement} el\n * @returns {Promise} true if silent, false if not.\n */\nfunction detectSilentAudio(el) {\n // NOTE(mmalavalli): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const AudioContextFactory = require('../webaudio/audiocontext');\n const holder = {};\n const audioContext = AudioContextFactory.getOrCreate(holder);\n\n let attemptsLeft = N_ATTEMPTS;\n\n function doCheckSilence() {\n attemptsLeft--;\n return detectSilence(audioContext, el.srcObject, ATTEMPT_DURATION_MS).then(isSilent => {\n if (!isSilent) {\n return false;\n }\n if (attemptsLeft > 0) {\n return doCheckSilence();\n }\n return true;\n }).catch(() => {\n // NOTE(mmalavalli): If an error is thrown while detect silence, the audio\n // stream is assumed to be silent.\n return true;\n });\n }\n\n // Resolve the returned Promise with true if 3 consecutive attempts\n // to detect silent audio are successful.\n return doCheckSilence().finally(() => {\n AudioContextFactory.release(holder);\n });\n}\n\nmodule.exports = detectSilentAudio;\n", "'use strict';\n\n// Cached copy of the used to check silent video frames.\nlet canvas = null;\n\nconst N_SAMPLES = 3;\nconst SAMPLE_HEIGHT = 50;\nconst SAMPLE_INTERVAL_MS = 250;\nconst SAMPLE_WIDTH = 50;\n\n/**\n * Check whether the current video frame is silent by selecting a 50x50\n * sample and calculating the max value of the pixel data. If it is 0, then\n * the frame is considered to be silent.\n * @private\n * @param {HTMLVideoElement} el\n * @returns {boolean} true if silent, false if not\n */\nfunction checkSilence(el) {\n try {\n const context = canvas.getContext('2d');\n context.drawImage(el, 0, 0, SAMPLE_WIDTH, SAMPLE_HEIGHT);\n const frame = context.getImageData(0, 0, SAMPLE_WIDTH, SAMPLE_HEIGHT);\n const frameDataWithoutAlpha = frame.data.filter((item, i) => (i + 1) % 4);\n const max = Math.max.apply(Math, frameDataWithoutAlpha);\n return max === 0;\n } catch (ex) {\n // eslint-disable-next-line no-console\n console.log('Error checking silence: ', ex);\n return false;\n }\n\n}\n\n/**\n * Detect whether the video stream rendered by the given HTMLVideoElement is silent.\n * @param {HTMLVideoElement} el\n * @returns {Promise} true if silent, false if not.\n */\nfunction detectSilentVideo(el) {\n // Create the canvas when detectSilentVideo() is called for the\n // first time.\n canvas = canvas || document.createElement('canvas');\n\n // Resolve the returned Promise with true if 3 consecutive sample\n // frames from the video being played by the HTMLVideoElement are\n // silent.\n return new Promise(resolve => {\n let samplesLeft = N_SAMPLES;\n setTimeout(function doCheckSilence() {\n samplesLeft--;\n if (!checkSilence(el)) {\n return resolve(false);\n }\n if (samplesLeft > 0) {\n return setTimeout(doCheckSilence, SAMPLE_INTERVAL_MS);\n }\n return resolve(true);\n }, SAMPLE_INTERVAL_MS);\n });\n}\n\nmodule.exports = detectSilentVideo;\n", "'use strict';\n\n/**\n * The {@link DocumentVisibilityMonitor} monitors the visibility state of the DOM\n * and executes the attached listeners in phase order when the DOM is visible.\n */\nclass DocumentVisibilityMonitor {\n /**\n * Constructor.\n * @param {number} [nPhases=1] - the number of phases\n */\n constructor(nPhases = 1) {\n Object.defineProperties(this, {\n _listeners: {\n value: []\n },\n _onVisibilityChange: {\n value: () => {\n this._emitVisible(document.visibilityState === 'visible');\n }\n }\n });\n\n for (let i = 0; i < nPhases; i++) {\n this._listeners.push([]);\n }\n }\n\n\n /**\n * clears the state.\n */\n clear() {\n const nPhases = this._listeners.length;\n for (let i = 0; i < nPhases; i++) {\n this._listeners[i] = [];\n }\n }\n\n _listenerCount() {\n return this._listeners.reduce((count, phaseListeners) => count + phaseListeners.length, 0);\n }\n\n /**\n * Call all the listeners. Makes sure that all listeners for a given phase\n * are executed before calling the listeners of the next phase.\n * @private\n */\n _emitVisible(isVisible) {\n let promise = Promise.resolve();\n for (let phase = 1; phase <= this._listeners.length; phase++) {\n promise = promise.then(() => this._emitVisiblePhase(phase, isVisible));\n }\n return promise;\n }\n\n /**\n * Call all the listeners for a given phase.\n * @private\n */\n _emitVisiblePhase(phase, isVisible) {\n const phaseListeners = this._listeners[phase - 1];\n return Promise.all(phaseListeners.map(listener => {\n const ret = listener(isVisible);\n return ret instanceof Promise ? ret : Promise.resolve(ret);\n }));\n }\n\n /**\n * Start listening to the DOM visibility state change.\n * @private\n */\n _start() {\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /**\n * Stop listening to the DOM visibility state change.\n * @private\n */\n _stop() {\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n }\n\n /**\n * Listen for the DOM visibility changes at the given phase.\n * @param {number} phase\n * @param {function} listener\n * @returns {this}\n */\n onVisibilityChange(phase, listener) {\n if (typeof phase !== 'number' || phase <= 0 || phase > this._listeners.length) {\n throw new Error('invalid phase: ', phase);\n }\n const phaseListeners = this._listeners[phase - 1];\n phaseListeners.push(listener);\n if (this._listenerCount() === 1) {\n this._start();\n }\n return this;\n }\n\n /**\n * Stop listening for the DOM visibility change at the given phase.\n * @param {number} phase\n * @param {function} listener\n * @returns {this}\n */\n offVisibilityChange(phase, listener) {\n if (typeof phase !== 'number' || phase <= 0 || phase > this._listeners.length) {\n throw new Error('invalid phase: ', phase);\n }\n\n const phaseListeners = this._listeners[phase - 1];\n const index = phaseListeners.indexOf(listener);\n if (index !== -1) {\n phaseListeners.splice(index, 1);\n if (this._listenerCount() === 0) {\n this._stop();\n }\n }\n return this;\n }\n}\n\nmodule.exports = new DocumentVisibilityMonitor(2);\n", "'use strict';\n\nconst detectSilence = require('./detectsilence');\n\n/**\n * This function attempts to workaround WebKit Bug 180748. It does so by\n *\n * 1. Calling `getUserMedia`, and\n * 2. Checking to see if the resulting MediaStream is silent.\n * 3. If so, repeat Step 1; otherwise, return the MediaStream.\n *\n * The function only repeats up to `n` times, and it only waits `timeout`\n * milliseconds when detecting silence. Assuming `getUserMedia` is\n * instantaneous, in the best case, this function returns a Promise that\n * resolves immediately; in the worst case, this function returns a Promise that\n * resolves in `n` * `timeout` milliseconds.\n *\n * @param {Log} log\n * @param {function(MediaStreamConstraints): Promise} getUserMedia\n * @param {MediaStreamConstraints} constraints\n * @param {number} [n=3]\n * @param {number} [timeout=250]\n * @returns Promise\n */\nfunction workaround(log, getUserMedia, constraints, n, timeout) {\n n = typeof n === 'number' ? n : 3;\n let retry = 0;\n\n // NOTE(mroberts): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const AudioContextFactory = require('./audiocontext');\n const holder = {};\n const audioContext = AudioContextFactory.getOrCreate(holder);\n\n /**\n * We can't use async/await yet, so I need to factor this out.\n * @returns {Promise}\n */\n function doWorkaround() {\n return getUserMedia(constraints).then(stream => {\n const isSilentPromise = constraints.audio\n ? detectSilence(audioContext, stream, timeout).catch(err => {\n log.warn('Encountered an error while detecting silence', err);\n return true;\n })\n : Promise.resolve(false);\n return isSilentPromise.then(isSilent => {\n if (!isSilent) {\n log.info('Got a non-silent audio MediaStreamTrack; returning it.');\n return stream;\n } else if (n <= 0) {\n log.warn('Got a silent audio MediaStreamTrack. Normally we would try \\\nto get a new one, but we\\'ve run out of retries; returning it anyway.');\n return stream;\n }\n log.warn(`Got a silent audio MediaStreamTrack. Stopping all \\\nMediaStreamTracks and calling getUserMedia again. This is retry \\\n#${++retry}.`);\n stream.getTracks().forEach(track => track.stop());\n n--;\n return doWorkaround();\n });\n });\n }\n\n return doWorkaround().then(stream => {\n AudioContextFactory.release(holder);\n return stream;\n }, error => {\n AudioContextFactory.release(holder);\n throw error;\n });\n}\n\nmodule.exports = workaround;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * A {@link QueueingEventEmitter} can queue events until a listener has been\n * added.\n * @extends EventEmitter\n */\nclass QueueingEventEmitter extends EventEmitter {\n /**\n * Construct a {@link QueueingEventEmitter}\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _queuedEvents: {\n value: new Map()\n }\n });\n }\n\n /**\n * Emit any queued events.\n * @returns {boolean} true if every event had listeners, false otherwise\n *//**\n * Emit any queued events matching the event name.\n * @param {string} event\n * @returns {boolean} true if every event had listeners, false otherwise\n */\n dequeue(event) {\n let result = true;\n if (!event) {\n this._queuedEvents.forEach(function(_, queuedEvent) {\n result = this.dequeue(queuedEvent) && result;\n }, this);\n return result;\n }\n const queue = this._queuedEvents.get(event) || [];\n this._queuedEvents.delete(event);\n return queue.reduce((result, args) => this.emit(...[event].concat(args)) && result, result);\n }\n\n /**\n * If the event has listeners, emit the event; otherwise, queue the event.\n * @param {string} event\n * @param {...*} args\n * @returns {boolean} true if the event had listeners, false if the event was queued\n */\n queue() {\n const args = [].slice.call(arguments);\n if (this.emit(...args)) {\n return true;\n }\n const event = args[0];\n if (!this._queuedEvents.has(event)) {\n this._queuedEvents.set(event, []);\n }\n this._queuedEvents.get(event).push(args.slice(1));\n return false;\n }\n}\n\nmodule.exports = QueueingEventEmitter;\n", "'use strict';\n\nconst QueueingEventEmitter = require('./queueingeventemitter');\n\n/**\n * A {@link TrackTransceiver} represents either one or more local RTCRtpSenders\n * or RTCDataChannels, or a single RTCRtpReceiver or remote RTCDataChannel.\n * @extends QueueingEventEmitter\n * @property {Track.ID} id\n * @property {Track.kind} kind\n */\nclass TrackTransceiver extends QueueingEventEmitter {\n /**\n * Construct a {@link TrackTransceiver}.\n * @param {Track.ID} id\n * @param {Track.kind} kind\n */\n constructor(id, kind) {\n super();\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id\n },\n kind: {\n enumerable: true,\n value: kind\n }\n });\n }\n\n /**\n * Stop the {@link TrackTransceiver}.\n * #emits TrackTransceiver#stopped\n * @returns {void}\n */\n stop() {\n this.emit('stopped');\n }\n}\n\n/**\n * The {@link TrackTransceiver} was stopped.\n * @event TrackTransceiver#stopped\n */\n\nmodule.exports = TrackTransceiver;\n", "'use strict';\n\nconst TrackTransceiver = require('../../transceiver');\n\n/**\n * A {@link MediaTrackTransceiver} represents either one or more local\n * RTCRtpSenders, or a single RTCRtpReceiver.\n * @extends TrackTransceiver\n * @property {MediaStreamTrack} track\n */\nclass MediaTrackTransceiver extends TrackTransceiver {\n /**\n * Construct a {@link MediaTrackTransceiver}.\n * @param {Track.ID} id - The MediaStreamTrack ID signaled through RSP/SDP\n * @param {MediaStreamTrack} mediaStreamTrack\n */\n constructor(id, mediaStreamTrack) {\n super(id, mediaStreamTrack.kind);\n Object.defineProperties(this, {\n _track: {\n value: mediaStreamTrack,\n writable: true\n },\n enabled: {\n enumerable: true,\n get() {\n return this._track.enabled;\n }\n },\n readyState: {\n enumerable: true,\n get() {\n return this._track.readyState;\n }\n },\n track: {\n enumerable: true,\n get() {\n return this._track;\n }\n }\n });\n }\n\n stop() {\n this.track.stop();\n super.stop();\n }\n}\n\nmodule.exports = MediaTrackTransceiver;\n", "'use strict';\n\nconst MediaTrackTransceiver = require('./transceiver');\n\n/**\n * A {@link MediaTrackSender} represents one or more local RTCRtpSenders.\n * @extends MediaTrackTransceiver\n * @emits MediaTrackSender#replaced\n */\nclass MediaTrackSender extends MediaTrackTransceiver {\n /**\n * Construct a {@link MediaTrackSender}.\n * @param {MediaStreamTrack} mediaStreamTrack\n */\n constructor(mediaStreamTrack) {\n super(mediaStreamTrack.id, mediaStreamTrack);\n Object.defineProperties(this, {\n _clones: {\n value: new Set()\n },\n _eventsToReemitters: {\n value: new Map([\n ['mute', () => this.queue('muted')],\n ['unmute', () => this.queue('unmuted')]\n ])\n },\n _senders: {\n value: new Set()\n },\n _senderToPublisherHintCallbacks: {\n value: new Map()\n },\n isPublishing: {\n enumerable: true,\n get() {\n return !!this._clones.size;\n }\n },\n muted: {\n enumerable: true,\n get() {\n return this._track.muted;\n }\n }\n });\n\n this._reemitMediaStreamTrackEvents();\n }\n\n /**\n * @private\n */\n _reemitMediaStreamTrackEvents(mediaStreamTrack = this._track) {\n const { _eventsToReemitters: eventsToReemitters, _track: track } = this;\n eventsToReemitters.forEach((reemitter, event) => mediaStreamTrack.addEventListener(event, reemitter));\n if (track !== mediaStreamTrack) {\n eventsToReemitters.forEach((reemitter, event) => track.removeEventListener(event, reemitter));\n if (track.muted !== mediaStreamTrack.muted) {\n const reemitter = eventsToReemitters.get(mediaStreamTrack.muted ? 'mute' : 'unmute');\n reemitter();\n }\n }\n }\n\n /**\n * Return a new {@link MediaTrackSender} containing a clone of the underlying\n * MediaStreamTrack. No RTCRtpSenders are copied.\n * @returns {MediaTrackSender}\n */\n clone() {\n const clone = new MediaTrackSender(this.track.clone());\n this._clones.add(clone);\n return clone;\n }\n\n /**\n * Remove a cloned {@link MediaTrackSender}.\n * @returns {void}\n */\n removeClone(clone) {\n this._clones.delete(clone);\n }\n\n /**\n * Set the given MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack\n * @returns {Promise}\n */\n setMediaStreamTrack(mediaStreamTrack) {\n const clones = Array.from(this._clones);\n const senders = Array.from(this._senders);\n return Promise.all(clones.map(clone => {\n return clone.setMediaStreamTrack(mediaStreamTrack.clone());\n }).concat(senders.map(sender => {\n return this._replaceTrack(sender, mediaStreamTrack);\n }))).finally(() => {\n this._reemitMediaStreamTrackEvents(mediaStreamTrack);\n this._track = mediaStreamTrack;\n });\n }\n\n /**\n * Add an RTCRtpSender.\n * @param {RTCRtpSender} sender\n * @param {?()=>Promise} publisherHintCallback\n * @returns {this}\n */\n addSender(sender, publisherHintCallback) {\n this._senders.add(sender);\n if (publisherHintCallback) {\n this._senderToPublisherHintCallbacks.set(sender, publisherHintCallback);\n }\n return this;\n }\n\n /**\n * Remove an RTCRtpSender.\n * @param {RTCRtpSender} sender\n * @returns {this}\n */\n removeSender(sender) {\n this._senders.delete(sender);\n this._senderToPublisherHintCallbacks.delete(sender);\n return this;\n }\n\n /**\n * Applies given encodings, or resets encodings if none specified.\n * @param {Array<{enabled: boolean, layer_index: number}>|null} encodings\n * @returns {Promise}\n */\n setPublisherHint(encodings) {\n // Note(mpatwardhan): since publisher hint applies only to group rooms we only look at 1st call callback.\n const [publisherHintCallback] = Array.from(this._senderToPublisherHintCallbacks.values());\n return publisherHintCallback ? publisherHintCallback(encodings) : Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n _replaceTrack(sender, mediaStreamTrack) {\n return sender.replaceTrack(mediaStreamTrack).then(replaceTrackResult => {\n // clear any publisherHints and apply default encodings.\n this.setPublisherHint(null).catch(() => {});\n this.emit('replaced');\n return replaceTrackResult;\n });\n }\n}\n\n/**\n * The {@link MediaTrackSender}'s underlying MediaStreamTrack was muted.\n * @event MediaTrackSender#muted\n */\n\n/**\n * The {@link MediaTrackSender} replaced the underlying MediaStreamTrack.\n * @event MediaTrackSender#replaced\n */\n\n/**\n * The {@link MediaTrackSender}'s underlying MediaStreamTrack was unmuted.\n * @event MediaTrackSender#unmuted\n */\n\nmodule.exports = MediaTrackSender;\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst { getUserMedia } = require('../../webrtc');\nconst { isIOS } = require('../../util/browserdetection');\n\nconst { capitalize, defer, isUserMediaTrack, waitForSometime, waitForEvent } = require('../../util');\nconst { typeErrors: { ILLEGAL_INVOKE } } = require('../../util/constants');\nconst detectSilentAudio = require('../../util/detectsilentaudio');\nconst detectSilentVideo = require('../../util/detectsilentvideo');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\nconst localMediaRestartDeferreds = require('../../util/localmediarestartdeferreds');\nconst gUMSilentTrackWorkaround = require('../../webaudio/workaround180748');\nconst MediaTrackSender = require('./sender');\n\nfunction mixinLocalMediaTrack(AudioOrVideoTrack) {\n /**\n * A {@link LocalMediaTrack} represents audio or video that your\n * {@link LocalParticipant} is sending to a {@link Room}. As such, it can be\n * enabled and disabled with {@link LocalMediaTrack#enable} and\n * {@link LocalMediaTrack#disable} or stopped completely with\n * {@link LocalMediaTrack#stop}.\n * @emits LocalMediaTrack#muted\n * @emits LocalMediaTrack#stopped\n * @emits LocalMediaTrack#unmuted\n */\n return class LocalMediaTrack extends AudioOrVideoTrack {\n /**\n * Construct a {@link LocalMediaTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - The underlying MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n const workaroundWebKitBug1208516 = isIOS()\n && isUserMediaTrack(mediaStreamTrack)\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string';\n\n options = Object.assign({\n getUserMedia,\n isCreatedByCreateLocalTracks: false,\n workaroundWebKitBug1208516,\n gUMSilentTrackWorkaround\n }, options);\n\n const mediaTrackSender = new MediaTrackSender(mediaStreamTrack);\n const { kind } = mediaTrackSender;\n\n super(mediaTrackSender, options);\n\n Object.defineProperties(this, {\n _constraints: {\n value: typeof options[kind] === 'object'\n ? options[kind]\n : {},\n writable: true\n },\n _getUserMedia: {\n value: options.getUserMedia\n },\n _gUMSilentTrackWorkaround: {\n value: options.gUMSilentTrackWorkaround\n },\n _eventsToReemitters: {\n value: new Map([\n ['muted', () => this.emit('muted', this)],\n ['unmuted', () => this.emit('unmuted', this)]\n ])\n },\n _workaroundWebKitBug1208516: {\n value: options.workaroundWebKitBug1208516\n },\n _workaroundWebKitBug1208516Cleanup: {\n value: null,\n writable: true\n },\n _didCallEnd: {\n value: false,\n writable: true\n },\n _isCreatedByCreateLocalTracks: {\n value: options.isCreatedByCreateLocalTracks\n },\n _trackSender: {\n value: mediaTrackSender\n },\n id: {\n enumerable: true,\n value: mediaTrackSender.id\n },\n isEnabled: {\n enumerable: true,\n get() {\n return mediaTrackSender.enabled;\n }\n },\n isMuted: {\n enumerable: true,\n get() {\n return mediaTrackSender.muted;\n }\n },\n isStopped: {\n enumerable: true,\n get() {\n return mediaTrackSender.readyState === 'ended';\n }\n }\n });\n\n // NOTE(mpatwardhan): As a workaround for WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=208516,\n // upon foregrounding, re-acquire new MediaStreamTrack if the existing one is ended or muted.\n if (this._workaroundWebKitBug1208516) {\n this._workaroundWebKitBug1208516Cleanup = restartWhenInadvertentlyStopped(this);\n }\n\n this._reemitTrackSenderEvents();\n }\n\n /**\n * @private\n */\n _end() {\n if (this._didCallEnd) {\n return;\n }\n super._end.call(this);\n this._didCallEnd = true;\n this._eventsToReemitters.forEach((reemitter, event) => this._trackSender.removeListener(event, reemitter));\n this.emit('stopped', this);\n }\n\n /**\n * @private\n */\n _initialize() {\n if (this._didCallEnd) {\n this._didCallEnd = false;\n }\n if (this._eventsToReemitters) {\n this._reemitTrackSenderEvents();\n }\n super._initialize.call(this);\n }\n\n /**\n * @private\n */\n _reacquireTrack(constraints) {\n const {\n _getUserMedia: getUserMedia,\n _gUMSilentTrackWorkaround: gUMSilentTrackWorkaround,\n _log: log,\n mediaStreamTrack: { kind }\n } = this;\n\n log.info('Re-acquiring the MediaStreamTrack');\n log.debug('Constraints:', constraints);\n\n const gUMConstraints = Object.assign({\n audio: false,\n video: false\n }, { [kind]: constraints });\n\n const gUMPromise = this._workaroundWebKitBug1208516Cleanup\n ? gUMSilentTrackWorkaround(log, getUserMedia, gUMConstraints)\n : getUserMedia(gUMConstraints);\n\n return gUMPromise.then(mediaStream => {\n return mediaStream.getTracks()[0];\n });\n }\n\n /**\n * @private\n */\n _reemitTrackSenderEvents() {\n this._eventsToReemitters.forEach((reemitter, event) => this._trackSender.on(event, reemitter));\n this._trackSender.dequeue('muted');\n this._trackSender.dequeue('unmuted');\n }\n\n /**\n * @private\n */\n _restart(constraints) {\n const { _log: log } = this;\n constraints = constraints || this._constraints;\n\n // NOTE(mmalavalli): If we try and restart a silent MediaStreamTrack\n // without stopping it first, then a NotReadableError is raised in case of\n // video, or the restarted audio will still be silent. Hence, we stop the\n // MediaStreamTrack here.\n this._stop();\n\n return this._reacquireTrack(constraints).catch(error => {\n log.error('Failed to re-acquire the MediaStreamTrack:', { error, constraints });\n throw error;\n }).then(newMediaStreamTrack => {\n log.info('Re-acquired the MediaStreamTrack');\n log.debug('MediaStreamTrack:', newMediaStreamTrack);\n this._constraints = Object.assign({}, constraints);\n return this._setMediaStreamTrack(newMediaStreamTrack);\n });\n }\n\n /**\n * @private\n */\n _setMediaStreamTrack(mediaStreamTrack) {\n // NOTE(mpatwardhan): Preserve the value of the \"enabled\" flag.\n mediaStreamTrack.enabled = this.mediaStreamTrack.enabled;\n\n // NOTE(mmalavalli): Stop the current MediaStreamTrack. If not already\n // stopped, this should fire a \"stopped\" event.\n this._stop();\n\n // NOTE(csantos): If there's an unprocessedTrack, this means RTCRtpSender has\n // the processedTrack already set, we don't want to replace that.\n return (this._unprocessedTrack ? Promise.resolve().then(() => {\n this._unprocessedTrack = mediaStreamTrack;\n }) : this._trackSender.setMediaStreamTrack(mediaStreamTrack).catch(error => {\n this._log.warn('setMediaStreamTrack failed:', { error, mediaStreamTrack });\n })).then(() => {\n this._initialize();\n this._getAllAttachedElements().forEach(el => this._attach(el));\n });\n }\n\n /**\n * @private\n */\n _stop() {\n this.mediaStreamTrack.stop();\n this._end();\n return this;\n }\n\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (enabled !== this.mediaStreamTrack.enabled) {\n this._log.info(`${enabled ? 'En' : 'Dis'}abling`);\n this.mediaStreamTrack.enabled = enabled;\n this.emit(enabled ? 'enabled' : 'disabled', this);\n }\n return this;\n }\n\n disable() {\n return this.enable(false);\n }\n\n restart(constraints) {\n const { kind } = this;\n if (!this._isCreatedByCreateLocalTracks) {\n return Promise.reject(ILLEGAL_INVOKE('restart', 'can only be called on a'\n + ` Local${capitalize(kind)}Track that is created using createLocalTracks`\n + ` or createLocal${capitalize(kind)}Track.`));\n }\n if (this._workaroundWebKitBug1208516Cleanup) {\n this._workaroundWebKitBug1208516Cleanup();\n this._workaroundWebKitBug1208516Cleanup = null;\n }\n let promise = this._restart(constraints);\n\n if (this._workaroundWebKitBug1208516) {\n promise = promise.finally(() => {\n this._workaroundWebKitBug1208516Cleanup = restartWhenInadvertentlyStopped(this);\n });\n }\n return promise;\n }\n\n stop() {\n this._log.info('Stopping');\n if (this._workaroundWebKitBug1208516Cleanup) {\n this._workaroundWebKitBug1208516Cleanup();\n this._workaroundWebKitBug1208516Cleanup = null;\n }\n return this._stop();\n }\n };\n}\n\n/**\n * Restart the given {@link LocalMediaTrack} if it has been inadvertently stopped.\n * @private\n * @param {LocalAudioTrack|LocalVideoTrack} localMediaTrack\n * @returns {function} Clean up listeners attached by the workaround\n */\nfunction restartWhenInadvertentlyStopped(localMediaTrack) {\n const { _log: log, kind } = localMediaTrack;\n const detectSilence = { audio: detectSilentAudio, video: detectSilentVideo }[kind];\n\n let { _dummyEl: el, mediaStreamTrack } = localMediaTrack;\n let trackChangeInProgress = null;\n\n function checkSilence() {\n // The dummy element is paused, so play it and then detect silence.\n return el.play().then(() => detectSilence(el)).then(isSilent => {\n if (isSilent) {\n log.warn('Silence detected');\n } else {\n log.info('Non-silence detected');\n }\n return isSilent;\n }).catch(error => {\n log.warn('Failed to detect silence:', error);\n }).finally(() => {\n // Pause the dummy element again.\n el.pause();\n });\n }\n\n function shouldReacquireTrack() {\n const {\n _workaroundWebKitBug1208516Cleanup,\n isStopped,\n mediaStreamTrack: { muted }\n } = localMediaTrack;\n\n const isInadvertentlyStopped = isStopped && !!_workaroundWebKitBug1208516Cleanup;\n\n // NOTE(mmalavalli): Restart the LocalMediaTrack if:\n // 1. The app is foregrounded, and\n // 2. A restart is not already in progress, and\n // 3. The LocalMediaTrack is either muted, inadvertently stopped or silent\n return Promise.resolve().then(() => {\n return document.visibilityState === 'visible'\n && !trackChangeInProgress\n && (muted || isInadvertentlyStopped || checkSilence());\n });\n }\n\n function maybeRestart() {\n return Promise.race([\n waitForEvent(mediaStreamTrack, 'unmute'),\n waitForSometime(50)\n ]).then(() => shouldReacquireTrack()).then(shouldReacquire => {\n if (shouldReacquire && !trackChangeInProgress) {\n trackChangeInProgress = defer();\n localMediaTrack._restart().finally(() => {\n el = localMediaTrack._dummyEl;\n removeMediaStreamTrackListeners();\n mediaStreamTrack = localMediaTrack.mediaStreamTrack;\n addMediaStreamTrackListeners();\n trackChangeInProgress.resolve();\n trackChangeInProgress = null;\n }).catch(error => {\n log.error('failed to restart track: ', error);\n });\n }\n\n // NOTE(mmalavalli): If the MediaStreamTrack ends before the DOM is visible,\n // then this makes sure that visibility callback for phase 2 is called only\n // after the MediaStreamTrack is re-acquired.\n const promise = (trackChangeInProgress && trackChangeInProgress.promise) || Promise.resolve();\n return promise.finally(() => localMediaRestartDeferreds.resolveDeferred(kind));\n }).catch(ex => {\n log.error(`error in maybeRestart: ${ex.message}`);\n });\n }\n\n function onMute() {\n const { _log: log, kind } = localMediaTrack;\n log.info('Muted');\n log.debug('LocalMediaTrack:', localMediaTrack);\n\n // NOTE(mmalavalli): When a LocalMediaTrack is muted without the app being\n // backgrounded, and the inadvertently paused elements are played before it\n // is restarted, it never gets unmuted due to the WebKit Bug 213853. Hence,\n // setting this Deferred will make sure that the inadvertently paused elements\n // are played only after the LocalMediaTrack is unmuted.\n //\n // Bug: https://bugs.webkit.org/show_bug.cgi?id=213853\n //\n localMediaRestartDeferreds.startDeferred(kind);\n }\n\n function addMediaStreamTrackListeners() {\n mediaStreamTrack.addEventListener('ended', maybeRestart);\n mediaStreamTrack.addEventListener('mute', onMute);\n mediaStreamTrack.addEventListener('unmute', maybeRestart);\n }\n\n function removeMediaStreamTrackListeners() {\n mediaStreamTrack.removeEventListener('ended', maybeRestart);\n mediaStreamTrack.removeEventListener('mute', onMute);\n mediaStreamTrack.removeEventListener('unmute', maybeRestart);\n }\n\n // NOTE(mpatwardhan): listen for document visibility callback on phase 1.\n // this ensures that we acquire media tracks before RemoteMediaTrack\n // tries to `play` them (in phase 2). This order is important because\n // play can fail on safari if audio is not being captured.\n let onVisibilityChange = isVisible => {\n return isVisible ? maybeRestart() : false;\n };\n documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChange);\n addMediaStreamTrackListeners();\n\n return () => {\n documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChange);\n removeMediaStreamTrackListeners();\n };\n}\n\nmodule.exports = mixinLocalMediaTrack;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst AudioTrack = require('./audiotrack');\nconst mixinLocalMediaTrack = require('./localmediatrack');\n\nconst LocalMediaAudioTrack = mixinLocalMediaTrack(AudioTrack);\n\n/**\n * A {@link LocalAudioTrack} is an {@link AudioTrack} representing audio that\n * your {@link LocalParticipant} can publish to a {@link Room}. It can be\n * enabled and disabled with {@link LocalAudioTrack#enable} and\n * {@link LocalAudioTrack#disable} or stopped completely with\n * {@link LocalAudioTrack#stop}.\n * @extends AudioTrack\n * @property {Track.ID} id - The {@link LocalAudioTrack}'s ID\n * @property {boolean} isMuted - Whether or not the audio source has stopped sending samples to the\n * {@link LocalAudioTrack}; This can happen when the microphone is taken over by another application,\n * mainly on mobile devices; When this property toggles, then muted and unmuted\n * events are fired appropriately\n * @property {boolean} isStopped - Whether or not the {@link LocalAudioTrack} is\n * stopped\n * @property {NoiseCancellation?} noiseCancellation - When a LocalAudioTrack is created\n * with {@link NoiseCancellationOptions}, this property provides interface\n * to enable or disable the noise cancellation at runtime.\n * @emits LocalAudioTrack#disabled\n * @emits LocalAudioTrack#enabled\n * @emits LocalAudioTrack#muted\n * @emits LocalAudioTrack#started\n * @emits LocalAudioTrack#stopped\n * @emits LocalAudioTrack#unmuted\n */\nclass LocalAudioTrack extends LocalMediaAudioTrack {\n /**\n * Construct a {@link LocalAudioTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - An audio MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n const noiseCancellation = options?.noiseCancellation || null;\n super(mediaStreamTrack, options);\n\n const { _log: log } = this;\n const { label: defaultDeviceLabel = '' } = mediaStreamTrack;\n const { deviceId: defaultDeviceId = '', groupId: defaultGroupId = '' } = mediaStreamTrack.getSettings();\n\n Object.defineProperties(this, {\n _currentDefaultDeviceInfo: {\n value: { deviceId: defaultDeviceId, groupId: defaultGroupId, label: defaultDeviceLabel },\n writable: true\n },\n _defaultDeviceCaptureMode: {\n value: !isIOS()\n && this._isCreatedByCreateLocalTracks\n && typeof navigator === 'object'\n && typeof navigator.mediaDevices === 'object'\n && typeof navigator.mediaDevices.addEventListener === 'function'\n && typeof navigator.mediaDevices.enumerateDevices === 'function'\n ? options?.defaultDeviceCaptureMode || 'auto'\n : 'manual'\n },\n _onDeviceChange: {\n value: () => {\n navigator.mediaDevices.enumerateDevices().then(deviceInfos => {\n // NOTE(mmalavalli): In Chrome, when the default device changes, and we restart the LocalAudioTrack with\n // device ID \"default\", it will not switch to the new default device unless all LocalAudioTracks capturing\n // from the old default device are stopped. So, we restart the LocalAudioTrack with the actual device ID of\n // the new default device instead.\n const defaultDeviceInfo = deviceInfos.find(({ deviceId, kind }) => {\n return kind === 'audioinput' && deviceId !== 'default';\n });\n\n if (defaultDeviceInfo && ['deviceId', 'groupId'].some(prop => {\n return defaultDeviceInfo[prop] !== this._currentDefaultDeviceInfo[prop];\n })) {\n log.info('Default device changed, restarting the LocalAudioTrack');\n log.debug(`Old default device: \"${this._currentDefaultDeviceInfo.deviceId}\" => \"${this._currentDefaultDeviceInfo.label}\"`);\n log.debug(`New default device: \"${defaultDeviceInfo.deviceId}\" => \"${defaultDeviceInfo.label}\"`);\n this._currentDefaultDeviceInfo = defaultDeviceInfo;\n this._restartDefaultDevice().catch(error => log.warn(`Failed to restart: ${error.message}`));\n }\n }, error => {\n log.warn(`Failed to run enumerateDevices(): ${error.message}`);\n });\n }\n },\n _restartOnDefaultDeviceChangeCleanup: {\n value: null,\n writable: true\n },\n noiseCancellation: {\n enumerable: true,\n value: noiseCancellation,\n writable: false\n },\n });\n\n log.debug('defaultDeviceCaptureMode:', this._defaultDeviceCaptureMode);\n this._maybeRestartOnDefaultDeviceChange();\n }\n\n toString() {\n return `[LocalAudioTrack #${this._instanceId}: ${this.id}]`;\n }\n\n attach(el) {\n el = super.attach.call(this, el);\n el.muted = true;\n return el;\n }\n\n /**\n * @private\n */\n _end() {\n return super._end.apply(this, arguments);\n }\n\n /**\n * @private\n */\n _maybeRestartOnDefaultDeviceChange() {\n const { _constraints: constraints, _defaultDeviceCaptureMode: defaultDeviceCaptureMode, _log: log } = this;\n const mediaStreamTrack = this.noiseCancellation ? this.noiseCancellation.sourceTrack : this.mediaStreamTrack;\n const { deviceId } = mediaStreamTrack.getSettings();\n\n const isNotEqualToCapturedDeviceIdOrEqualToDefault = requestedDeviceId => {\n return requestedDeviceId !== deviceId || requestedDeviceId === 'default';\n };\n\n const isCapturingFromDefaultDevice = (function checkIfCapturingFromDefaultDevice(deviceIdConstraint = {}) {\n if (typeof deviceIdConstraint === 'string') {\n return isNotEqualToCapturedDeviceIdOrEqualToDefault(deviceIdConstraint);\n } else if (Array.isArray(deviceIdConstraint)) {\n return deviceIdConstraint.every(isNotEqualToCapturedDeviceIdOrEqualToDefault);\n } else if (deviceIdConstraint.exact) {\n return checkIfCapturingFromDefaultDevice(deviceIdConstraint.exact);\n } else if (deviceIdConstraint.ideal) {\n return checkIfCapturingFromDefaultDevice(deviceIdConstraint.ideal);\n }\n return true;\n }(constraints.deviceId));\n\n if (defaultDeviceCaptureMode === 'auto' && isCapturingFromDefaultDevice) {\n if (!this._restartOnDefaultDeviceChangeCleanup) {\n log.info('LocalAudioTrack will be restarted if the default device changes');\n navigator.mediaDevices.addEventListener('devicechange', this._onDeviceChange);\n this._restartOnDefaultDeviceChangeCleanup = () => {\n log.info('Cleaning up the listener to restart the LocalAudioTrack if the default device changes');\n navigator.mediaDevices.removeEventListener('devicechange', this._onDeviceChange);\n this._restartOnDefaultDeviceChangeCleanup = null;\n };\n }\n } else {\n log.info('LocalAudioTrack will NOT be restarted if the default device changes');\n if (this._restartOnDefaultDeviceChangeCleanup) {\n this._restartOnDefaultDeviceChangeCleanup();\n }\n }\n }\n\n /**\n * @private\n */\n _reacquireTrack(constraints) {\n this._log.debug('_reacquireTrack: ', constraints);\n if (this.noiseCancellation) {\n return this.noiseCancellation.reacquireTrack(() => {\n return super._reacquireTrack.call(this, constraints);\n });\n }\n\n return super._reacquireTrack.call(this, constraints);\n }\n\n /**\n * @private\n */\n _restartDefaultDevice() {\n const constraints = Object.assign({}, this._constraints);\n const restartConstraints = Object.assign({}, constraints, { deviceId: this._currentDefaultDeviceInfo.deviceId });\n return this.restart(restartConstraints).then(() => {\n // NOTE(mmalavalli): Since we used the new default device's ID while restarting the LocalAudioTrack,\n // we reset the constraints to the original constraints so that the default device detection logic in\n // _maybeRestartOnDefaultDeviceChange() still works.\n this._constraints = constraints;\n this._maybeRestartOnDefaultDeviceChange();\n });\n }\n\n /**\n * Disable the {@link LocalAudioTrack}. This is equivalent to muting the audio source.\n * @returns {this}\n * @fires LocalAudioTrack#disabled\n */\n disable() {\n return super.disable.apply(this, arguments);\n }\n\n /**\n * Enable the {@link LocalAudioTrack}. This is equivalent to unmuting the audio source.\n * @returns {this}\n * @fires LocalAudioTrack#enabled\n *//**\n * Enable or disable the {@link LocalAudioTrack}. This is equivalent to unmuting or muting\n * the audio source respectively.\n * @param {boolean} [enabled] - Specify false to disable the\n * {@link LocalAudioTrack}\n * @returns {this}\n * @fires LocalAudioTrack#disabled\n * @fires LocalAudioTrack#enabled\n */\n enable() {\n return super.enable.apply(this, arguments);\n }\n\n /**\n * Restart the {@link LocalAudioTrack}. This stops the existing MediaStreamTrack\n * and creates a new MediaStreamTrack. If the {@link LocalAudioTrack} is being published\n * to a {@link Room}, then all the {@link RemoteParticipant}s will start receiving media\n * from the newly created MediaStreamTrack. You can access the new MediaStreamTrack via\n * the mediaStreamTrack property. If you want to listen to events on\n * the MediaStreamTrack directly, please do so in the \"started\" event handler. Also,\n * the {@link LocalAudioTrack}'s ID is no longer guaranteed to be the same as the\n * underlying MediaStreamTrack's ID.\n * @param {MediaTrackConstraints} [constraints] - The optional MediaTrackConstraints\n * for restarting the {@link LocalAudioTrack}; If not specified, then the current MediaTrackConstraints\n * will be used; If {} (empty object) is specified, then the default MediaTrackConstraints\n * will be used\n * @returns {Promise} Rejects with a TypeError if the {@link LocalAudioTrack} was not created\n * using an one of createLocalAudioTrack, createLocalTracks or connect;\n * Also rejects with the DOMException\n * raised by getUserMedia when it fails\n * @fires LocalAudioTrack#stopped\n * @fires LocalAudioTrack#started\n * @example\n * const { connect, createLocalAudioTrack } = require('twilio-video');\n *\n * // Create a LocalAudioTrack that captures audio from a USB microphone.\n * createLocalAudioTrack({ deviceId: 'usb-mic-id' }).then(function(localAudioTrack) {\n * return connect('token', {\n * name: 'my-cool-room',\n * tracks: [localAudioTrack]\n * });\n * }).then(function(room) {\n * // Restart the LocalAudioTrack to capture audio from the default microphone.\n * const localAudioTrack = Array.from(room.localParticipant.audioTracks.values())[0].track;\n * return localAudioTrack.restart({ deviceId: 'default-mic-id' });\n * });\n */\n restart() {\n return super.restart.apply(this, arguments);\n }\n\n /**\n * Calls stop on the underlying MediaStreamTrack. If you choose to stop a\n * {@link LocalAudioTrack}, you should unpublish it after stopping.\n * @returns {this}\n * @fires LocalAudioTrack#stopped\n */\n stop() {\n if (this.noiseCancellation) {\n this.noiseCancellation.stop();\n }\n if (this._restartOnDefaultDeviceChangeCleanup) {\n this._restartOnDefaultDeviceChangeCleanup();\n }\n return super.stop.apply(this, arguments);\n }\n}\n\n/**\n * The {@link LocalAudioTrack} was disabled, i.e. the audio source was muted by the user.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was\n * disabled\n * @event LocalAudioTrack#disabled\n */\n\n/**\n * The {@link LocalAudioTrack} was enabled, i.e. the audio source was unmuted by the user.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was enabled\n * @event LocalAudioTrack#enabled\n */\n\n/**\n * The {@link LocalAudioTrack} was muted because the audio source stopped sending samples, most\n * likely due to another application taking said audio source, especially on mobile devices.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was muted\n * @event LocalAudioTrack#muted\n */\n\n/**\n * The {@link LocalAudioTrack} started. This means there is enough audio data to\n * begin playback.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that started\n * @event LocalAudioTrack#started\n */\n\n/**\n * The {@link LocalAudioTrack} stopped, either because {@link LocalAudioTrack#stop}\n * or {@link LocalAudioTrack#restart} was called or because the underlying\n * MediaStreamTrack ended.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that stopped\n * @event LocalAudioTrack#stopped\n */\n\n/**\n * The {@link LocalAudioTrack} was unmuted because the audio source resumed sending samples,\n * most likely due to the application that took over the said audio source has released it\n * back to the application, especially on mobile devices. This event is also fired when\n * {@link LocalAudioTrack#restart} is called on a muted {@link LocalAudioTrack} with a\n * new audio source.\n * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was unmuted\n * @event LocalAudioTrack#unmuted\n */\n\nmodule.exports = LocalAudioTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\nconst LocalAudioTrackClass = require('../localaudiotrack');\n\nfunction LocalAudioTrack(mediaStreamTrack, options) {\n const track = new LocalAudioTrackClass(mediaStreamTrack, options);\n Object.setPrototypeOf(track, LocalAudioTrack.prototype);\n return track;\n}\n\ninherits(LocalAudioTrack, LocalAudioTrackClass);\n\nmodule.exports = LocalAudioTrack;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\nconst { DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS } = require('../../util/constants');\n\n/**\n * VideoProcessorEventObserver listens to {@link VideoProcessor} related events\n * and re-emits them as a generic event with some additional information.\n * @extends EventEmitter\n * @emits VideoProcessorEventObserver#event\n */\nclass VideoProcessorEventObserver extends EventEmitter {\n\n /**\n * Constructor.\n * @param {Log} log\n */\n constructor(log) {\n super();\n\n Object.defineProperties(this, {\n _lastStatsSaveTime: {\n value: null,\n writable: true\n },\n _lastStatsPublishTime: {\n value: null,\n writable: true\n },\n _log: {\n value: log\n },\n _processorInfo: {\n value: null,\n writable: true\n },\n _stats: {\n value: null,\n writable: true\n }\n });\n\n this.on('add', info => {\n this._lastStatsSaveTime = Date.now();\n this._lastStatsPublishTime = Date.now();\n this._processorInfo = info;\n this._stats = [];\n this._reemitEvent('add', this._getEventData());\n });\n\n this.on('remove', () => {\n const data = this._getEventData();\n this._lastStatsSaveTime = null;\n this._lastStatsPublishTime = null;\n this._processorInfo = null;\n this._stats = null;\n this._reemitEvent('remove', data);\n });\n\n this.on('start', () => {\n this._reemitEvent('start', this._getEventData());\n });\n\n this.on('stop', message => {\n this._reemitEvent('stop', Object.assign({ message }, this._getEventData()));\n });\n\n this.on('stats', () => this._maybeEmitStats());\n }\n\n /**\n * @private\n */\n _getEventData() {\n if (!this._processorInfo) {\n return {};\n }\n\n const { processor, captureHeight, captureWidth, inputFrameRate, isRemoteVideoTrack } = this._processorInfo;\n const data = { captureHeight, captureWidth, inputFrameRate, isRemoteVideoTrack };\n data.name = processor._name || 'VideoProcessor';\n\n ['assetsPath', 'blurFilterRadius', 'fitType', 'isSimdEnabled', 'maskBlurRadius', 'version'].forEach(prop => {\n const val = processor[`_${prop}`];\n if (typeof val !== 'undefined') {\n data[prop] = val;\n }\n });\n\n Object.keys(data).forEach(prop => {\n const val = data[prop];\n if (typeof val === 'boolean') {\n data[prop] = val ? 'true' : 'false';\n }\n });\n\n return data;\n }\n\n /**\n * Save stats every second. If a specific time interval has elapsed,\n * the stats event will be emitted\n * @private\n */\n _maybeEmitStats() {\n if (!this._stats || !this._processorInfo) {\n return;\n }\n const benchmark = this._processorInfo.processor._benchmark;\n if (!benchmark) {\n return;\n }\n const now = Date.now();\n if (now - this._lastStatsSaveTime < 1000) {\n return;\n }\n\n const entry = { outputFrameRate: benchmark.getRate('totalProcessingDelay') };\n ['captureFrameDelay', 'imageCompositionDelay', 'inputImageResizeDelay', 'processFrameDelay', 'segmentationDelay'].forEach(name => {\n entry[name] = benchmark.getAverageDelay(name);\n });\n this._lastStatsSaveTime = now;\n this._stats.push(entry);\n\n if (now - this._lastStatsPublishTime < DEFAULT_VIDEO_PROCESSOR_STATS_INTERVAL_MS) {\n return;\n }\n this._lastStatsPublishTime = now;\n const stats = this._stats.splice(0);\n const averages = stats.reduce((averages, current, n) => {\n Object.keys(entry).forEach(name => {\n if (!averages[name]) {\n averages[name] = 0;\n }\n averages[name] = ((averages[name] * n) + current[name]) / (n + 1);\n });\n return averages;\n }, {});\n\n Object.keys(averages).forEach(name => {\n averages[name] = parseFloat(averages[name].toFixed(2));\n });\n this._reemitEvent('stats', Object.assign({}, averages, this._getEventData()));\n }\n\n /**\n * @private\n */\n _reemitEvent(name, data) {\n this._log.debug(`VideoProcessor:${name}`, data);\n this.emit('event', { name, data });\n }\n}\n\nmodule.exports = VideoProcessorEventObserver;\n", "'use strict';\n\nconst MediaTrack = require('./mediatrack');\nconst VideoProcessorEventObserver = require('./videoprocessoreventobserver');\nconst { DEFAULT_FRAME_RATE } = require('../../util/constants');\n\n/**\n * A {@link VideoTrack} is a {@link Track} representing video.\n * @extends Track\n * @property {boolean} isStarted - Whether or not the {@link VideoTrack} has\n * started; if the {@link VideoTrack} started, there is enough video data to\n * begin playback\n * @property {boolean} isEnabled - Whether or not the {@link VideoTrack} is\n * enabled; if the {@link VideoTrack} is not enabled, it is \"paused\"\n * @property {VideoTrack.Dimensions} dimensions - The {@link VideoTrack}'s\n * {@link VideoTrack.Dimensions}\n * @property {Track.Kind} kind - \"video\"\n * @property {MediaStreamTrack} mediaStreamTrack - A video MediaStreamTrack\n * @property {?MediaStreamTrack} processedTrack - The source of processed video frames.\n * It is null if no VideoProcessor has been added.\n * @property {?VideoProcessor} processor - A {@link VideoProcessor} that is currently\n * processing video frames. It is null if video frames are not being processed.\n * @emits VideoTrack#dimensionsChanged\n * @emits VideoTrack#disabled\n * @emits VideoTrack#enabled\n * @emits VideoTrack#started\n */\nclass VideoTrack extends MediaTrack {\n /**\n * Construct a {@link VideoTrack}.\n * @param {MediaTrackTransceiver} mediaTrackTransceiver\n * @param {{log: Log}} options\n */\n constructor(mediaTrackTransceiver, options) {\n super(mediaTrackTransceiver, options);\n Object.defineProperties(this, {\n _captureTimeoutId: {\n value: null,\n writable: true\n },\n _isCapturing: {\n value: false,\n writable: true\n },\n _inputFrame: {\n value: null,\n writable: true\n },\n _outputFrame: {\n value: null,\n writable: true\n },\n _processorEventObserver: {\n value: null,\n writable: true,\n },\n _unmuteHandler: {\n value: null,\n writable: true\n },\n dimensions: {\n enumerable: true,\n value: {\n width: null,\n height: null\n }\n },\n processor: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n\n this._processorEventObserver = new (options.VideoProcessorEventObserver || VideoProcessorEventObserver)(this._log);\n\n return this;\n }\n\n /**\n * @private\n */\n _checkIfCanCaptureFrames(isPublishing = false) {\n let canCaptureFrames = true;\n let message = '';\n const { enabled, readyState } = this.mediaStreamTrack;\n\n if (!enabled) {\n canCaptureFrames = false;\n message = 'MediaStreamTrack is disabled';\n }\n if (readyState === 'ended') {\n canCaptureFrames = false;\n message = 'MediaStreamTrack is ended';\n }\n if (!this.processor) {\n canCaptureFrames = false;\n message = 'VideoProcessor not detected.';\n }\n if (!this._attachments.size && !isPublishing) {\n canCaptureFrames = false;\n message = 'VideoTrack is not publishing and there is no attached element.';\n }\n\n if (message) {\n this._log.debug(message);\n }\n return { canCaptureFrames, message };\n }\n\n /**\n * @private\n */\n _captureFrames() {\n if (this._isCapturing) {\n this._log.debug('Ignoring captureFrames call. Capture is already in progress');\n return;\n }\n if (!this._checkIfCanCaptureFrames().canCaptureFrames) {\n this._isCapturing = false;\n this._log.debug('Cannot capture frames. Ignoring captureFrames call.');\n return;\n }\n this._isCapturing = true;\n this._processorEventObserver.emit('start');\n this._log.debug('Start capturing frames');\n\n let startTime = Date.now();\n let processFramePeriodMs;\n\n this._dummyEl.play().then(() => {\n const captureFrame = cb => {\n clearTimeout(this._captureTimeoutId);\n const { frameRate = DEFAULT_FRAME_RATE } = this.mediaStreamTrack.getSettings();\n const capturePeriodMs = Math.floor(1000 / frameRate);\n let delay = capturePeriodMs - processFramePeriodMs;\n if (delay < 0 || typeof processFramePeriodMs !== 'number') {\n delay = 0;\n }\n this._captureTimeoutId = setTimeout(cb, delay);\n };\n const process = () => {\n const checkResult = this._checkIfCanCaptureFrames();\n if (!checkResult.canCaptureFrames) {\n this._isCapturing = false;\n this._processorEventObserver.emit('stop', checkResult.message);\n this._log.debug('Cannot capture frames. Stopping capturing frames.');\n return;\n }\n startTime = Date.now();\n\n const { width = 0, height = 0 } = this.mediaStreamTrack.getSettings();\n // Setting the canvas' dimension triggers a redraw.\n // Only set it if it has changed.\n if (this._inputFrame.width !== width) {\n this._inputFrame.width = width;\n this._inputFrame.height = height;\n\n if (this._outputFrame) {\n this._outputFrame.width = width;\n this._outputFrame.height = height;\n }\n }\n this._inputFrame.getContext('2d').drawImage(this._dummyEl, 0, 0, width, height);\n\n let result = null;\n try {\n result = this.processor.processFrame(this._inputFrame, this._outputFrame);\n } catch (ex) {\n this._log.debug('Exception detected after calling processFrame.', ex);\n }\n ((result instanceof Promise) ? result : Promise.resolve(result))\n .then(() => {\n if (this._outputFrame) {\n this.processedTrack.requestFrame();\n this._processorEventObserver.emit('stats');\n }\n })\n .finally(() => {\n processFramePeriodMs = Date.now() - startTime;\n captureFrame(process);\n });\n };\n captureFrame(process);\n }).catch(error => this._log.error('Video element cannot be played', { error, track: this }));\n }\n\n /**\n * @private\n */\n _initialize() {\n super._initialize();\n if (this._dummyEl) {\n this._dummyEl.onloadedmetadata = () => {\n if (dimensionsChanged(this, this._dummyEl)) {\n this.dimensions.width = this._dummyEl.videoWidth;\n this.dimensions.height = this._dummyEl.videoHeight;\n }\n };\n this._dummyEl.onresize = () => {\n if (dimensionsChanged(this, this._dummyEl)) {\n this.dimensions.width = this._dummyEl.videoWidth;\n this.dimensions.height = this._dummyEl.videoHeight;\n if (this.isStarted) {\n this._log.debug('Dimensions changed:', this.dimensions);\n this.emit(VideoTrack.DIMENSIONS_CHANGED, this);\n }\n }\n };\n }\n }\n\n /**\n * @private\n */\n _restartProcessor() {\n const processor = this.processor;\n if (processor) {\n this.removeProcessor(processor);\n this.addProcessor(processor);\n }\n }\n\n /**\n * @private\n */\n _start(dummyEl) {\n this.dimensions.width = dummyEl.videoWidth;\n this.dimensions.height = dummyEl.videoHeight;\n\n this._log.debug('Dimensions:', this.dimensions);\n this.emit(VideoTrack.DIMENSIONS_CHANGED, this);\n return super._start.call(this, dummyEl);\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * videoTrack.addProcessor(new GrayScaleProcessor(100));\n * });\n */\n addProcessor(processor) {\n if (typeof OffscreenCanvas !== 'function') {\n return this._log.warn('Adding a VideoProcessor is not supported in this browser.');\n }\n if (!processor || typeof processor.processFrame !== 'function') {\n throw new Error('Received an invalid VideoProcessor from addProcessor.');\n }\n if (this.processor) {\n throw new Error('A VideoProcessor has already been added.');\n }\n if (!this._dummyEl) {\n throw new Error('VideoTrack has not been initialized.');\n }\n\n this._log.debug('Adding VideoProcessor to the VideoTrack', processor);\n\n if (!this._unmuteHandler) {\n this._unmuteHandler = () => {\n this._log.debug('mediaStreamTrack unmuted');\n // NOTE(csantos): On certain scenarios where mediaStreamTrack is coming from muted to unmuted state,\n // the processedTrack doesn't unmutes automatically although enabled is already set to true.\n // This is a terminal state for the processedTrack and should be restarted. (VIDEO-4176)\n if (this.processedTrack.muted) {\n this._log.debug('mediaStreamTrack is unmuted but processedTrack is muted. Restarting processor.');\n this._restartProcessor();\n }\n };\n this.mediaStreamTrack.addEventListener('unmute', this._unmuteHandler);\n }\n\n const { width = 0, height = 0, frameRate = DEFAULT_FRAME_RATE } = this.mediaStreamTrack.getSettings();\n this._inputFrame = new OffscreenCanvas(width, height);\n this._outputFrame = document.createElement('canvas');\n this._outputFrame.width = width;\n this._outputFrame.height = height;\n\n this.processedTrack = this._outputFrame.captureStream(0).getTracks()[0];\n this.processedTrack.enabled = this.mediaStreamTrack.enabled;\n this.processor = processor;\n\n this._processorEventObserver.emit('add', {\n processor,\n captureHeight: height,\n captureWidth: width,\n inputFrameRate: frameRate,\n isRemoteVideoTrack: this.toString().includes('RemoteVideoTrack')\n });\n this._updateElementsMediaStreamTrack();\n this._captureFrames();\n return this;\n }\n\n /**\n * Create an HTMLVideoElement and attach the {@link VideoTrack} to it.\n *\n * The HTMLVideoElement's srcObject will be set to a new\n * MediaStream containing the {@link VideoTrack}'s MediaStreamTrack.\n *\n * @returns {HTMLVideoElement} videoElement\n * @example\n * const Video = require('twilio-video');\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * const videoElement = videoTrack.attach();\n * document.body.appendChild(videoElement);\n * });\n *//**\n * Attach the {@link VideoTrack} to an existing HTMLMediaElement. The\n * HTMLMediaElement could be an HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link VideoTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link MediaTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {HTMLMediaElement} mediaElement - The HTMLMediaElement to attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * videoTrack.attach(videoElement);\n * });\n *//**\n * Attach the {@link VideoTrack} to an HTMLMediaElement selected by\n * document.querySelector. The HTMLMediaElement could be an\n * HTMLAudioElement or an HTMLVideoElement.\n *\n * If the HTMLMediaElement's srcObject is not set to a MediaStream,\n * this method sets it to a new MediaStream containing the {@link VideoTrack}'s\n * MediaStreamTrack; otherwise, it adds the {@link VideoTrack}'s\n * MediaStreamTrack to the existing MediaStream. Finally, if there are any other\n * MediaStreamTracks of the same kind on the MediaStream, this method removes\n * them.\n *\n * @param {string} selector - A query selector for the HTMLMediaElement to\n * attach to\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const Video = require('twilio-video');\n *\n * const videoElement = document.createElement('video');\n * videoElement.id = 'my-video-element';\n * document.body.appendChild(videoElement);\n *\n * Video.createLocalVideoTrack().then(function(track) {\n * track.attach('#my-video-element');\n * });\n */\n attach() {\n const result = super.attach.apply(this, arguments);\n if (this.processor) {\n this._captureFrames();\n }\n return result;\n }\n\n /**\n * Detach the {@link VideoTrack} from all previously attached HTMLMediaElements.\n * @returns {Array} mediaElements\n * @example\n * const mediaElements = videoTrack.detach();\n * mediaElements.forEach(mediaElement => mediaElement.remove());\n *//**\n * Detach the {@link VideoTrack} from a previously attached HTMLMediaElement.\n * @param {HTMLMediaElement} mediaElement - One of the HTMLMediaElements to\n * which the {@link VideoTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * const videoElement = document.getElementById('my-video-element');\n * videoTrack.detach(videoElement).remove();\n *//**\n * Detach the {@link VideoTrack} from a previously attached HTMLMediaElement\n * specified by document.querySelector.\n * @param {string} selector - The query selector of HTMLMediaElement to which\n * the {@link VideoTrack} is attached\n * @returns {HTMLMediaElement} mediaElement\n * @example\n * videoTrack.detach('#my-video-element').remove();\n */\n detach() {\n return super.detach.apply(this, arguments);\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * Video.createLocalVideoTrack().then(function(videoTrack) {\n * const grayScaleProcessor = new GrayScaleProcessor(100);\n * videoTrack.addProcessor(grayScaleProcessor);\n * document.getElementById('remove-button').onclick = () => videoTrack.removeProcessor(grayScaleProcessor);\n * });\n */\n removeProcessor(processor) {\n if (!processor) {\n throw new Error('Received an invalid VideoProcessor from removeProcessor.');\n }\n if (!this.processor) {\n throw new Error('No existing VideoProcessor detected.');\n }\n if (processor !== this.processor) {\n throw new Error('The provided VideoProcessor is different than the existing one.');\n }\n\n this._processorEventObserver.emit('remove');\n this._log.debug('Removing VideoProcessor from the VideoTrack', processor);\n clearTimeout(this._captureTimeoutId);\n this.mediaStreamTrack.removeEventListener('unmute', this._unmuteHandler);\n this._unmuteHandler = null;\n this._isCapturing = false;\n\n this.processor = null;\n this.processedTrack = null;\n this._inputFrame.getContext('2d').clearRect(0, 0, this._inputFrame.width, this._inputFrame.height);\n this._outputFrame.getContext('2d').clearRect(0, 0, this._outputFrame.width, this._outputFrame.height);\n this._inputFrame = null;\n this._outputFrame = null;\n\n this._updateElementsMediaStreamTrack();\n return this;\n }\n}\n\nVideoTrack.DIMENSIONS_CHANGED = 'dimensionsChanged';\n\nfunction dimensionsChanged(track, elem) {\n return track.dimensions.width !== elem.videoWidth\n || track.dimensions.height !== elem.videoHeight;\n}\n\n/**\n * A {@link VideoTrack}'s width and height.\n * @typedef {object} VideoTrack.Dimensions\n * @property {?number} width - The {@link VideoTrack}'s width or null if the\n * {@link VideoTrack} has not yet started\n * @property {?number} height - The {@link VideoTrack}'s height or null if the\n * {@link VideoTrack} has not yet started\n */\n\n/**\n * A {@link VideoProcessor}, when added via {@link VideoTrack#addProcessor},\n * is used to process incoming video frames before\n * sending to the encoder or renderer.\n * @typedef {object} VideoProcessor\n * @property {function} processFrame - A callback to receive input and output frame buffers for processing.\n * The input frame buffer contains the original video frame which can be used for additional processing\n * such as applying filters to it. The output frame buffer is used to receive the processed video frame\n * before sending to the encoder or renderer.\n *\n * Any exception raised (either synchronously or asynchronously) in `processFrame` will result in the frame being dropped.\n * This callback has the following signature:

\n * processFrame(
\n *   inputFrameBuffer: OffscreenCanvas,
\n *   outputFrameBuffer: HTMLCanvasElement
\n * ): Promise<void> | void;\n *\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n */\n\n/**\n * The {@link VideoTrack}'s dimensions changed.\n * @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed\n * @event VideoTrack#dimensionsChanged\n */\n\n/**\n * The {@link VideoTrack} was disabled, i.e. \"paused\".\n * @param {VideoTrack} track - The {@link VideoTrack} that was disabled\n * @event VideoTrack#disabled\n */\n\n/**\n * The {@link VideoTrack} was enabled, i.e. \"unpaused\".\n * @param {VideoTrack} track - The {@link VideoTrack} that was enabled\n * @event VideoTrack#enabled\n */\n\n/**\n * The {@link VideoTrack} started. This means there is enough video data to\n * begin playback.\n * @param {VideoTrack} track - The {@link VideoTrack} that started\n * @event VideoTrack#started\n */\n\nmodule.exports = VideoTrack;\n", "'use strict';\n\nconst { isIOS } = require('../../util/browserdetection');\nconst detectSilentVideo = require('../../util/detectsilentvideo');\nconst mixinLocalMediaTrack = require('./localmediatrack');\nconst VideoTrack = require('./videotrack');\nconst { isUserMediaTrack } = require('../../util');\n\nconst LocalMediaVideoTrack = mixinLocalMediaTrack(VideoTrack);\n\n/**\n * A {@link LocalVideoTrack} is a {@link VideoTrack} representing video that\n * your {@link LocalParticipant} can publish to a {@link Room}. It can be\n * enabled and disabled with {@link LocalVideoTrack#enable} and\n * {@link LocalVideoTrack#disable} or stopped completely with\n * {@link LocalVideoTrack#stop}.\n * @extends VideoTrack\n * @property {Track.ID} id - The {@link LocalVideoTrack}'s ID\n * @property {boolean} isMuted - Whether or not the video source has stopped sending frames to the\n * {@link LocalVideoTrack}; This can happen when the camera is taken over by another application,\n * mainly on mobile devices; When this property toggles, then muted and unmuted\n * events are fired appropriately\n * @property {boolean} isStopped - Whether or not the {@link LocalVideoTrack} is\n * stopped\n * @emits LocalVideoTrack#disabled\n * @emits LocalVideoTrack#enabled\n * @emits LocalVideoTrack#muted\n * @emits LocalVideoTrack#started\n * @emits LocalVideoTrack#stopped\n * @emits LocalVideoTrack#unmuted\n */\nclass LocalVideoTrack extends LocalMediaVideoTrack {\n /**\n * Construct a {@link LocalVideoTrack} from a MediaStreamTrack.\n * @param {MediaStreamTrack} mediaStreamTrack - The underlying MediaStreamTrack\n * @param {LocalTrackOptions} [options] - {@link LocalTrack} options\n */\n constructor(mediaStreamTrack, options) {\n options = Object.assign({\n workaroundSilentLocalVideo: isIOS()\n && isUserMediaTrack(mediaStreamTrack)\n && typeof document !== 'undefined'\n && typeof document.createElement === 'function'\n }, options);\n\n super(mediaStreamTrack, options);\n\n Object.defineProperties(this, {\n _workaroundSilentLocalVideo: {\n value: options.workaroundSilentLocalVideo\n ? workaroundSilentLocalVideo\n : null\n },\n _workaroundSilentLocalVideoCleanup: {\n value: null,\n writable: true\n }\n });\n\n // NOTE(mmalavalli): In iOS Safari, we work around a bug where local video\n // MediaStreamTracks are silent (even though they are enabled, live and unmuted)\n // after accepting/rejecting a phone call.\n if (this._workaroundSilentLocalVideo) {\n this._workaroundSilentLocalVideoCleanup = this._workaroundSilentLocalVideo(this, document);\n }\n }\n\n toString() {\n return `[LocalVideoTrack #${this._instanceId}: ${this.id}]`;\n }\n\n /**\n * @private\n */\n _checkIfCanCaptureFrames() {\n return super._checkIfCanCaptureFrames.call(this, this._trackSender.isPublishing);\n }\n\n /**\n * @private\n */\n _end() {\n return super._end.apply(this, arguments);\n }\n\n /**\n * @private\n */\n _setSenderMediaStreamTrack(useProcessed) {\n const unprocessedTrack = this.mediaStreamTrack;\n const mediaStreamTrack = useProcessed ? this.processedTrack : unprocessedTrack;\n\n return this._trackSender.setMediaStreamTrack(mediaStreamTrack)\n .catch(error => this._log.warn(\n 'setMediaStreamTrack failed on LocalVideoTrack RTCRtpSender', { error, mediaStreamTrack }))\n .then(() => {\n this._unprocessedTrack = useProcessed ? unprocessedTrack : null;\n });\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * localVideoTrack.addProcessor(new GrayScaleProcessor(100));\n */\n addProcessor() {\n this._log.debug('Adding VideoProcessor to the LocalVideoTrack');\n const result = super.addProcessor.apply(this, arguments);\n\n if (!this.processedTrack) {\n return this._log.warn('Unable to add a VideoProcessor to the LocalVideoTrack');\n }\n\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the processed MediaStreamTrack', this.processedTrack);\n this._setSenderMediaStreamTrack(true);\n\n return result;\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * const grayScaleProcessor = new GrayScaleProcessor(100);\n * localVideoTrack.addProcessor(grayScaleProcessor);\n *\n * document.getElementById('remove-button').onclick = () => localVideoTrack.removeProcessor(grayScaleProcessor);\n */\n removeProcessor() {\n this._log.debug('Removing VideoProcessor from the LocalVideoTrack');\n const result = super.removeProcessor.apply(this, arguments);\n\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the original MediaStreamTrack');\n this._setSenderMediaStreamTrack()\n .then(() => this._updateElementsMediaStreamTrack());\n\n return result;\n }\n\n /**\n * Disable the {@link LocalVideoTrack}. This is equivalent to pausing a video source.\n * If a {@link VideoProcessor} is added, then processedTrack is also disabled.\n * @returns {this}\n * @fires VideoTrack#disabled\n */\n disable() {\n const result = super.disable.apply(this, arguments);\n if (this.processedTrack) {\n this.processedTrack.enabled = false;\n }\n return result;\n }\n\n /**\n * Enable the {@link LocalVideoTrack}. This is equivalent to unpausing the video source.\n * If a {@link VideoProcessor} is added, then processedTrack is also enabled.\n * @returns {this}\n * @fires VideoTrack#enabled\n *//**\n * Enable or disable the {@link LocalVideoTrack}. This is equivalent to unpausing or pausing\n * the video source respectively. If a {@link VideoProcessor} is added, then processedTrack\n * is also enabled or disabled.\n * @param {boolean} [enabled] - Specify false to disable the\n * {@link LocalVideoTrack}\n * @returns {this}\n * @fires VideoTrack#disabled\n * @fires VideoTrack#enabled\n */\n enable(enabled = true) {\n const result = super.enable.apply(this, arguments);\n if (this.processedTrack) {\n this.processedTrack.enabled = enabled;\n\n if (enabled) {\n this._captureFrames();\n this._log.debug('Updating LocalVideoTrack\\'s MediaStreamTrack with the processed MediaStreamTrack', this.processedTrack);\n this._setSenderMediaStreamTrack(true);\n }\n }\n return result;\n }\n\n /**\n * Restart the {@link LocalVideoTrack}. This stops the existing MediaStreamTrack\n * and creates a new MediaStreamTrack. If the {@link LocalVideoTrack} is being published\n * to a {@link Room}, then all the {@link RemoteParticipant}s will start receiving media\n * from the newly created MediaStreamTrack. You can access the new MediaStreamTrack via\n * the mediaStreamTrack property. If you want to listen to events on\n * the MediaStreamTrack directly, please do so in the \"started\" event handler. Also,\n * the {@link LocalVideoTrack}'s ID is no longer guaranteed to be the same as the\n * underlying MediaStreamTrack's ID.\n * @param {MediaTrackConstraints} [constraints] - The optional MediaTrackConstraints\n * for restarting the {@link LocalVideoTrack}; If not specified, then the current MediaTrackConstraints\n * will be used; If {} (empty object) is specified, then the default MediaTrackConstraints\n * will be used\n * @returns {Promise} Rejects with a TypeError if the {@link LocalVideoTrack} was not created\n * using an one of createLocalVideoTrack, createLocalTracks or connect;\n * Also rejects with the DOMException\n * raised by getUserMedia when it fails\n * @fires LocalVideoTrack#stopped\n * @fires LocalVideoTrack#started\n * @example\n * const { connect, createLocalVideoTrack } = require('twilio-video');\n *\n * // Create a LocalVideoTrack that captures video from the front-facing camera.\n * createLocalVideoTrack({ facingMode: 'user' }).then(function(localVideoTrack) {\n * return connect('token', {\n * name: 'my-cool-room',\n * tracks: [localVideoTrack]\n * });\n * }).then(function(room) {\n * // Restart the LocalVideoTrack to capture video from the back-facing camera.\n * const localVideoTrack = Array.from(room.localParticipant.videoTracks.values())[0].track;\n * return localVideoTrack.restart({ facingMode: 'environment' });\n * });\n */\n restart() {\n if (this._workaroundSilentLocalVideoCleanup) {\n this._workaroundSilentLocalVideoCleanup();\n this._workaroundSilentLocalVideoCleanup = null;\n }\n\n const promise = super.restart.apply(this, arguments);\n if (this.processor) {\n promise.then(() => {\n this._restartProcessor();\n });\n }\n\n if (this._workaroundSilentLocalVideo) {\n promise.finally(() => {\n this._workaroundSilentLocalVideoCleanup = this._workaroundSilentLocalVideo(this, document);\n });\n }\n return promise;\n }\n\n /**\n * Calls stop on the underlying MediaStreamTrack. If you choose to stop a\n * {@link LocalVideoTrack}, you should unpublish it after stopping.\n * @returns {this}\n * @fires LocalVideoTrack#stopped\n */\n stop() {\n if (this._workaroundSilentLocalVideoCleanup) {\n this._workaroundSilentLocalVideoCleanup();\n this._workaroundSilentLocalVideoCleanup = null;\n }\n return super.stop.apply(this, arguments);\n }\n}\n\n/**\n * Work around a bug where local video MediaStreamTracks are silent (even though\n * they are enabled, live and unmuted) after accepting/rejecting a phone call.\n * @private\n * @param {LocalVideoTrack} localVideoTrack\n * @param {HTMLDocument} doc\n * @returns {function} Cleans up listeners attached by the workaround\n */\nfunction workaroundSilentLocalVideo(localVideoTrack, doc) {\n const { _log: log } = localVideoTrack;\n let { _dummyEl: el, mediaStreamTrack } = localVideoTrack;\n\n function onUnmute() {\n if (!localVideoTrack.isEnabled) {\n return;\n }\n log.info('Unmuted, checking silence');\n\n // The dummy element is paused, so play it and then detect silence.\n el.play().then(() => detectSilentVideo(el, doc)).then(isSilent => {\n if (!isSilent) {\n log.info('Non-silent frames detected, so no need to restart');\n return;\n }\n log.warn('Silence detected, restarting');\n\n // NOTE(mmalavalli): If we try and restart a silent MediaStreamTrack\n // without stopping it first, then a NotReadableError is raised. Hence,\n // we stop the MediaStreamTrack here.\n localVideoTrack._stop();\n\n // Restart the LocalVideoTrack.\n // eslint-disable-next-line consistent-return\n return localVideoTrack._restart();\n }).catch(error => {\n log.warn('Failed to detect silence and restart:', error);\n }).finally(() => {\n // If silent frames were not detected, then pause the dummy element again.\n el = localVideoTrack._dummyEl;\n if (el && !el.paused) {\n el.pause();\n }\n\n // Reset the unmute handler.\n mediaStreamTrack.removeEventListener('unmute', onUnmute);\n mediaStreamTrack = localVideoTrack.mediaStreamTrack;\n mediaStreamTrack.addEventListener('unmute', onUnmute);\n });\n }\n\n // Set the unmute handler.\n mediaStreamTrack.addEventListener('unmute', onUnmute);\n\n return () => {\n mediaStreamTrack.removeEventListener('unmute', onUnmute);\n };\n}\n\n/**\n * The {@link LocalVideoTrack} was disabled, i.e. the video source was paused by the user.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was\n * disabled\n * @event LocalVideoTrack#disabled\n */\n\n/**\n * The {@link LocalVideoTrack} was enabled, i.e. the video source was unpaused by the user.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was enabled\n * @event LocalVideoTrack#enabled\n */\n\n/**\n * The {@link LocalVideoTrack} was muted because the video source stopped sending frames, most\n * likely due to another application taking said video source, especially on mobile devices.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was muted\n * @event LocalVideoTrack#muted\n */\n\n/**\n * The {@link LocalVideoTrack} started. This means there is enough video data\n * to begin playback.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that started\n * @event LocalVideoTrack#started\n */\n\n/**\n * The {@link LocalVideoTrack} stopped, either because {@link LocalVideoTrack#stop}\n * or {@link LocalVideoTrack#restart} was called or because the underlying\n * MediaStreamTrack ended.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that stopped\n * @event LocalVideoTrack#stopped\n */\n\n/**\n * The {@link LocalVideoTrack} was unmuted because the video source resumed sending frames,\n * most likely due to the application that took over the said video source has released it\n * back to the application, especially on mobile devices. This event is also fired when\n * {@link LocalVideoTrack#restart} is called on a muted {@link LocalVideoTrack} with a\n * new video source.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} that was unmuted\n * @event LocalVideoTrack#unmuted\n */\n\nmodule.exports = LocalVideoTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\n\nconst LocalVideoTrackClass = require('../localvideotrack');\n\nfunction LocalVideoTrack(mediaStreamTrack, options) {\n const track = new LocalVideoTrackClass(mediaStreamTrack, options);\n Object.setPrototypeOf(track, LocalVideoTrack.prototype);\n return track;\n}\n\ninherits(LocalVideoTrack, LocalVideoTrackClass);\n\nmodule.exports = LocalVideoTrack;\n", "'use strict';\n\nconst TrackTransceiver = require('../transceiver');\n\n/**\n * A {@link DataTrackTransceiver} represents either one or more local\n * RTCDataChannels or a single remote RTCDataChannel. It can be used to send or\n * receive data.\n * @extends TrackTransceiver\n * @property {string} id\n * @property {string} kind - \"data\"\n * @property {?number} maxPacketLifeTime\n * @property {?number} maxRetransmits\n * @property {boolean} ordered\n */\nclass DataTrackTransceiver extends TrackTransceiver {\n /**\n * Construct a {@link DataTrackTransceiver}.\n * @param {string} id\n * @param {?number} maxPacketLifeTime\n * @param {?number} maxRetransmits\n * @param {boolean} ordered\n */\n constructor(id, maxPacketLifeTime, maxRetransmits, ordered) {\n super(id, 'data');\n Object.defineProperties(this, {\n maxPacketLifeTime: {\n enumerable: true,\n value: maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: ordered\n }\n });\n }\n}\n\nmodule.exports = DataTrackTransceiver;\n", "'use strict';\n\nconst DataTrackTransceiver = require('./transceiver');\nconst makeUUID = require('../util').makeUUID;\n\n/**\n * A {@link DataTrackSender} represents a {@link DataTrackTransceiver} over\n * which data can be sent. Internally, it uses a collection of RTCDataChannels\n * to send data.\n * @extends DataTrackTransceiver\n */\nclass DataTrackSender extends DataTrackTransceiver {\n /**\n * Construct a {@link DataTrackSender}.\n * @param {?number} maxPacketLifeTime\n * @param {?number} maxRetransmits\n * @param {boolean} ordered\n */\n constructor(maxPacketLifeTime, maxRetransmtis, ordered) {\n super(makeUUID(), maxPacketLifeTime, maxRetransmtis, ordered);\n Object.defineProperties(this, {\n _clones: {\n value: new Set()\n },\n _dataChannels: {\n value: new Set()\n }\n });\n }\n\n /**\n * Add a cloned {@link DataTrackSender}.\n * @private\n * @returns {void}\n */\n _addClone(clone) {\n this._clones.add(clone);\n }\n\n /**\n * Remove a cloned {@link DataTrackSender}.\n * @returns {void}\n */\n removeClone(clone) {\n this._clones.delete(clone);\n }\n\n /**\n * Add an RTCDataChannel to the {@link DataTrackSender}.\n * @param {RTCDataChannel} dataChannel\n * @returns {this}\n */\n addDataChannel(dataChannel) {\n this._dataChannels.add(dataChannel);\n return this;\n }\n\n\n /**\n * Return a new {@link DataTrackSender}. Any message sent over this\n * {@link DataTrackSender} will also be sent over the clone. Whenever this\n * {@link DataTrackSender} is stopped, so to will the clone.\n * @returns {DataTrackSender}\n */\n clone() {\n const clone = new DataTrackSender(\n this.maxPacketLifeTime,\n this.maxRetransmits,\n this.ordered);\n this._addClone(clone);\n clone.once('stopped', () => this.removeClone(clone));\n return clone;\n }\n\n /**\n * Remove an RTCDataChannel from the {@link DataTrackSender}.\n * @param {RTCDataChannel} dataChannel\n * @returns {this}\n */\n removeDataChannel(dataChannel) {\n this._dataChannels.delete(dataChannel);\n return this;\n }\n\n /**\n * Send data over the {@link DataTrackSender}. Internally, this calls\n * send over each of the underlying RTCDataChannels.\n * @param {string|Blob|ArrayBuffer|ArrayBufferView} data\n * @returns {this}\n */\n send(data) {\n this._dataChannels.forEach(dataChannel => {\n try {\n dataChannel.send(data);\n } catch (error) {\n // Do nothing.\n }\n });\n this._clones.forEach(clone => {\n try {\n clone.send(data);\n } catch (error) {\n // Do nothing.\n }\n });\n return this;\n }\n\n stop() {\n this._dataChannels.forEach(dataChannel => dataChannel.close());\n this._clones.forEach(clone => clone.stop());\n super.stop();\n }\n}\n\nmodule.exports = DataTrackSender;\n", "'use strict';\n\nconst Track = require('./');\nconst DefaultDataTrackSender = require('../../data/sender');\n\n/**\n * A {@link LocalDataTrack} is a {@link Track} representing data that your\n * {@link LocalParticipant} can publish to a {@link Room}.\n * @extends Track\n * @property {Track.ID} id - The {@link LocalDataTrack}'s ID\n * @property {Track.Kind} kind - \"data\"\n * @property {?number} maxPacketLifeTime - If non-null, this represents a time\n * limit (in milliseconds) during which the {@link LocalDataTrack} will send\n * or re-send data if not acknowledged on the underlying RTCDataChannel(s).\n * @property {?number} maxRetransmits - If non-null, this represents the number\n * of times the {@link LocalDataTrack} will resend data if not successfully\n * delivered on the underlying RTCDataChannel(s).\n * @property {boolean} ordered - true if data on the {@link LocalDataTrack} is\n * guaranteed to be sent in order.\n * @property {boolean} reliable - This is true if both\n * maxPacketLifeTime and maxRetransmits are set to\n * null. In other words, if this is true, there is no bound on packet lifetime\n * or the number of times the {@link LocalDataTrack} will attempt to send\n * data, ensuring \"reliable\" transmission.\n * @example\n * var Video = require('twilio-video');\n *\n * var localDataTrack = new Video.LocalDataTrack();\n * window.addEventListener('mousemove', function(event) {\n * localDataTrack.send(JSON.stringify({\n * x: e.clientX,\n * y: e.clientY\n * }));\n * });\n *\n * var token1 = getAccessToken();\n * Video.connect(token1, {\n * name: 'my-cool-room',\n * tracks: [localDataTrack]\n * });\n *\n * var token2 = getAccessToken();\n * Video.connect(token2, {\n * name: 'my-cool-room',\n * tracks: []\n * }).then(function(room) {\n * room.on('trackSubscribed', function(track) {\n * track.on('message', function(message) {\n * console.log(JSON.parse(message)); // { x: , y: }\n * });\n * });\n * });\n */\nclass LocalDataTrack extends Track {\n /**\n * Construct a {@link LocalDataTrack}.\n * @param {LocalDataTrackOptions} [options] - {@link LocalDataTrack} options\n */\n constructor(options) {\n options = Object.assign({\n DataTrackSender: DefaultDataTrackSender,\n maxPacketLifeTime: null,\n maxRetransmits: null,\n ordered: true\n }, options);\n\n const DataTrackSender = options.DataTrackSender;\n const dataTrackSender = new DataTrackSender(\n options.maxPacketLifeTime,\n options.maxRetransmits,\n options.ordered);\n\n super(dataTrackSender.id, 'data', options);\n\n Object.defineProperties(this, {\n _trackSender: {\n value: dataTrackSender\n },\n id: {\n enumerable: true,\n value: dataTrackSender.id\n },\n maxPacketLifeTime: {\n enumerable: true,\n value: options.maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: options.maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: options.ordered\n },\n reliable: {\n enumerable: true,\n value: options.maxPacketLifeTime === null\n && options.maxRetransmits === null\n }\n });\n }\n\n /**\n * Send a message over the {@link LocalDataTrack}.\n * @param {string|Blob|ArrayBuffer|ArrayBufferView} data\n * @returns {void}\n */\n send(data) {\n this._trackSender.send(data);\n }\n}\n\n/**\n * {@link LocalDataTrack} options\n * @typedef {LocalTrackOptions} LocalDataTrackOptions\n * @property {?number} [maxPacketLifeTime=null] - Set this to limit the time\n * (in milliseconds) during which the LocalDataTrack will send or re-send data\n * if not successfully delivered on the underlying RTCDataChannel(s). It is an\n * error to specify both this and maxRetransmits.\n * @property {?number} [maxRetransmits=null] - Set this to limit the number of\n * times the {@link LocalDataTrack} will send or re-send data if not\n * acknowledged on the underlying RTCDataChannel(s). It is an error to specify\n * both this and maxPacketLifeTime.\n * @property {boolean} [ordered=true] - Set this to false to allow data on the\n * LocalDataTrack to be sent out-of-order.\n */\n\nmodule.exports = LocalDataTrack;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): Remove this when we go to the next major version. This is\n// only in place so that we can support ES6 classes without requiring `new`.\n'use strict';\n\nconst inherits = require('../../../vendor/inherits');\n\nconst LocalDataTrackClass = require('../localdatatrack');\n\nfunction LocalDataTrack(options) {\n const track = new LocalDataTrackClass(options);\n Object.setPrototypeOf(track, LocalDataTrack.prototype);\n return track;\n}\n\ninherits(LocalDataTrack, LocalDataTrackClass);\n\nmodule.exports = LocalDataTrack;\n", "'use strict';\n\nmodule.exports = {\n LocalAudioTrack: require('./localaudiotrack'),\n LocalVideoTrack: require('./localvideotrack'),\n LocalDataTrack: require('./localdatatrack')\n};\n", "/* eslint-disable @typescript-eslint/no-explicit-any */\n'use strict';\n\nimport {\n CreateLocalAudioTrackOptions,\n CreateLocalTrackOptions,\n CreateLocalTracksOptions,\n DefaultDeviceCaptureMode,\n LocalTrack,\n NoiseCancellationOptions\n} from '../tsdef';\n\nimport { applyNoiseCancellation } from './media/track/noisecancellationimpl';\n\nconst { buildLogLevels } = require('./util');\nconst { getUserMedia, MediaStreamTrack } = require('./webrtc');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst Log = require('./util/log');\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME, typeErrors: { INVALID_VALUE } } = require('./util/constants');\nconst workaround180748 = require('./webaudio/workaround180748');\n\n// This is used to make out which createLocalTracks() call a particular Log\n// statement belongs to. Each call to createLocalTracks() increments this\n// counter.\nlet createLocalTrackCalls = 0;\n\n\ntype ExtraLocalTrackOption = CreateLocalTrackOptions & { isCreatedByCreateLocalTracks?: boolean };\ntype ExtraLocalAudioTrackOption = ExtraLocalTrackOption & { defaultDeviceCaptureMode? : DefaultDeviceCaptureMode };\ntype ExtraLocalTrackOptions = { audio: ExtraLocalAudioTrackOption; video: ExtraLocalTrackOption; };\n\ninterface InternalOptions extends CreateLocalTracksOptions {\n getUserMedia: any;\n LocalAudioTrack: any;\n LocalDataTrack: any;\n LocalVideoTrack: any;\n MediaStreamTrack: any;\n Log: any;\n}\n\n/**\n * Request {@link LocalTrack}s. By default, it requests a\n * {@link LocalAudioTrack} and a {@link LocalVideoTrack}.\n * Note that on mobile browsers, the camera can be reserved by only one {@link LocalVideoTrack}\n * at any given time. If you attempt to create a second {@link LocalVideoTrack}, video frames\n * will no longer be supplied to the first {@link LocalVideoTrack}.\n * @alias module:twilio-video.createLocalTracks\n * @param {CreateLocalTracksOptions} [options]\n * @returns {Promise>}\n * @example\n * var Video = require('twilio-video');\n * // Request audio and video tracks\n * Video.createLocalTracks().then(function(localTracks) {\n * var localMediaContainer = document.getElementById('local-media-container-id');\n * localTracks.forEach(function(track) {\n * localMediaContainer.appendChild(track.attach());\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n * // Request just the default audio track\n * Video.createLocalTracks({ audio: true }).then(function(localTracks) {\n * return Video.connect('my-token', {\n * name: 'my-cool-room',\n * tracks: localTracks\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n * // Request the audio and video tracks with custom names\n * Video.createLocalTracks({\n * audio: { name: 'microphone' },\n * video: { name: 'camera' }\n * }).then(function(localTracks) {\n * localTracks.forEach(function(localTrack) {\n * console.log(localTrack.name);\n * });\n * });\n *\n * @example\n * var Video = require('twilio-video');\n * var localTracks;\n *\n * // Pre-acquire tracks to display camera preview.\n * Video.createLocalTracks().then(function(tracks) {\n * localTracks = tracks;\n * var localVideoTrack = localTracks.find(track => track.kind === 'video');\n * divContainer.appendChild(localVideoTrack.attach());\n * })\n *\n * // Later, join the Room with the pre-acquired LocalTracks.\n * Video.connect('token', {\n * name: 'my-cool-room',\n * tracks: localTracks\n * });\n *\n */\nexport async function createLocalTracks(options?: CreateLocalTracksOptions): Promise {\n const isAudioVideoAbsent =\n !(options && ('audio' in options || 'video' in options));\n\n const fullOptions: InternalOptions = {\n audio: isAudioVideoAbsent,\n getUserMedia,\n loggerName: DEFAULT_LOGGER_NAME,\n logLevel: DEFAULT_LOG_LEVEL,\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack,\n MediaStreamTrack,\n Log,\n video: isAudioVideoAbsent,\n ...options,\n };\n\n const logComponentName = `[createLocalTracks #${++createLocalTrackCalls}]`;\n const logLevels = buildLogLevels(fullOptions.logLevel);\n const log = new fullOptions.Log('default', logComponentName, logLevels, fullOptions.loggerName);\n\n const localTrackOptions = Object.assign({ log }, fullOptions);\n\n // NOTE(mmalavalli): The Room \"name\" in \"options\" was being used\n // as the LocalTrack name in asLocalTrack(). So we pass a copy of\n // \"options\" without the \"name\".\n // NOTE(joma): CreateLocalTracksOptions type does not really have a \"name\" property when used publicly by customers.\n // But we are passing this property when used internally by other JS files.\n // We can update this \"any\" type once those JS files are converted to TS.\n delete (localTrackOptions as any).name;\n\n if (fullOptions.audio === false && fullOptions.video === false) {\n log.info('Neither audio nor video requested, so returning empty LocalTracks');\n return [];\n }\n\n if (fullOptions.tracks) {\n log.info('Adding user-provided LocalTracks');\n log.debug('LocalTracks:', fullOptions.tracks);\n return fullOptions.tracks;\n }\n\n const extraLocalTrackOptions: ExtraLocalTrackOptions = {\n audio: typeof fullOptions.audio === 'object' && fullOptions.audio.name\n ? { name: fullOptions.audio.name }\n : { defaultDeviceCaptureMode: 'auto' },\n video: typeof fullOptions.video === 'object' && fullOptions.video.name\n ? { name: fullOptions.video.name }\n : {}\n };\n\n extraLocalTrackOptions.audio.isCreatedByCreateLocalTracks = true;\n extraLocalTrackOptions.video.isCreatedByCreateLocalTracks = true;\n\n let noiseCancellationOptions: NoiseCancellationOptions | undefined;\n\n if (typeof fullOptions.audio === 'object') {\n if (typeof fullOptions.audio.workaroundWebKitBug1208516 === 'boolean') {\n extraLocalTrackOptions.audio.workaroundWebKitBug1208516 = fullOptions.audio.workaroundWebKitBug1208516;\n }\n\n if ('noiseCancellationOptions' in fullOptions.audio) {\n noiseCancellationOptions = fullOptions.audio.noiseCancellationOptions;\n delete fullOptions.audio.noiseCancellationOptions;\n }\n\n if (!('defaultDeviceCaptureMode' in fullOptions.audio)) {\n extraLocalTrackOptions.audio.defaultDeviceCaptureMode = 'auto';\n } else if (['auto', 'manual'].every(mode => mode !== (fullOptions.audio as CreateLocalAudioTrackOptions).defaultDeviceCaptureMode)) {\n // eslint-disable-next-line new-cap\n throw INVALID_VALUE('CreateLocalAudioTrackOptions.defaultDeviceCaptureMode', ['auto', 'manual']);\n } else {\n extraLocalTrackOptions.audio.defaultDeviceCaptureMode = fullOptions.audio.defaultDeviceCaptureMode;\n }\n }\n\n if (typeof fullOptions.video === 'object' && typeof fullOptions.video.workaroundWebKitBug1208516 === 'boolean') {\n extraLocalTrackOptions.video.workaroundWebKitBug1208516 = fullOptions.video.workaroundWebKitBug1208516;\n }\n\n if (typeof fullOptions.audio === 'object') {\n delete fullOptions.audio.name;\n }\n if (typeof fullOptions.video === 'object') {\n delete fullOptions.video.name;\n }\n\n const mediaStreamConstraints = {\n audio: fullOptions.audio,\n video: fullOptions.video\n };\n\n const workaroundWebKitBug180748 = typeof fullOptions.audio === 'object' && fullOptions.audio.workaroundWebKitBug180748;\n\n try {\n const mediaStream = await (workaroundWebKitBug180748\n ? workaround180748(log, fullOptions.getUserMedia, mediaStreamConstraints)\n : fullOptions.getUserMedia(mediaStreamConstraints));\n\n const mediaStreamTracks = [\n ...mediaStream.getAudioTracks(),\n ...mediaStream.getVideoTracks(),\n ];\n\n log.info('Call to getUserMedia successful; got tracks:', mediaStreamTracks);\n\n return await Promise.all(\n mediaStreamTracks.map(async mediaStreamTrack => {\n if (mediaStreamTrack.kind === 'audio' && noiseCancellationOptions) {\n const { cleanTrack, noiseCancellation } = await applyNoiseCancellation(mediaStreamTrack, noiseCancellationOptions, log);\n return new localTrackOptions.LocalAudioTrack(cleanTrack, {\n ...extraLocalTrackOptions.audio,\n ...localTrackOptions,\n noiseCancellation\n });\n } else if (mediaStreamTrack.kind === 'audio') {\n return new localTrackOptions.LocalAudioTrack(mediaStreamTrack, {\n ...extraLocalTrackOptions.audio,\n ...localTrackOptions,\n });\n }\n return new localTrackOptions.LocalVideoTrack(mediaStreamTrack, {\n ...extraLocalTrackOptions.video,\n ...localTrackOptions,\n });\n })\n );\n } catch (error) {\n log.warn('Call to getUserMedia failed:', error);\n throw error;\n }\n}\n\n/**\n * {@link createLocalTracks} options\n * @typedef {object} CreateLocalTracksOptions\n * @property {boolean|CreateLocalTrackOptions|CreateLocalAudioTrackOptions} [audio=true] - Whether or not to\n * get local audio with getUserMedia when tracks\n * are not provided.\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {boolean|CreateLocalTrackOptions} [video=true] - Whether or not to\n * get local video with getUserMedia when tracks\n * are not provided.\n */\n", "import type { TimeMeasurement } from '../../tsdef/PreflightTypes';\n\nexport class Timer {\n // eslint-disable-next-line no-undefined\n private _end: number | undefined = undefined;\n private _start: number;\n\n constructor() {\n this.start();\n }\n\n start() : this {\n this._start = Date.now();\n return this;\n }\n\n stop(): this {\n this._end = Date.now();\n return this;\n }\n\n getTimeMeasurement() : TimeMeasurement {\n return {\n start: this._start,\n end: this._end,\n // eslint-disable-next-line no-undefined\n duration: this._end === undefined ? undefined : this._end - this._start\n };\n }\n}\n", "const r0 = 94.768; // Constant used in computing \"rFactor\".\n// copied from https://code.hq.twilio.com/client/sdk-frd/blob/master/voice/voice-mos-calculation.md\nexport function calculateMOS(rtt: number, jitter: number, fractionLost: number): number {\n // Compute the effective latency.\n const effectiveLatency: number = rtt + (jitter * 2) + 10;\n\n // Compute the initial \"rFactor\" from effective latency.\n let rFactor = 0;\n switch (true) {\n case effectiveLatency < 160:\n rFactor = r0 - (effectiveLatency / 40);\n break;\n case effectiveLatency < 1000:\n rFactor = r0 - ((effectiveLatency - 120) / 10);\n break;\n }\n\n // Adjust \"rFactor\" with the fraction of packets lost.\n switch (true) {\n case fractionLost <= (rFactor / 2.5):\n rFactor = Math.max(rFactor - fractionLost * 2.5, 6.52);\n break;\n default:\n rFactor = 0;\n break;\n }\n\n // Compute MOS from \"rFactor\".\n const mos: number = 1 +\n (0.035 * rFactor) +\n (0.000007 * rFactor) *\n (rFactor - 60) *\n (100 - rFactor);\n\n return mos;\n}\n\nexport function mosToScore(mosValue: number|null|undefined): number {\n let score = 0;\n if (!mosValue) {\n score = 0;\n } else if (mosValue > 4.2) {\n score = 5;\n } else if (mosValue > 4.0) {\n score = 4;\n } else if (mosValue > 3.6) {\n score = 3;\n } else if (mosValue > 3) {\n score = 2;\n } else {\n score = 1;\n }\n return score;\n}\n", "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RTCIceCandidateStats, SelectedIceCandidatePairStats, } from '../../tsdef/PreflightTypes';\nimport type { RTCIceCandidatePairStats } from './rtctypes';\n\ninterface RTCStatsReport {\n [x: string]: any;\n forEach(callbackfn: (value: any, key: string, parent: RTCStatsReport) => void, thisArg?: any): void;\n}\nexport interface CombinedConnectionStats {\n timestamp: number;\n bytesSent: number;\n bytesReceived: number;\n packets: number;\n packetsLost: number;\n roundTripTime: number;\n jitter: number;\n selectedIceCandidatePairStats: SelectedIceCandidatePairStats | null;\n iceCandidateStats: RTCIceCandidateStats[];\n}\n\nfunction getStatValues(report: RTCStatsReport, statName: string, kind: string[], reportTypes: string[]): number[] {\n let results: number[] = [];\n report.forEach(stat => {\n if (\n (reportTypes.length === 0 || reportTypes.includes(stat.type)) &&\n (kind.length === 0 || kind.includes(stat.kind)) &&\n typeof stat[statName] === 'number') {\n results.push(stat[statName]);\n }\n });\n return results;\n}\n\nexport async function getCombinedConnectionStats({ publisher, subscriber }: { publisher: RTCPeerConnection, subscriber: RTCPeerConnection }): Promise {\n const [publisherStats, subscriberStats] = await Promise.all([publisher, subscriber].map(pc => pc.getStats()));\n\n const timestamps = getStatValues(subscriberStats, 'timestamp', ['audio'], ['inbound-rtp']);\n const timestamp = timestamps.length > 0 ? timestamps[0] : 0;\n\n // jitter: subscriber, inbound-rtp, audio\n // Note: chrome has jitter for video, but not Safari.\n // Note: also jitter values are in seconds, but chrome's video jitter values were found to be are too big to be in seconds.\n const jitter = getStatValues(subscriberStats, 'jitter', ['audio'], ['inbound-rtp']).reduce((a, b) => Math.max(a, b), 0);\n\n // packets, packetsLost:\n // subscriber, audio, inbound-rtp,\n // subscriber, video, inbound-rtp\n const packets = getStatValues(subscriberStats, 'packetsReceived', ['audio', 'video'], ['inbound-rtp']).reduce((a, b) => a + b, 0);\n const packetsLost = getStatValues(subscriberStats, 'packetsLost', ['audio', 'video'], ['inbound-rtp']).reduce((a, b) => a + b, 0);\n\n // roundTripTime: publisher, audio, remote-inbound-rtp\n // publisher, video, remote-inbound-rtp\n const trackRoundTripTime = getStatValues(publisherStats, 'roundTripTime', ['audio', 'video'], ['remote-inbound-rtp']).reduce((a, b) => Math.max(a, b), 0);\n\n // currentRoundTripTime. subscriber, 'candidate-pair'\n const currentRoundTripTime = getStatValues(subscriberStats, 'currentRoundTripTime', [], ['candidate-pair']).reduce((a, b) => Math.max(a, b), 0);\n const roundTripTime = (currentRoundTripTime || trackRoundTripTime) * 1000;\n\n const bytesSent = getStatValues(publisherStats, 'bytesSent', [], ['candidate-pair']).reduce((a, b) => a + b, 0);\n const bytesReceived = getStatValues(subscriberStats, 'bytesReceived', [], ['candidate-pair']).reduce((a, b) => a + b, 0);\n\n const selectedIceCandidatePairStats = extractSelectedActiveCandidatePair(subscriberStats);\n\n const iceCandidateStats: RTCIceCandidateStats[] = [];\n subscriberStats.forEach(stat => {\n if (stat.type === 'local-candidate' || stat.type === 'remote-candidate') {\n iceCandidateStats.push(makeStandardCandidateStats(stat));\n }\n });\n return { timestamp, jitter, packets, packetsLost, roundTripTime, bytesSent, bytesReceived, selectedIceCandidatePairStats, iceCandidateStats };\n}\n\n\nfunction makeStandardCandidateStats(input: any) : RTCIceCandidateStats {\n const standardizedCandidateStatsKeys = [\n { key: 'transportId', type: 'string' },\n { key: 'candidateType', type: 'string' },\n { key: 'port', altKeys: ['portNumber'], type: 'number' },\n { key: 'address', altKeys: ['ip', 'ipAddress'], type: 'string' },\n { key: 'priority', type: 'number' },\n { key: 'protocol', altKeys: ['transport'], type: 'string' },\n { key: 'url', type: 'string' },\n { key: 'relayProtocol', type: 'string' },\n ];\n\n return standardizedCandidateStatsKeys.reduce(function(report: any, keyInfo) {\n let keysToLookFor = [keyInfo.key];\n if (keyInfo.altKeys) {\n keysToLookFor = keysToLookFor.concat(keyInfo.altKeys);\n }\n var key = keysToLookFor.find(key => key in input);\n if (key && typeof input[key] === keyInfo.type) {\n report[keyInfo.key] = input[key];\n }\n\n return report;\n }, {});\n}\n\nfunction extractSelectedActiveCandidatePair(stats: RTCStatsReport) : SelectedIceCandidatePairStats | null {\n let selectedCandidatePairId:string|null = null;\n const candidatePairs: RTCIceCandidatePairStats[] = [];\n stats.forEach(stat => {\n if (stat.type === 'transport' && stat.selectedCandidatePairId) {\n selectedCandidatePairId = stat.selectedCandidatePairId;\n } else if (stat.type === 'candidate-pair') {\n candidatePairs.push(stat);\n }\n });\n\n const activeCandidatePairStatsFound = candidatePairs.find(pair =>\n // Firefox\n pair.selected ||\n // Spec-compliant way\n (selectedCandidatePairId && pair.id === selectedCandidatePairId)\n );\n\n if (!activeCandidatePairStatsFound) {\n return null;\n }\n\n const activeCandidatePairStats = activeCandidatePairStatsFound as RTCIceCandidatePairStats;\n const activeLocalCandidateStats = stats.get(activeCandidatePairStats.localCandidateId);\n const activeRemoteCandidateStats = stats.get(activeCandidatePairStats.remoteCandidateId);\n if (!activeLocalCandidateStats || !activeRemoteCandidateStats) {\n return null;\n }\n\n return {\n localCandidate: makeStandardCandidateStats(activeLocalCandidateStats),\n remoteCandidate: makeStandardCandidateStats(activeRemoteCandidateStats)\n };\n}\n\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\nconst util = require('./util');\n\n/**\n * {@link StateMachine} represents a state machine. The state machine supports a\n * reentrant locking mechanism to allow asynchronous state transitions to ensure\n * they have not been preempted. Calls to {@link StateMachine#takeLock} are\n * guaranteed to be resolved in FIFO order.\n * @extends EventEmitter\n * @property {boolean} isLocked - whether or not the {@link StateMachine} is\n * locked performing asynchronous state transition\n * @property {string} state - the current state\n * @emits {@link StateMachine#stateChanged}\n */\nclass StateMachine extends EventEmitter {\n /**\n * Construct a {@link StateMachine}.\n * @param {string} initialState - the intiial state\n * @param {object} states\n */\n constructor(initialState, states) {\n super();\n let lock = null;\n let state = initialState;\n states = transformStates(states);\n Object.defineProperties(this, {\n _lock: {\n get() {\n return lock;\n },\n set(_lock) {\n lock = _lock;\n }\n },\n _reachableStates: {\n value: reachable(states)\n },\n _state: {\n get() {\n return state;\n },\n set(_state) {\n state = _state;\n }\n },\n _states: {\n value: states\n },\n _whenDeferreds: {\n value: new Set()\n },\n isLocked: {\n enumerable: true,\n get() {\n return lock !== null;\n }\n },\n state: {\n enumerable: true,\n get() {\n return state;\n }\n }\n });\n\n this.on('stateChanged', state => {\n this._whenDeferreds.forEach(deferred => {\n deferred.when(state, deferred.resolve, deferred.reject);\n });\n });\n }\n\n /**\n * Returns a promise whose executor function is called on each state change.\n * @param {function(state: string, resolve: function, reject: function): void} when\n * @returns {Promise.<*>}\n * @private\n */\n _whenPromise(when) {\n if (typeof when !== 'function') {\n return Promise.reject(new Error('when() executor must be a function'));\n }\n\n const deferred = util.defer();\n\n deferred.when = when;\n this._whenDeferreds.add(deferred);\n\n return deferred.promise.then(payload => {\n this._whenDeferreds.delete(deferred);\n return payload;\n }, error => {\n this._whenDeferreds.delete(deferred);\n throw error;\n });\n }\n\n /**\n * This method takes a lock and passes the {@link StateMachine#Key} to your\n * transition function. You may perform zero or more state transitions in your\n * transition function, but you should check for preemption in each tick. You\n * may also reenter the lock. Once the Promise returned by your transition\n * function resolves or rejects, this method releases the lock it acquired for\n * you.\n * @param {string} name - a name for the lock\n * @param {function(StateMachine#Key): Promise} transitionFunction\n * @returns {Promise}\n */\n // NOTE(mroberts): This method is named after a Haskell function:\n // https://hackage.haskell.org/package/base-4.8.2.0/docs/Control-Exception.html#v:bracket\n bracket(name, transitionFunction) {\n let key;\n const self = this;\n\n function releaseLock(error) {\n if (self.hasLock(key)) {\n self.releaseLockCompletely(key);\n }\n if (error) {\n throw error;\n }\n }\n\n return this.takeLock(name).then(function gotKey(_key) {\n key = _key;\n return transitionFunction(key);\n }).then(function success(result) {\n releaseLock();\n return result;\n }, releaseLock);\n }\n\n /**\n * Check whether or not a {@link StateMachine#Key} matches the lock.\n * @param {StateMachine#Key} key\n * @returns {boolean}\n */\n hasLock(key) {\n return this._lock === key;\n }\n\n /**\n * Preempt any pending state transitions and immediately transition to the new\n * state. If a lock name is specified, take the lock and return the\n * {@link StateMachine#Key}.\n * @param {string} newState\n * @param {?string} [name=null] - a name for the lock\n * @param {Array<*>} [payload=[]]\n * @returns {?StateMachine#Key}\n */\n preempt(newState, name, payload) {\n // 1. Check that the new state is valid.\n if (!isValidTransition(this._states, this.state, newState)) {\n throw new Error(`Cannot transition from \"${this.state}\" to \"${newState}\"`);\n }\n\n // 2. Release the old lock, if any.\n let oldLock;\n if (this.isLocked) {\n oldLock = this._lock;\n this._lock = null;\n }\n\n // 3. Take the lock, if requested.\n let key = null;\n if (name) {\n key = this.takeLockSync(name);\n }\n\n // 4. If a lock wasn't requested, take a \"preemption\" lock in order to\n // maintain FIFO order of those taking locks.\n const preemptionKey = key ? null : this.takeLockSync('preemption');\n\n // 5. Transition.\n this.transition(newState, key || preemptionKey, payload);\n\n // 6. Preempt anyone blocked on the old lock.\n if (oldLock) {\n oldLock.resolve();\n }\n\n // 7. Release the \"preemption\" lock, if we took it.\n if (preemptionKey) {\n this.releaseLock(preemptionKey);\n }\n\n return key;\n }\n\n /**\n * Release a lock. This method succeeds only if the {@link StateMachine} is\n * still locked and has not been preempted.\n * @param {StateMachine#Key} key\n * @throws Error\n */\n releaseLock(key) {\n if (!this.isLocked) {\n throw new Error(`Could not release the lock for ${key.name} because the StateMachine is not locked`);\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not release the lock for ${key.name} because ${this._lock.name} has the lock`);\n }\n if (key.depth === 0) {\n this._lock = null;\n key.resolve();\n } else {\n key.depth--;\n }\n }\n\n /**\n * Release a lock completely, even if it has been reentered. This method\n * succeeds only if the {@link StateMachine} is still locked and has not been\n * preempted.\n * @param {StateMachine#Key} key\n * @throws Error\n */\n releaseLockCompletely(key) {\n if (!this.isLocked) {\n throw new Error(`Could not release the lock for ${key.name} because the StateMachine is not locked`);\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not release the lock for ${key.name} because ${this._lock.name} has the lock`);\n }\n key.depth = 0;\n this._lock = null;\n key.resolve();\n }\n\n /**\n * Take a lock, returning a Promise for the {@link StateMachine#Key}. You should\n * take a lock anytime you intend to perform asynchronous transitions. Calls to\n * this method are guaranteed to be resolved in FIFO order. You may reenter\n * a lock by passing its {@link StateMachine#Key}.\n * @param {string|StateMachine#Key} nameOrKey - a name for the lock or an\n * existing {@link StateMachine#Key}\n * @returns {Promise}\n */\n takeLock(nameOrKey) {\n // Reentrant lock\n if (typeof nameOrKey === 'object') {\n const key = nameOrKey;\n return new Promise(resolve => {\n resolve(this.takeLockSync(key));\n });\n }\n\n // New lock\n const name = nameOrKey;\n if (this.isLocked) {\n var takeLock = this.takeLock.bind(this, name);\n return this._lock.promise.then(takeLock);\n }\n return Promise.resolve(this.takeLockSync(name));\n }\n\n /**\n * Take a lock, returning the {@Link StateMachine#Key}. This method throws if\n * the {@link StateMachine} is locked or the wrong {@link StateMachine#Key} is\n * provided. You may reenter a lock by passing its {@link StateMachine#Key}.\n * @param {string|StateMachine#Key} nameOrKey - a name for the lock or an\n * existing {@link StateMachine#Key}\n * @returns {object}\n * @throws Error\n */\n takeLockSync(nameOrKey) {\n const key = typeof nameOrKey === 'string' ? null : nameOrKey;\n const name = key ? key.name : nameOrKey;\n\n if (key && !this.hasLock(key) || !key && this.isLocked) {\n throw new Error(`Could not take the lock for ${name} because the lock for ${this._lock.name} was not released`);\n }\n\n // Reentrant lock\n if (key) {\n key.depth++;\n return key;\n }\n\n // New lock\n const lock = makeLock(name);\n this._lock = lock;\n return lock;\n }\n\n /**\n * Transition to a new state. If the {@link StateMachine} is locked, you must\n * provide the {@link StateMachine#Key}. An invalid state or the wrong\n * {@link StateMachine#Key} will throw an error.\n * @param {string} newState\n * @param {?StateMachine#Key} [key=null]\n * @param {Array<*>} [payload=[]]\n * @throws {Error}\n */\n transition(newState, key, payload) {\n payload = payload || [];\n\n // 1. If we're locked, required the key.\n if (this.isLocked) {\n if (!key) {\n throw new Error('You must provide the key in order to ' +\n 'transition');\n } else if (!this.hasLock(key)) {\n throw new Error(`Could not transition using the key for ${key.name} because ${this._lock.name} has the lock`);\n }\n } else if (key) {\n throw new Error(`Key provided for ${key.name}, but the StateMachine was not locked (possibly due to preemption)`);\n }\n\n // 2. Check that the new state is valid.\n if (!isValidTransition(this._states, this.state, newState)) {\n throw new Error(`Cannot transition from \"${this.state}\" to \"${newState}\"`);\n }\n\n // 3. Update the state and emit an event.\n this._state = newState;\n this.emit(...['stateChanged', newState].concat(payload));\n }\n\n /**\n * Attempt to transition to a new state. Unlike {@link StateMachine#transition},\n * this method does not throw.\n * @param {string} newState\n * @param {?StateMachine#Key} [key=null]\n * @param {Array<*>} [payload=[]]\n * @returns {boolean}\n */\n tryTransition(newState, key, payload) {\n try {\n this.transition(newState, key, payload);\n } catch (error) {\n return false;\n }\n return true;\n }\n\n /**\n * Return a Promise that resolves when the {@link StateMachine} transitions to\n * the specified state. If the {@link StateMachine} transitions such that the\n * requested state becomes unreachable, the Promise rejects.\n * @param {string} state\n * @returns {Promise}\n */\n when(state) {\n if (this.state === state) {\n return Promise.resolve(this);\n } else if (!isValidTransition(this._reachableStates, this.state, state)) {\n return Promise.reject(createUnreachableError(this.state, state));\n }\n return this._whenPromise((newState, resolve, reject) => {\n if (newState === state) {\n resolve(this);\n } else if (!isValidTransition(this._reachableStates, newState, state)) {\n reject(createUnreachableError(newState, state));\n }\n });\n }\n}\n\n/**\n * @event StateMachine#stateChanged\n * @param {string} newState\n */\n\n/**\n * Check if a transition is valid.\n * @private\n * @param {Map<*, Set<*>>} graph\n * @param {*} from\n * @param {*} to\n * @returns {boolean}\n */\nfunction isValidTransition(graph, from, to) {\n return graph.get(from).has(to);\n}\n\n/**\n * @typedef {object} StateMachine#Key\n */\n\nfunction makeLock(name) {\n const lock = util.defer();\n lock.name = name;\n lock.depth = 0;\n return lock;\n}\n\n/**\n * Compute the transitive closure of a graph (i.e. what nodes are reachable from\n * where).\n * @private\n * @param {Map<*, Set<*>>} graph\n * @returns {Map<*, Set<*>>}\n */\nfunction reachable(graph) {\n return Array.from(graph.keys()).reduce((newGraph, from) => newGraph.set(from, reachableFrom(graph, from)), new Map());\n}\n\n/**\n * Compute the Set of node reachable from a particular node in the graph.\n * @private\n * @param {Map<*, Set<*>>} graph\n * @param {*} from\n * @param {Set<*>} [to]\n * @returns {Set<*>}\n */\nfunction reachableFrom(graph, from, to) {\n to = to || new Set();\n graph.get(from).forEach(node => {\n if (!to.has(node)) {\n to.add(node);\n reachableFrom(graph, node, to).forEach(to.add, to);\n }\n });\n return to;\n}\n\nfunction transformStates(states) {\n const newStates = new Map();\n for (const key in states) {\n newStates.set(key, new Set(states[key]));\n }\n return newStates;\n}\n\n/**\n * Create an \"unreachable state\" Error.\n * @param {string} here\n * @param {string} there\n * @returns {Error}\n */\nfunction createUnreachableError(here, there) {\n return new Error(`\"${there}\" cannot be reached from \"${here}\"`);\n}\n\nmodule.exports = StateMachine;\n", "'use strict';\n\n/**\n * Monitor the network connection status to detect interruptions and handoffs.\n */\nclass NetworkMonitor {\n /**\n * Construct a {@link NetworkMonitor}.\n * @param {function} onNetworkChanged\n * @param {*} [options]\n */\n constructor(onNetworkChanged, options) {\n options = Object.assign({\n navigator,\n window,\n }, options);\n\n const nav = options.navigator;\n const connection = nav.connection || { type: null };\n let type = connection.type;\n\n const { _events, _listener, _target } = connection.type ? {\n _events: {\n value: ['change', 'typechange']\n },\n _listener: {\n value: () => {\n const networkChanged = type !== this.type && this.isOnline;\n type = this.type;\n if (networkChanged) {\n onNetworkChanged();\n }\n }\n },\n _target: {\n value: connection\n }\n } : {\n _events: {\n value: ['online']\n },\n _listener: {\n value: onNetworkChanged\n },\n _target: {\n value: options.window\n }\n };\n\n Object.defineProperties(this, {\n isOnline: {\n enumerable: true,\n get() {\n return typeof nav.onLine === 'boolean'\n ? nav.onLine\n : true;\n }\n },\n type: {\n enumerable: true,\n get() {\n return connection.type || null;\n }\n },\n _listener,\n _events,\n _target\n });\n }\n\n /**\n * Start the {@link NetworkMonitor}.\n */\n start() {\n this._events.forEach(event => {\n this._target.addEventListener(event, this._listener);\n });\n }\n\n /**\n * Stop the {@link NetworkMonitor}.\n */\n stop() {\n this._events.forEach(event => {\n this._target.removeEventListener(event, this._listener);\n });\n }\n}\n\nmodule.exports = NetworkMonitor;\n", "'use strict';\n\n/**\n * A {@link Timeout} represents a resettable and clearable timeout.\n */\nclass Timeout {\n /**\n * Construct a {@link Timeout}.\n * @param {function} fn - Function to call\n * @param {number} delay - Delay in milliseconds\n * @param {boolean} [autoStart=true] - If true, then start the {@link Timeout}.\n */\n constructor(fn, delay, autoStart = true) {\n Object.defineProperties(this, {\n _delay: {\n value: delay,\n writable: true\n },\n _fn: {\n value: fn\n },\n _timeout: {\n value: null,\n writable: true\n }\n });\n\n if (autoStart) {\n this.start();\n }\n }\n\n /**\n * The {@link Timeout} delay in milliseconds.\n * @property {number}\n */\n get delay() {\n return this._delay;\n }\n\n /**\n * Whether the {@link Timeout} is set.\n * @property {boolean}\n */\n get isSet() {\n return !!this._timeout;\n }\n\n /**\n * Update the {@link Timeout} delay.\n * @param {number} delay\n * @returns {void}\n */\n setDelay(delay) {\n this._delay = delay;\n }\n\n /**\n * Start the {@link Timeout}, if not already started.\n * @returns {void}\n */\n start() {\n if (!this.isSet) {\n this._timeout = setTimeout(() => {\n const fn = this._fn;\n this.clear();\n fn();\n }, this._delay);\n }\n }\n\n /**\n * Clear the {@link Timeout}.\n * @returns {void}\n */\n clear() {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n\n /**\n * Reset the {@link Timeout}.\n * @returns {void}\n */\n reset() {\n this.clear();\n this.start();\n }\n}\n\nmodule.exports = Timeout;\n", "module.exports = WebSocket;\n", "'use strict';\n\nconst StateMachine = require('./statemachine');\nconst { buildLogLevels, makeUUID } = require('./util');\nconst Log = require('./util/log');\nconst NetworkMonitor = require('./util/networkmonitor');\nconst Timeout = require('./util/timeout');\n\nlet nInstances = 0;\n\n/*\n TwilioConnection states\n -----------------------\n\n ------------------------------------------\n | |\n | v\n +---------+ +--------------+ +----------+\n | early | ----> | connecting | ----> | closed |\n +---------+ +--------------+ +----------+\n ^ | ^ | ^ ^\n | --------------------- | | | |\n | | --------------------- | | |\n | | | --------------------|------------------ |\n | v | | v |\n +----------+ +--------+ |\n | waiting | --------> | open | ---------------\n +----------+ +--------+\n */\n\nconst states = {\n closed: [],\n connecting: ['closed', 'open', 'waiting'],\n early: ['closed', 'connecting'],\n open: ['closed'],\n waiting: ['closed', 'connecting', 'early', 'open']\n};\n\nconst events = {\n closed: 'close',\n open: 'open',\n waiting: 'waiting'\n};\n\nconst TCMP_VERSION = 2;\n\nconst DEFAULT_MAX_CONSECUTIVE_MISSED_HEARTBEATS = 3;\nconst DEFAULT_MAX_CONSECUTIVE_FAILED_HELLOS = 3;\nconst DEFAULT_MAX_REQUESTED_HEARTBEAT_TIMEOUT = 5000;\nconst DEFAULT_OPEN_TIMEOUT = 15000;\nconst DEFAULT_WELCOME_TIMEOUT = 5000;\nconst OUTGOING_HEARTBEAT_OFFSET = 200;\n\nconst WS_CLOSE_NORMAL = 1000;\nconst WS_CLOSE_WELCOME_TIMEOUT = 3000;\nconst WS_CLOSE_HEARTBEATS_MISSED = 3001;\nconst WS_CLOSE_HELLO_FAILED = 3002;\nconst WS_CLOSE_SEND_FAILED = 3003;\nconst WS_CLOSE_NETWORK_CHANGED = 3004;\nconst WS_CLOSE_BUSY_WAIT = 3005;\nconst WS_CLOSE_SERVER_BUSY = 3006;\nconst WS_CLOSE_OPEN_TIMEOUT = 3007;\n\n// NOTE(joma): If you want to use close code 3008, please increment\n// the close code in test/integration/spec/docker/reconnection.js\n// line number 492.\n\nconst toplevel = globalThis;\nconst WebSocket = toplevel.WebSocket ? toplevel.WebSocket : require('ws');\n\nconst CloseReason = {\n BUSY: 'busy',\n FAILED: 'failed',\n LOCAL: 'local',\n REMOTE: 'remote',\n TIMEOUT: 'timeout'\n};\n\nconst wsCloseCodesToCloseReasons = new Map([\n [WS_CLOSE_WELCOME_TIMEOUT, CloseReason.TIMEOUT],\n [WS_CLOSE_HEARTBEATS_MISSED, CloseReason.TIMEOUT],\n [WS_CLOSE_HELLO_FAILED, CloseReason.FAILED],\n [WS_CLOSE_SEND_FAILED, CloseReason.FAILED],\n [WS_CLOSE_NETWORK_CHANGED, CloseReason.TIMEOUT],\n [WS_CLOSE_SERVER_BUSY, CloseReason.BUSY],\n [WS_CLOSE_OPEN_TIMEOUT, CloseReason.TIMEOUT]\n]);\n\n/**\n * A {@link TwilioConnection} represents a WebSocket connection\n * to a Twilio Connections Messaging Protocol (TCMP) server.\n * @fires TwilioConnection#close\n * @fires TwilioConnection#error\n * @fires TwilioConnection#message\n * @fires TwilioConnection#open\n * @fires TwilioConnection#waiting\n */\nclass TwilioConnection extends StateMachine {\n /**\n * Construct a {@link TwilioConnection}.\n * @param {string} serverUrl - TCMP server url\n * @param {TwilioConnectionOptions} options - {@link TwilioConnection} options\n */\n constructor(serverUrl, options) {\n super('early', states);\n\n options = Object.assign({\n helloBody: null,\n maxConsecutiveFailedHellos: DEFAULT_MAX_CONSECUTIVE_FAILED_HELLOS,\n maxConsecutiveMissedHeartbeats: DEFAULT_MAX_CONSECUTIVE_MISSED_HEARTBEATS,\n requestedHeartbeatTimeout: DEFAULT_MAX_REQUESTED_HEARTBEAT_TIMEOUT,\n openTimeout: DEFAULT_OPEN_TIMEOUT,\n welcomeTimeout: DEFAULT_WELCOME_TIMEOUT,\n Log,\n WebSocket\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n const log = new options.Log('default', this, logLevels, options.loggerName);\n\n const networkMonitor = options.networkMonitor ? new NetworkMonitor(() => {\n const { type } = networkMonitor;\n const reason = `Network changed${type ? ` to ${type}` : ''}`;\n log.debug(reason);\n this._close({ code: WS_CLOSE_NETWORK_CHANGED, reason });\n }) : null;\n\n Object.defineProperties(this, {\n _busyWaitTimeout: {\n value: null,\n writable: true\n },\n _consecutiveHeartbeatsMissed: {\n value: 0,\n writable: true\n },\n _cookie: {\n value: null,\n writable: true\n },\n _eventObserver: {\n value: options.eventObserver\n },\n _heartbeatTimeout: {\n value: null,\n writable: true\n },\n _hellosLeft: {\n value: options.maxConsecutiveFailedHellos,\n writable: true\n },\n _instanceId: {\n value: ++nInstances\n },\n _log: {\n value: log\n },\n _messageQueue: {\n value: []\n },\n _networkMonitor: {\n value: networkMonitor\n },\n _options: {\n value: options\n },\n _openTimeout: {\n value: null,\n writable: true\n },\n _sendHeartbeatTimeout: {\n value: null,\n writable: true\n },\n _serverUrl: {\n value: serverUrl\n },\n _welcomeTimeout: {\n value: null,\n writable: true\n },\n _ws: {\n value: null,\n writable: true\n }\n });\n\n const eventsToLevels = {\n connecting: 'info',\n early: 'info',\n open: 'info',\n waiting: 'warning',\n closed: 'info'\n };\n\n this.on('stateChanged', (state, ...args) => {\n if (state in events) {\n this.emit(events[state], ...args);\n }\n const event = { name: state, group: 'signaling', level: eventsToLevels[this.state] };\n if (state === 'closed') {\n const [reason] = args;\n event.payload = { reason };\n event.level = reason === CloseReason.LOCAL ? 'info' : 'error';\n }\n this._eventObserver.emit('event', event);\n });\n\n this._eventObserver.emit('event', { name: this.state, group: 'signaling', level: eventsToLevels[this.state] });\n this._connect();\n }\n\n toString() {\n return `[TwilioConnection #${this._instanceId}: ${this._ws.url}]`;\n }\n\n /**\n * Close the {@link TwilioConnection}.\n * @param {{code: number, reason: string}} event\n * @private\n */\n _close({ code, reason }) {\n if (this.state === 'closed') {\n return;\n }\n if (this._openTimeout) {\n this._openTimeout.clear();\n }\n if (this._welcomeTimeout) {\n this._welcomeTimeout.clear();\n }\n if (this._heartbeatTimeout) {\n this._heartbeatTimeout.clear();\n }\n if (this._sendHeartbeatTimeout) {\n this._sendHeartbeatTimeout.clear();\n }\n if (this._networkMonitor) {\n this._networkMonitor.stop();\n }\n if (this._busyWaitTimeout && code !== WS_CLOSE_BUSY_WAIT) {\n this._busyWaitTimeout.clear();\n }\n this._messageQueue.splice(0);\n const log = this._log;\n\n if (code === WS_CLOSE_NORMAL) {\n log.debug('Closed');\n this.transition('closed', null, [CloseReason.LOCAL]);\n } else {\n log.warn(`Closed: ${code} - ${reason}`);\n if (code !== WS_CLOSE_BUSY_WAIT) {\n this.transition('closed', null, [\n wsCloseCodesToCloseReasons.get(code) || CloseReason.REMOTE\n ]);\n }\n }\n const { readyState } = this._ws;\n const { WebSocket } = this._options;\n\n if (readyState !== WebSocket.CLOSING && readyState !== WebSocket.CLOSED) {\n this._ws.close(code, reason);\n }\n }\n\n /**\n * Connect to the TCMP server.\n * @private\n */\n _connect() {\n const log = this._log;\n if (this.state === 'waiting') {\n this.transition('early');\n } else if (this.state !== 'early') {\n log.warn(`Unexpected state \"${this.state}\" for connecting to the`\n + ' TCMP server.');\n return;\n }\n this._ws = new this._options.WebSocket(this._serverUrl);\n const ws = this._ws;\n log.debug('Created a new WebSocket:', ws);\n ws.addEventListener('close', event => this._close(event));\n\n const { openTimeout } = this._options;\n // Add a timeout for getting the onopen event on the WebSocket (15 sec). After that, attempt to reconnect only if this is not the first attempt.\n this._openTimeout = new Timeout(() => {\n const reason = `Failed to open in ${openTimeout} ms`;\n this._close({ code: WS_CLOSE_OPEN_TIMEOUT, reason });\n }, openTimeout);\n\n ws.addEventListener('open', () => {\n log.debug('WebSocket opened:', ws);\n this._openTimeout.clear();\n this._startHandshake();\n if (this._networkMonitor) {\n this._networkMonitor.start();\n }\n });\n\n ws.addEventListener('message', message => {\n log.debug(`Incoming: ${message.data}`);\n try {\n message = JSON.parse(message.data);\n } catch (error) {\n this.emit('error', error);\n return;\n }\n\n switch (message.type) {\n case 'bad':\n this._handleBad(message);\n break;\n case 'busy':\n this._handleBusy(message);\n break;\n case 'bye':\n // Do nothing.\n break;\n case 'msg':\n this._handleMessage(message);\n // NOTE(mpatwardhan): Each incoming message should be treated as an incoming\n // heartbeat intentionally falling through to 'heartbeat' case.\n // eslint-disable-next-line no-fallthrough\n case 'heartbeat':\n this._handleHeartbeat();\n break;\n case 'welcome':\n this._handleWelcome(message);\n break;\n default:\n this._log.debug(`Unknown message type: ${message.type}`);\n this.emit('error', new Error(`Unknown message type: ${message.type}`));\n break;\n }\n });\n }\n\n /**\n * Handle an incoming \"bad\" message.\n * @param {{reason: string}} message\n * @private\n */\n _handleBad({ reason }) {\n const log = this._log;\n if (!['connecting', 'open'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"bad\" message`\n + ' from the TCMP server.');\n return;\n }\n if (this.state === 'connecting') {\n log.warn(`Closing: ${WS_CLOSE_HELLO_FAILED} - ${reason}`);\n this._close({ code: WS_CLOSE_HELLO_FAILED, reason });\n return;\n }\n log.debug(`Error: ${reason}`);\n this.emit('error', new Error(reason));\n }\n\n /**\n * Handle an incoming \"busy\" message.\n * @param {{cookie: ?string, keepAlive: boolean, retryAfter: number}} message\n * @private\n */\n _handleBusy({ cookie, keepAlive, retryAfter }) {\n const log = this._log;\n if (!['connecting', 'waiting'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"busy\" message`\n + ' from the TCMP server.');\n return;\n }\n if (this._busyWaitTimeout) {\n this._busyWaitTimeout.clear();\n }\n if (this._welcomeTimeout) {\n this._welcomeTimeout.clear();\n }\n const reason = retryAfter < 0\n ? 'Received terminal \"busy\" message'\n : `Received \"busy\" message, retrying after ${retryAfter} ms`;\n\n if (retryAfter < 0) {\n log.warn(`Closing: ${WS_CLOSE_SERVER_BUSY} - ${reason}`);\n this._close({ code: WS_CLOSE_SERVER_BUSY, reason });\n return;\n }\n const { maxConsecutiveFailedHellos } = this._options;\n this._hellosLeft = maxConsecutiveFailedHellos;\n this._cookie = cookie || null;\n\n if (keepAlive) {\n log.warn(reason);\n this._busyWaitTimeout = new Timeout(() => this._startHandshake(), retryAfter);\n } else {\n log.warn(`Closing: ${WS_CLOSE_BUSY_WAIT} - ${reason}`);\n this._close({ code: WS_CLOSE_BUSY_WAIT, reason });\n this._busyWaitTimeout = new Timeout(() => this._connect(), retryAfter);\n }\n\n this.transition('waiting', null, [keepAlive, retryAfter]);\n }\n\n /**\n * Handle an incoming \"heartbeat\" message.\n * @private\n */\n _handleHeartbeat() {\n if (this.state !== 'open') {\n this._log.warn(`Unexpected state \"${this.state}\" for handling a \"heartbeat\"`\n + ' message from the TCMP server.');\n return;\n }\n this._heartbeatTimeout.reset();\n }\n\n /**\n * Handle a missed \"heartbeat\" message.\n * @private\n */\n _handleHeartbeatTimeout() {\n if (this.state !== 'open') {\n return;\n }\n const log = this._log;\n const { maxConsecutiveMissedHeartbeats } = this._options;\n\n log.debug(`Consecutive heartbeats missed: ${maxConsecutiveMissedHeartbeats}`);\n const reason = `Missed ${maxConsecutiveMissedHeartbeats} \"heartbeat\" messages`;\n log.warn(`Closing: ${WS_CLOSE_HEARTBEATS_MISSED} - ${reason}`);\n this._close({ code: WS_CLOSE_HEARTBEATS_MISSED, reason });\n }\n\n /**\n * Handle an incoming \"msg\" message.\n * @param {{body: object}} message\n * @private\n */\n _handleMessage({ body }) {\n if (this.state !== 'open') {\n this._log.warn(`Unexpected state \"${this.state}\" for handling a \"msg\" message`\n + ' from the TCMP server.');\n return;\n }\n this.emit('message', body);\n }\n\n /**\n * Handle an incoming \"welcome\" message.\n * @param {{ negotiatedTimeout: number }} message\n * @private\n */\n _handleWelcome({ negotiatedTimeout }) {\n const log = this._log;\n\n if (!['connecting', 'waiting'].includes(this.state)) {\n log.warn(`Unexpected state \"${this.state}\" for handling a \"welcome\"`\n + ' message from the TCMP server.');\n return;\n }\n\n if (this.state === 'waiting') {\n log.debug('Received \"welcome\" message, no need to retry connection.');\n this._busyWaitTimeout.clear();\n }\n\n const { maxConsecutiveMissedHeartbeats } = this._options;\n const heartbeatTimeout = negotiatedTimeout * maxConsecutiveMissedHeartbeats;\n const outgoingHeartbeatTimeout = negotiatedTimeout - OUTGOING_HEARTBEAT_OFFSET;\n\n this._welcomeTimeout.clear();\n this._heartbeatTimeout = new Timeout(() => this._handleHeartbeatTimeout(), heartbeatTimeout);\n this._messageQueue.splice(0).forEach(message => this._send(message));\n this._sendHeartbeatTimeout = new Timeout(() => this._sendHeartbeat(), outgoingHeartbeatTimeout);\n this.transition('open');\n }\n\n /**\n * Handle a missed \"welcome\" message.\n * @private\n */\n _handleWelcomeTimeout() {\n if (this.state !== 'connecting') {\n return;\n }\n const log = this._log;\n\n if (this._hellosLeft <= 0) {\n const reason = 'All handshake attempts failed';\n log.warn(`Closing: ${WS_CLOSE_WELCOME_TIMEOUT} - ${reason}`);\n this._close({ code: WS_CLOSE_WELCOME_TIMEOUT, reason });\n return;\n }\n\n const { maxConsecutiveFailedHellos } = this._options;\n log.warn(`Handshake attempt ${maxConsecutiveFailedHellos - this._hellosLeft} failed`);\n this._startHandshake();\n }\n\n /**\n * Send a message to the TCMP server.\n * @param {*} message\n * @private\n */\n _send(message) {\n const { readyState } = this._ws;\n const { WebSocket } = this._options;\n if (readyState === WebSocket.OPEN) {\n const data = JSON.stringify(message);\n this._log.debug(`Outgoing: ${data}`);\n try {\n this._ws.send(data);\n if (this._sendHeartbeatTimeout) {\n // Each outgoing message is to be treated as an outgoing heartbeat.\n this._sendHeartbeatTimeout.reset();\n }\n } catch (error) {\n const reason = 'Failed to send message';\n this._log.warn(`Closing: ${WS_CLOSE_SEND_FAILED} - ${reason}`);\n this._close({ code: WS_CLOSE_SEND_FAILED, reason });\n }\n }\n }\n\n /**\n * Send a \"heartbeat\" message.\n * @private\n */\n _sendHeartbeat() {\n if (this.state === 'closed') {\n return;\n }\n this._send({ type: 'heartbeat' });\n }\n\n /**\n * Send a \"hello\" message.\n * @private\n */\n _sendHello() {\n const { helloBody, requestedHeartbeatTimeout: timeout } = this._options;\n const hello = {\n id: makeUUID(),\n timeout,\n type: 'hello',\n version: TCMP_VERSION\n };\n if (this._cookie) {\n hello.cookie = this._cookie;\n }\n if (helloBody) {\n hello.body = helloBody;\n }\n this._send(hello);\n }\n\n /**\n * Send or enqueue a message.\n * @param {*} message\n * @private\n */\n _sendOrEnqueue(message) {\n if (this.state === 'closed') {\n return;\n }\n const sendOrEnqueue = this.state === 'open'\n ? message => this._send(message)\n : message => this._messageQueue.push(message);\n\n sendOrEnqueue(message);\n }\n\n /**\n * Start the TCMP handshake.\n * @private\n */\n _startHandshake() {\n if (['early', 'waiting'].includes(this.state)) {\n this.transition('connecting');\n }\n if (this.state !== 'connecting') {\n return;\n }\n this._hellosLeft--;\n this._sendHello();\n const { welcomeTimeout } = this._options;\n this._welcomeTimeout = new Timeout(() => this._handleWelcomeTimeout(), welcomeTimeout);\n }\n\n /**\n * Close the {@link TwilioConnection}.\n * @returns {void}\n */\n close() {\n if (this.state === 'closed') {\n return;\n }\n this._sendOrEnqueue({ type: 'bye' });\n this._close({ code: WS_CLOSE_NORMAL, reason: 'Normal' });\n }\n\n /**\n * Send a \"msg\" message.\n * @param {*} body\n * @returns {void}\n */\n sendMessage(body) {\n this._sendOrEnqueue({ body, type: 'msg' });\n }\n}\n\n/**\n * A unique string depicting the reason for the {@link TwilioConnection} being closed.\n * @enum {string}\n */\nTwilioConnection.CloseReason = CloseReason;\n\n/**\n * A {@link TwilioConnection} was closed.\n * @event TwilioConnection#close\n * @param {CloseReason} reason - The reason for the {@link TwilioConnection} being closed\n */\n\n/**\n * A {@link TwilioConnection} received an error from the TCMP server.\n * @event TwilioConnection#error\n * @param {Error} error - The TCMP server error\n */\n\n/**\n * A {@link TwilioConnection} received a message from the TCMP server.\n * @event TwilioConnection#message\n * @param {*} body - Message body\n */\n\n/**\n * A {@link TwilioConnection} completed a hello/welcome handshake with the TCMP server.\n * @event TwilioConnection#open\n */\n\n/**\n * A {@link TwilioConnection} received a \"busy\" message from the TCMP server.\n * @event TwilioConnection#waiting\n * @param {boolean} keepAlive - true if the WebSocket connection is retained\n * @param {number} retryAfter - delay in milliseconds after which a retry is attempted\n */\n\n/**\n * {@link TwilioConnection} options\n * @typedef {object} TwilioConnectionOptions\n * @property {EventObserver} [eventObserver] - Optional event observer\n * @property {*} [helloBody=null] - Optional body for \"hello\" message\n * @property {LogLevel} [logLevel=warn] - Log level of the {@link TwilioConnection}\n * @property {number} [maxConsecutiveFailedHellos=3] - Max. number of consecutive failed \"hello\"s\n * @property {number} [maxConsecutiveMissedHeartbeats=3] - Max. number of (effective) consecutive \"heartbeat\" messages that can be missed\n * @property {number} [requestedHeartbeatTimeout=5000] - \"heartbeat\" timeout (ms) requested by the {@link TwilioConnection}\n * @property {number} [welcomeTimeout=5000] - Time (ms) to wait for the \"welcome\" message after sending the \"hello\" message\n */\n\nmodule.exports = TwilioConnection;\n", "'use strict';\n\n/**\n * @extends Error\n * @property {number} code - Error code\n */\nclass TwilioError extends Error {\n /**\n * Creates a new {@link TwilioError}\n * @param {number} code - Error code\n * @param {string} [message] - Error message\n * @param {string} [fileName] - Name of the script file where error was generated\n * @param {number} [lineNumber] - Line number of the script file where error was generated\n */\n constructor(code) {\n const args = [].slice.call(arguments, 1);\n super(...args);\n Object.setPrototypeOf(this, TwilioError.prototype);\n\n const error = Error.apply(this, args);\n error.name = 'TwilioError';\n\n Object.defineProperty(this, 'code', {\n value: code,\n enumerable: true\n });\n\n Object.getOwnPropertyNames(error).forEach(function(prop) {\n Object.defineProperty(this, prop, {\n value: error[prop],\n enumerable: true\n });\n }, this);\n }\n\n /**\n * Returns human readable string describing the error.\n * @returns {string}\n */\n toString() {\n const message = this.message ? `: ${this.message}` : '';\n return `${this.name} ${this.code}${message}`;\n }\n}\n\nmodule.exports = TwilioError;\n", "// NOTE: Do not edit this file. This code is auto-generated. Contact the\n// Twilio SDK Team for more information.\n\n'use strict';\n\nconst TwilioError = require('./twilioerror');\nconst TwilioErrorByCode = {};\n\n/**\n * Create a {@link TwilioError} for a given code and message.\n * @private\n * @param {number} [code] - Error code\n * @param {string} [message] - Error message\n * @returns {TwilioError}\n */\nexports.createTwilioError = function createTwilioError(code, message) {\n code = typeof code === 'number' ? code : 0;\n message = typeof message === 'string' && message ? message : 'Unknown error';\n return TwilioErrorByCode[code] ? new TwilioErrorByCode[code]() : new TwilioError(code, message);\n};\n\n/**\n * @class AccessTokenInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room is invalid.\n * @extends TwilioError\n * @property {number} code - 20101\n * @property {string} message - 'Invalid Access Token'\n */\nclass AccessTokenInvalidError extends TwilioError {\n constructor() {\n super(20101, 'Invalid Access Token');\n Object.setPrototypeOf(this, AccessTokenInvalidError.prototype);\n }\n}\n\nexports.AccessTokenInvalidError = AccessTokenInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20101, { value: AccessTokenInvalidError });\n\n/**\n * @class AccessTokenHeaderInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has an invalid header.\n * @extends TwilioError\n * @property {number} code - 20102\n * @property {string} message - 'Invalid Access Token header'\n */\nclass AccessTokenHeaderInvalidError extends TwilioError {\n constructor() {\n super(20102, 'Invalid Access Token header');\n Object.setPrototypeOf(this, AccessTokenHeaderInvalidError.prototype);\n }\n}\n\nexports.AccessTokenHeaderInvalidError = AccessTokenHeaderInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20102, { value: AccessTokenHeaderInvalidError });\n\n/**\n * @class AccessTokenIssuerInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room contains an invalid issuer or subject.\n * @extends TwilioError\n * @property {number} code - 20103\n * @property {string} message - 'Invalid Access Token issuer/subject'\n */\nclass AccessTokenIssuerInvalidError extends TwilioError {\n constructor() {\n super(20103, 'Invalid Access Token issuer/subject');\n Object.setPrototypeOf(this, AccessTokenIssuerInvalidError.prototype);\n }\n}\n\nexports.AccessTokenIssuerInvalidError = AccessTokenIssuerInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20103, { value: AccessTokenIssuerInvalidError });\n\n/**\n * @class AccessTokenExpiredError\n * @classdesc Raised whenever the AccessToken used for connecting, or reconnecting to a Room has expired.\n * @extends TwilioError\n * @property {number} code - 20104\n * @property {string} message - 'Access Token expired or expiration date invalid'\n */\nclass AccessTokenExpiredError extends TwilioError {\n constructor() {\n super(20104, 'Access Token expired or expiration date invalid');\n Object.setPrototypeOf(this, AccessTokenExpiredError.prototype);\n }\n}\n\nexports.AccessTokenExpiredError = AccessTokenExpiredError;\nObject.defineProperty(TwilioErrorByCode, 20104, { value: AccessTokenExpiredError });\n\n/**\n * @class AccessTokenNotYetValidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room is not yet valid.\n * @extends TwilioError\n * @property {number} code - 20105\n * @property {string} message - 'Access Token not yet valid'\n */\nclass AccessTokenNotYetValidError extends TwilioError {\n constructor() {\n super(20105, 'Access Token not yet valid');\n Object.setPrototypeOf(this, AccessTokenNotYetValidError.prototype);\n }\n}\n\nexports.AccessTokenNotYetValidError = AccessTokenNotYetValidError;\nObject.defineProperty(TwilioErrorByCode, 20105, { value: AccessTokenNotYetValidError });\n\n/**\n * @class AccessTokenGrantsInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has invalid grants.\n * @extends TwilioError\n * @property {number} code - 20106\n * @property {string} message - 'Invalid Access Token grants'\n */\nclass AccessTokenGrantsInvalidError extends TwilioError {\n constructor() {\n super(20106, 'Invalid Access Token grants');\n Object.setPrototypeOf(this, AccessTokenGrantsInvalidError.prototype);\n }\n}\n\nexports.AccessTokenGrantsInvalidError = AccessTokenGrantsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20106, { value: AccessTokenGrantsInvalidError });\n\n/**\n * @class AccessTokenSignatureInvalidError\n * @classdesc Raised whenever the AccessToken used for connecting to a Room has an invalid signature.\n * @extends TwilioError\n * @property {number} code - 20107\n * @property {string} message - 'Invalid Access Token signature'\n */\nclass AccessTokenSignatureInvalidError extends TwilioError {\n constructor() {\n super(20107, 'Invalid Access Token signature');\n Object.setPrototypeOf(this, AccessTokenSignatureInvalidError.prototype);\n }\n}\n\nexports.AccessTokenSignatureInvalidError = AccessTokenSignatureInvalidError;\nObject.defineProperty(TwilioErrorByCode, 20107, { value: AccessTokenSignatureInvalidError });\n\n/**\n * @class SignalingConnectionError\n * @classdesc Raised whenever a signaling connection error occurs that is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53000\n * @property {string} message - 'Signaling connection error'\n */\nclass SignalingConnectionError extends TwilioError {\n constructor() {\n super(53000, 'Signaling connection error');\n Object.setPrototypeOf(this, SignalingConnectionError.prototype);\n }\n}\n\nexports.SignalingConnectionError = SignalingConnectionError;\nObject.defineProperty(TwilioErrorByCode, 53000, { value: SignalingConnectionError });\n\n/**\n * @class SignalingConnectionDisconnectedError\n * @classdesc Raised whenever the signaling connection is unexpectedly disconnected.\n * @extends TwilioError\n * @property {number} code - 53001\n * @property {string} message - 'Signaling connection disconnected'\n */\nclass SignalingConnectionDisconnectedError extends TwilioError {\n constructor() {\n super(53001, 'Signaling connection disconnected');\n Object.setPrototypeOf(this, SignalingConnectionDisconnectedError.prototype);\n }\n}\n\nexports.SignalingConnectionDisconnectedError = SignalingConnectionDisconnectedError;\nObject.defineProperty(TwilioErrorByCode, 53001, { value: SignalingConnectionDisconnectedError });\n\n/**\n * @class SignalingConnectionTimeoutError\n * @classdesc Raised when connection liveliness checks fail, or when the signaling session expires.\n * @extends TwilioError\n * @property {number} code - 53002\n * @property {string} message - 'Signaling connection timed out'\n */\nclass SignalingConnectionTimeoutError extends TwilioError {\n constructor() {\n super(53002, 'Signaling connection timed out');\n Object.setPrototypeOf(this, SignalingConnectionTimeoutError.prototype);\n }\n}\n\nexports.SignalingConnectionTimeoutError = SignalingConnectionTimeoutError;\nObject.defineProperty(TwilioErrorByCode, 53002, { value: SignalingConnectionTimeoutError });\n\n/**\n * @class SignalingIncomingMessageInvalidError\n * @classdesc Raised whenever the Client receives a message from the Server that the Client cannot handle.\n * @extends TwilioError\n * @property {number} code - 53003\n * @property {string} message - 'Client received an invalid signaling message'\n */\nclass SignalingIncomingMessageInvalidError extends TwilioError {\n constructor() {\n super(53003, 'Client received an invalid signaling message');\n Object.setPrototypeOf(this, SignalingIncomingMessageInvalidError.prototype);\n }\n}\n\nexports.SignalingIncomingMessageInvalidError = SignalingIncomingMessageInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53003, { value: SignalingIncomingMessageInvalidError });\n\n/**\n * @class SignalingOutgoingMessageInvalidError\n * @classdesc Raised whenever the Client sends a message to the Server that the Server cannot handle.\n * @extends TwilioError\n * @property {number} code - 53004\n * @property {string} message - 'Client sent an invalid signaling message'\n */\nclass SignalingOutgoingMessageInvalidError extends TwilioError {\n constructor() {\n super(53004, 'Client sent an invalid signaling message');\n Object.setPrototypeOf(this, SignalingOutgoingMessageInvalidError.prototype);\n }\n}\n\nexports.SignalingOutgoingMessageInvalidError = SignalingOutgoingMessageInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53004, { value: SignalingOutgoingMessageInvalidError });\n\n/**\n * @class SignalingServerBusyError\n * @classdesc Raised when the server is too busy to accept new clients.\n * @extends TwilioError\n * @property {number} code - 53006\n * @property {string} message - 'Video server is busy'\n */\nclass SignalingServerBusyError extends TwilioError {\n constructor() {\n super(53006, 'Video server is busy');\n Object.setPrototypeOf(this, SignalingServerBusyError.prototype);\n }\n}\n\nexports.SignalingServerBusyError = SignalingServerBusyError;\nObject.defineProperty(TwilioErrorByCode, 53006, { value: SignalingServerBusyError });\n\n/**\n * @class RoomNameInvalidError\n * @classdesc Raised whenever a Room name is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53100\n * @property {string} message - 'Room name is invalid'\n */\nclass RoomNameInvalidError extends TwilioError {\n constructor() {\n super(53100, 'Room name is invalid');\n Object.setPrototypeOf(this, RoomNameInvalidError.prototype);\n }\n}\n\nexports.RoomNameInvalidError = RoomNameInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53100, { value: RoomNameInvalidError });\n\n/**\n * @class RoomNameTooLongError\n * @classdesc Raised whenever a Room name is too long.\n * @extends TwilioError\n * @property {number} code - 53101\n * @property {string} message - 'Room name is too long'\n */\nclass RoomNameTooLongError extends TwilioError {\n constructor() {\n super(53101, 'Room name is too long');\n Object.setPrototypeOf(this, RoomNameTooLongError.prototype);\n }\n}\n\nexports.RoomNameTooLongError = RoomNameTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53101, { value: RoomNameTooLongError });\n\n/**\n * @class RoomNameCharsInvalidError\n * @classdesc Raised whenever a Room name contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53102\n * @property {string} message - 'Room name contains invalid characters'\n */\nclass RoomNameCharsInvalidError extends TwilioError {\n constructor() {\n super(53102, 'Room name contains invalid characters');\n Object.setPrototypeOf(this, RoomNameCharsInvalidError.prototype);\n }\n}\n\nexports.RoomNameCharsInvalidError = RoomNameCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53102, { value: RoomNameCharsInvalidError });\n\n/**\n * @class RoomCreateFailedError\n * @classdesc Raised whenever the Server is unable to create a Room.\n * @extends TwilioError\n * @property {number} code - 53103\n * @property {string} message - 'Unable to create Room'\n */\nclass RoomCreateFailedError extends TwilioError {\n constructor() {\n super(53103, 'Unable to create Room');\n Object.setPrototypeOf(this, RoomCreateFailedError.prototype);\n }\n}\n\nexports.RoomCreateFailedError = RoomCreateFailedError;\nObject.defineProperty(TwilioErrorByCode, 53103, { value: RoomCreateFailedError });\n\n/**\n * @class RoomConnectFailedError\n * @classdesc Raised whenever a Client is unable to connect to a Room, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53104\n * @property {string} message - 'Unable to connect to Room'\n */\nclass RoomConnectFailedError extends TwilioError {\n constructor() {\n super(53104, 'Unable to connect to Room');\n Object.setPrototypeOf(this, RoomConnectFailedError.prototype);\n }\n}\n\nexports.RoomConnectFailedError = RoomConnectFailedError;\nObject.defineProperty(TwilioErrorByCode, 53104, { value: RoomConnectFailedError });\n\n/**\n * @class RoomMaxParticipantsExceededError\n * @classdesc Raised whenever a Client is unable to connect to a Room because the Room contains too many Participants.\n * @extends TwilioError\n * @property {number} code - 53105\n * @property {string} message - 'Room contains too many Participants'\n */\nclass RoomMaxParticipantsExceededError extends TwilioError {\n constructor() {\n super(53105, 'Room contains too many Participants');\n Object.setPrototypeOf(this, RoomMaxParticipantsExceededError.prototype);\n }\n}\n\nexports.RoomMaxParticipantsExceededError = RoomMaxParticipantsExceededError;\nObject.defineProperty(TwilioErrorByCode, 53105, { value: RoomMaxParticipantsExceededError });\n\n/**\n * @class RoomNotFoundError\n * @classdesc Raised whenever attempting operation on a non-existent Room.\n * @extends TwilioError\n * @property {number} code - 53106\n * @property {string} message - 'Room not found'\n */\nclass RoomNotFoundError extends TwilioError {\n constructor() {\n super(53106, 'Room not found');\n Object.setPrototypeOf(this, RoomNotFoundError.prototype);\n }\n}\n\nexports.RoomNotFoundError = RoomNotFoundError;\nObject.defineProperty(TwilioErrorByCode, 53106, { value: RoomNotFoundError });\n\n/**\n * @class RoomMaxParticipantsOutOfRangeError\n * @classdesc Raised in the REST API when MaxParticipants is set out of range.\n * @extends TwilioError\n * @property {number} code - 53107\n * @property {string} message - 'MaxParticipants is out of range'\n */\nclass RoomMaxParticipantsOutOfRangeError extends TwilioError {\n constructor() {\n super(53107, 'MaxParticipants is out of range');\n Object.setPrototypeOf(this, RoomMaxParticipantsOutOfRangeError.prototype);\n }\n}\n\nexports.RoomMaxParticipantsOutOfRangeError = RoomMaxParticipantsOutOfRangeError;\nObject.defineProperty(TwilioErrorByCode, 53107, { value: RoomMaxParticipantsOutOfRangeError });\n\n/**\n * @class RoomTypeInvalidError\n * @classdesc Raised in the REST API when the user attempts to create a Room with an invalid RoomType\n * @extends TwilioError\n * @property {number} code - 53108\n * @property {string} message - 'RoomType is not valid'\n */\nclass RoomTypeInvalidError extends TwilioError {\n constructor() {\n super(53108, 'RoomType is not valid');\n Object.setPrototypeOf(this, RoomTypeInvalidError.prototype);\n }\n}\n\nexports.RoomTypeInvalidError = RoomTypeInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53108, { value: RoomTypeInvalidError });\n\n/**\n * @class RoomTimeoutOutOfRangeError\n * @classdesc Raised in the REST API when Timeout is set out of range.\n * @extends TwilioError\n * @property {number} code - 53109\n * @property {string} message - 'Timeout is out of range'\n */\nclass RoomTimeoutOutOfRangeError extends TwilioError {\n constructor() {\n super(53109, 'Timeout is out of range');\n Object.setPrototypeOf(this, RoomTimeoutOutOfRangeError.prototype);\n }\n}\n\nexports.RoomTimeoutOutOfRangeError = RoomTimeoutOutOfRangeError;\nObject.defineProperty(TwilioErrorByCode, 53109, { value: RoomTimeoutOutOfRangeError });\n\n/**\n * @class RoomStatusCallbackMethodInvalidError\n * @classdesc Raised in the REST API when StatusCallbackMethod is set to an invalid value.\n * @extends TwilioError\n * @property {number} code - 53110\n * @property {string} message - 'StatusCallbackMethod is invalid'\n */\nclass RoomStatusCallbackMethodInvalidError extends TwilioError {\n constructor() {\n super(53110, 'StatusCallbackMethod is invalid');\n Object.setPrototypeOf(this, RoomStatusCallbackMethodInvalidError.prototype);\n }\n}\n\nexports.RoomStatusCallbackMethodInvalidError = RoomStatusCallbackMethodInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53110, { value: RoomStatusCallbackMethodInvalidError });\n\n/**\n * @class RoomStatusCallbackInvalidError\n * @classdesc Raised in the REST API when StatusCallback is not a valid URL or the url is too long.\n * @extends TwilioError\n * @property {number} code - 53111\n * @property {string} message - 'StatusCallback is invalid'\n */\nclass RoomStatusCallbackInvalidError extends TwilioError {\n constructor() {\n super(53111, 'StatusCallback is invalid');\n Object.setPrototypeOf(this, RoomStatusCallbackInvalidError.prototype);\n }\n}\n\nexports.RoomStatusCallbackInvalidError = RoomStatusCallbackInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53111, { value: RoomStatusCallbackInvalidError });\n\n/**\n * @class RoomStatusInvalidError\n * @classdesc Raised in the REST API when Status is not valid or the Room is not in-progress.\n * @extends TwilioError\n * @property {number} code - 53112\n * @property {string} message - 'Status is invalid'\n */\nclass RoomStatusInvalidError extends TwilioError {\n constructor() {\n super(53112, 'Status is invalid');\n Object.setPrototypeOf(this, RoomStatusInvalidError.prototype);\n }\n}\n\nexports.RoomStatusInvalidError = RoomStatusInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53112, { value: RoomStatusInvalidError });\n\n/**\n * @class RoomRoomExistsError\n * @classdesc Raised in the REST API when the Room creation fails because a Room exists with the same name.\n * @extends TwilioError\n * @property {number} code - 53113\n * @property {string} message - 'Room exists'\n */\nclass RoomRoomExistsError extends TwilioError {\n constructor() {\n super(53113, 'Room exists');\n Object.setPrototypeOf(this, RoomRoomExistsError.prototype);\n }\n}\n\nexports.RoomRoomExistsError = RoomRoomExistsError;\nObject.defineProperty(TwilioErrorByCode, 53113, { value: RoomRoomExistsError });\n\n/**\n * @class RoomInvalidParametersError\n * @classdesc Raised in the REST API when one or more Room creation parameter is incompatible with the Room type.\n * @extends TwilioError\n * @property {number} code - 53114\n * @property {string} message - 'Room creation parameter(s) incompatible with the Room type'\n */\nclass RoomInvalidParametersError extends TwilioError {\n constructor() {\n super(53114, 'Room creation parameter(s) incompatible with the Room type');\n Object.setPrototypeOf(this, RoomInvalidParametersError.prototype);\n }\n}\n\nexports.RoomInvalidParametersError = RoomInvalidParametersError;\nObject.defineProperty(TwilioErrorByCode, 53114, { value: RoomInvalidParametersError });\n\n/**\n * @class RoomMediaRegionInvalidError\n * @classdesc Raised in the REST API when MediaRegion is set to an invalid value.\n * @extends TwilioError\n * @property {number} code - 53115\n * @property {string} message - 'MediaRegion is invalid'\n */\nclass RoomMediaRegionInvalidError extends TwilioError {\n constructor() {\n super(53115, 'MediaRegion is invalid');\n Object.setPrototypeOf(this, RoomMediaRegionInvalidError.prototype);\n }\n}\n\nexports.RoomMediaRegionInvalidError = RoomMediaRegionInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53115, { value: RoomMediaRegionInvalidError });\n\n/**\n * @class RoomMediaRegionUnavailableError\n * @classdesc Raised in the REST API when MediaRegion is set to a valid value but no media servers are available.\n * @extends TwilioError\n * @property {number} code - 53116\n * @property {string} message - 'There are no media servers available in the MediaRegion'\n */\nclass RoomMediaRegionUnavailableError extends TwilioError {\n constructor() {\n super(53116, 'There are no media servers available in the MediaRegion');\n Object.setPrototypeOf(this, RoomMediaRegionUnavailableError.prototype);\n }\n}\n\nexports.RoomMediaRegionUnavailableError = RoomMediaRegionUnavailableError;\nObject.defineProperty(TwilioErrorByCode, 53116, { value: RoomMediaRegionUnavailableError });\n\n/**\n * @class RoomSubscriptionOperationNotSupportedError\n * @classdesc Raised whenever the subscription operation requested is not supported for the Room type.\n * @extends TwilioError\n * @property {number} code - 53117\n * @property {string} message - 'The subscription operation requested is not supported for the Room type'\n */\nclass RoomSubscriptionOperationNotSupportedError extends TwilioError {\n constructor() {\n super(53117, 'The subscription operation requested is not supported for the Room type');\n Object.setPrototypeOf(this, RoomSubscriptionOperationNotSupportedError.prototype);\n }\n}\n\nexports.RoomSubscriptionOperationNotSupportedError = RoomSubscriptionOperationNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53117, { value: RoomSubscriptionOperationNotSupportedError });\n\n/**\n * @class RoomCompletedError\n * @classdesc Raised whenever a Room is completed via the REST API.\n * @extends TwilioError\n * @property {number} code - 53118\n * @property {string} message - 'Room completed'\n */\nclass RoomCompletedError extends TwilioError {\n constructor() {\n super(53118, 'Room completed');\n Object.setPrototypeOf(this, RoomCompletedError.prototype);\n }\n}\n\nexports.RoomCompletedError = RoomCompletedError;\nObject.defineProperty(TwilioErrorByCode, 53118, { value: RoomCompletedError });\n\n/**\n * @class RoomAudioOnlyFlagNotSupportedError\n * @classdesc Raised whenever a participant tries to set the AudioOnly flag for a Room type other than Group Rooms.\n * @extends TwilioError\n * @property {number} code - 53124\n * @property {string} message - 'The AudioOnly flag is not supported for the Room type'\n */\nclass RoomAudioOnlyFlagNotSupportedError extends TwilioError {\n constructor() {\n super(53124, 'The AudioOnly flag is not supported for the Room type');\n Object.setPrototypeOf(this, RoomAudioOnlyFlagNotSupportedError.prototype);\n }\n}\n\nexports.RoomAudioOnlyFlagNotSupportedError = RoomAudioOnlyFlagNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53124, { value: RoomAudioOnlyFlagNotSupportedError });\n\n/**\n * @class RoomTrackKindNotSupportedError\n * @classdesc Raised whenever a participant tries to publish a track or connects with a track that is not supported by the group room.\n * @extends TwilioError\n * @property {number} code - 53125\n * @property {string} message - 'The track kind is not supported by the Room'\n */\nclass RoomTrackKindNotSupportedError extends TwilioError {\n constructor() {\n super(53125, 'The track kind is not supported by the Room');\n Object.setPrototypeOf(this, RoomTrackKindNotSupportedError.prototype);\n }\n}\n\nexports.RoomTrackKindNotSupportedError = RoomTrackKindNotSupportedError;\nObject.defineProperty(TwilioErrorByCode, 53125, { value: RoomTrackKindNotSupportedError });\n\n/**\n * @class ParticipantIdentityInvalidError\n * @classdesc Raised whenever a Participant identity is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53200\n * @property {string} message - 'Participant identity is invalid'\n */\nclass ParticipantIdentityInvalidError extends TwilioError {\n constructor() {\n super(53200, 'Participant identity is invalid');\n Object.setPrototypeOf(this, ParticipantIdentityInvalidError.prototype);\n }\n}\n\nexports.ParticipantIdentityInvalidError = ParticipantIdentityInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53200, { value: ParticipantIdentityInvalidError });\n\n/**\n * @class ParticipantIdentityTooLongError\n * @classdesc Raised whenever a Participant identity is too long.\n * @extends TwilioError\n * @property {number} code - 53201\n * @property {string} message - 'Participant identity is too long'\n */\nclass ParticipantIdentityTooLongError extends TwilioError {\n constructor() {\n super(53201, 'Participant identity is too long');\n Object.setPrototypeOf(this, ParticipantIdentityTooLongError.prototype);\n }\n}\n\nexports.ParticipantIdentityTooLongError = ParticipantIdentityTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53201, { value: ParticipantIdentityTooLongError });\n\n/**\n * @class ParticipantIdentityCharsInvalidError\n * @classdesc Raised whenever a Participant identity contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53202\n * @property {string} message - 'Participant identity contains invalid characters'\n */\nclass ParticipantIdentityCharsInvalidError extends TwilioError {\n constructor() {\n super(53202, 'Participant identity contains invalid characters');\n Object.setPrototypeOf(this, ParticipantIdentityCharsInvalidError.prototype);\n }\n}\n\nexports.ParticipantIdentityCharsInvalidError = ParticipantIdentityCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53202, { value: ParticipantIdentityCharsInvalidError });\n\n/**\n * @class ParticipantMaxTracksExceededError\n * @classdesc Raised whenever a Participant tries to publish a Track and the maximum number of published tracks allowed in the Room at the same time has been reached\n * @extends TwilioError\n * @property {number} code - 53203\n * @property {string} message - 'The maximum number of published tracks allowed in the Room at the same time has been reached'\n */\nclass ParticipantMaxTracksExceededError extends TwilioError {\n constructor() {\n super(53203, 'The maximum number of published tracks allowed in the Room at the same time has been reached');\n Object.setPrototypeOf(this, ParticipantMaxTracksExceededError.prototype);\n }\n}\n\nexports.ParticipantMaxTracksExceededError = ParticipantMaxTracksExceededError;\nObject.defineProperty(TwilioErrorByCode, 53203, { value: ParticipantMaxTracksExceededError });\n\n/**\n * @class ParticipantNotFoundError\n * @classdesc Raised whenever attempting an operation on a non-existent Participant.\n * @extends TwilioError\n * @property {number} code - 53204\n * @property {string} message - 'Participant not found'\n */\nclass ParticipantNotFoundError extends TwilioError {\n constructor() {\n super(53204, 'Participant not found');\n Object.setPrototypeOf(this, ParticipantNotFoundError.prototype);\n }\n}\n\nexports.ParticipantNotFoundError = ParticipantNotFoundError;\nObject.defineProperty(TwilioErrorByCode, 53204, { value: ParticipantNotFoundError });\n\n/**\n * @class ParticipantDuplicateIdentityError\n * @classdesc Raised by the server to the existing Participant when a new Participant joins a Room with the same identity as the existing Participant.\n * @extends TwilioError\n * @property {number} code - 53205\n * @property {string} message - 'Participant disconnected because of duplicate identity'\n */\nclass ParticipantDuplicateIdentityError extends TwilioError {\n constructor() {\n super(53205, 'Participant disconnected because of duplicate identity');\n Object.setPrototypeOf(this, ParticipantDuplicateIdentityError.prototype);\n }\n}\n\nexports.ParticipantDuplicateIdentityError = ParticipantDuplicateIdentityError;\nObject.defineProperty(TwilioErrorByCode, 53205, { value: ParticipantDuplicateIdentityError });\n\n/**\n * @class TrackInvalidError\n * @classdesc Raised whenever a Track is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53300\n * @property {string} message - 'Track is invalid'\n */\nclass TrackInvalidError extends TwilioError {\n constructor() {\n super(53300, 'Track is invalid');\n Object.setPrototypeOf(this, TrackInvalidError.prototype);\n }\n}\n\nexports.TrackInvalidError = TrackInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53300, { value: TrackInvalidError });\n\n/**\n * @class TrackNameInvalidError\n * @classdesc Raised whenever a Track name is invalid, and the scenario is not covered by a more specific error code.\n * @extends TwilioError\n * @property {number} code - 53301\n * @property {string} message - 'Track name is invalid'\n */\nclass TrackNameInvalidError extends TwilioError {\n constructor() {\n super(53301, 'Track name is invalid');\n Object.setPrototypeOf(this, TrackNameInvalidError.prototype);\n }\n}\n\nexports.TrackNameInvalidError = TrackNameInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53301, { value: TrackNameInvalidError });\n\n/**\n * @class TrackNameTooLongError\n * @classdesc Raised whenever a Track name is too long.\n * @extends TwilioError\n * @property {number} code - 53302\n * @property {string} message - 'Track name is too long'\n */\nclass TrackNameTooLongError extends TwilioError {\n constructor() {\n super(53302, 'Track name is too long');\n Object.setPrototypeOf(this, TrackNameTooLongError.prototype);\n }\n}\n\nexports.TrackNameTooLongError = TrackNameTooLongError;\nObject.defineProperty(TwilioErrorByCode, 53302, { value: TrackNameTooLongError });\n\n/**\n * @class TrackNameCharsInvalidError\n * @classdesc Raised whenever a Track name contains invalid characters.\n * @extends TwilioError\n * @property {number} code - 53303\n * @property {string} message - 'Track name contains invalid characters'\n */\nclass TrackNameCharsInvalidError extends TwilioError {\n constructor() {\n super(53303, 'Track name contains invalid characters');\n Object.setPrototypeOf(this, TrackNameCharsInvalidError.prototype);\n }\n}\n\nexports.TrackNameCharsInvalidError = TrackNameCharsInvalidError;\nObject.defineProperty(TwilioErrorByCode, 53303, { value: TrackNameCharsInvalidError });\n\n/**\n * @class TrackNameIsDuplicatedError\n * @classdesc Raised whenever a Participant is currently publishing a Track with the same name.\n * @extends TwilioError\n * @property {number} code - 53304\n * @property {string} message - 'Track name is duplicated'\n */\nclass TrackNameIsDuplicatedError extends TwilioError {\n constructor() {\n super(53304, 'Track name is duplicated');\n Object.setPrototypeOf(this, TrackNameIsDuplicatedError.prototype);\n }\n}\n\nexports.TrackNameIsDuplicatedError = TrackNameIsDuplicatedError;\nObject.defineProperty(TwilioErrorByCode, 53304, { value: TrackNameIsDuplicatedError });\n\n/**\n * @class TrackServerTrackCapacityReachedError\n * @classdesc The server does not have enough resources available to create a new Track.\n * @extends TwilioError\n * @property {number} code - 53305\n * @property {string} message - 'The server has reached capacity and cannot fulfill this request'\n */\nclass TrackServerTrackCapacityReachedError extends TwilioError {\n constructor() {\n super(53305, 'The server has reached capacity and cannot fulfill this request');\n Object.setPrototypeOf(this, TrackServerTrackCapacityReachedError.prototype);\n }\n}\n\nexports.TrackServerTrackCapacityReachedError = TrackServerTrackCapacityReachedError;\nObject.defineProperty(TwilioErrorByCode, 53305, { value: TrackServerTrackCapacityReachedError });\n\n/**\n * @class MediaClientLocalDescFailedError\n * @classdesc Raised whenever a Client is unable to create or apply a local media description.\n * @extends TwilioError\n * @property {number} code - 53400\n * @property {string} message - 'Client is unable to create or apply a local media description'\n */\nclass MediaClientLocalDescFailedError extends TwilioError {\n constructor() {\n super(53400, 'Client is unable to create or apply a local media description');\n Object.setPrototypeOf(this, MediaClientLocalDescFailedError.prototype);\n }\n}\n\nexports.MediaClientLocalDescFailedError = MediaClientLocalDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53400, { value: MediaClientLocalDescFailedError });\n\n/**\n * @class MediaServerLocalDescFailedError\n * @classdesc Raised whenever the Server is unable to create or apply a local media description.\n * @extends TwilioError\n * @property {number} code - 53401\n * @property {string} message - 'Server is unable to create or apply a local media description'\n */\nclass MediaServerLocalDescFailedError extends TwilioError {\n constructor() {\n super(53401, 'Server is unable to create or apply a local media description');\n Object.setPrototypeOf(this, MediaServerLocalDescFailedError.prototype);\n }\n}\n\nexports.MediaServerLocalDescFailedError = MediaServerLocalDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53401, { value: MediaServerLocalDescFailedError });\n\n/**\n * @class MediaClientRemoteDescFailedError\n * @classdesc Raised whenever the Client receives a remote media description but is unable to apply it.\n * @extends TwilioError\n * @property {number} code - 53402\n * @property {string} message - 'Client is unable to apply a remote media description'\n */\nclass MediaClientRemoteDescFailedError extends TwilioError {\n constructor() {\n super(53402, 'Client is unable to apply a remote media description');\n Object.setPrototypeOf(this, MediaClientRemoteDescFailedError.prototype);\n }\n}\n\nexports.MediaClientRemoteDescFailedError = MediaClientRemoteDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53402, { value: MediaClientRemoteDescFailedError });\n\n/**\n * @class MediaServerRemoteDescFailedError\n * @classdesc Raised whenever the Server receives a remote media description but is unable to apply it.\n * @extends TwilioError\n * @property {number} code - 53403\n * @property {string} message - 'Server is unable to apply a remote media description'\n */\nclass MediaServerRemoteDescFailedError extends TwilioError {\n constructor() {\n super(53403, 'Server is unable to apply a remote media description');\n Object.setPrototypeOf(this, MediaServerRemoteDescFailedError.prototype);\n }\n}\n\nexports.MediaServerRemoteDescFailedError = MediaServerRemoteDescFailedError;\nObject.defineProperty(TwilioErrorByCode, 53403, { value: MediaServerRemoteDescFailedError });\n\n/**\n * @class MediaNoSupportedCodecError\n * @classdesc Raised whenever the intersection of codecs supported by the Client and the Server (or, in peer-to-peer, the Client and another Participant) is empty.\n * @extends TwilioError\n * @property {number} code - 53404\n * @property {string} message - 'No supported codec'\n */\nclass MediaNoSupportedCodecError extends TwilioError {\n constructor() {\n super(53404, 'No supported codec');\n Object.setPrototypeOf(this, MediaNoSupportedCodecError.prototype);\n }\n}\n\nexports.MediaNoSupportedCodecError = MediaNoSupportedCodecError;\nObject.defineProperty(TwilioErrorByCode, 53404, { value: MediaNoSupportedCodecError });\n\n/**\n * @class MediaConnectionError\n * @classdesc Raised by the Client or Server whenever a media connection fails or raised by the Client whenever it detects that media has stopped flowing.\n * @extends TwilioError\n * @property {number} code - 53405\n * @property {string} message - 'Media connection failed or Media activity ceased'\n */\nclass MediaConnectionError extends TwilioError {\n constructor() {\n super(53405, 'Media connection failed or Media activity ceased');\n Object.setPrototypeOf(this, MediaConnectionError.prototype);\n }\n}\n\nexports.MediaConnectionError = MediaConnectionError;\nObject.defineProperty(TwilioErrorByCode, 53405, { value: MediaConnectionError });\n\n/**\n * @class MediaDTLSTransportFailedError\n * @classdesc There was a problem while negotiating with the remote DTLS peer. Therefore the Participant will not be able to publish or subscribe to Tracks.\n * @extends TwilioError\n * @property {number} code - 53407\n * @property {string} message - 'Media connection failed due to DTLS handshake failure'\n */\nclass MediaDTLSTransportFailedError extends TwilioError {\n constructor() {\n super(53407, 'Media connection failed due to DTLS handshake failure');\n Object.setPrototypeOf(this, MediaDTLSTransportFailedError.prototype);\n }\n}\n\nexports.MediaDTLSTransportFailedError = MediaDTLSTransportFailedError;\nObject.defineProperty(TwilioErrorByCode, 53407, { value: MediaDTLSTransportFailedError });\n\n/**\n * @class ConfigurationAcquireFailedError\n * @classdesc Raised whenever the Client is unable to acquire configuration information from the Server.\n * @extends TwilioError\n * @property {number} code - 53500\n * @property {string} message - 'Unable to acquire configuration'\n */\nclass ConfigurationAcquireFailedError extends TwilioError {\n constructor() {\n super(53500, 'Unable to acquire configuration');\n Object.setPrototypeOf(this, ConfigurationAcquireFailedError.prototype);\n }\n}\n\nexports.ConfigurationAcquireFailedError = ConfigurationAcquireFailedError;\nObject.defineProperty(TwilioErrorByCode, 53500, { value: ConfigurationAcquireFailedError });\n\n/**\n * @class ConfigurationAcquireTurnFailedError\n * @classdesc Raised whenever the Server is unable to return TURN credentials to the Client\n * @extends TwilioError\n * @property {number} code - 53501\n * @property {string} message - 'Unable to acquire TURN credentials'\n */\nclass ConfigurationAcquireTurnFailedError extends TwilioError {\n constructor() {\n super(53501, 'Unable to acquire TURN credentials');\n Object.setPrototypeOf(this, ConfigurationAcquireTurnFailedError.prototype);\n }\n}\n\nexports.ConfigurationAcquireTurnFailedError = ConfigurationAcquireTurnFailedError;\nObject.defineProperty(TwilioErrorByCode, 53501, { value: ConfigurationAcquireTurnFailedError });\n", "/* eslint-disable camelcase */\nconst TwilioConnection = require('../twilioconnection.js');\nconst { ICE_VERSION } = require('../util/constants');\nconst { createTwilioError, SignalingConnectionError } = require('../util/twilio-video-errors');\n\nimport { RTCIceServer, RTCStats } from './rtctypes';\nimport { EventEmitter } from 'events';\n\n\nexport { RTCStats, RTCIceServer };\nexport function getTurnCredentials(token: string, wsServer: string): Promise {\n return new Promise((resolve, reject) => {\n const eventObserver = new EventEmitter();\n const connectionOptions = {\n networkMonitor: null,\n eventObserver,\n helloBody: {\n edge: 'roaming', // roaming here means use same edge as signaling.\n preflight: true,\n token: token,\n type: 'ice',\n version: ICE_VERSION\n },\n };\n\n const twilioConnection = new TwilioConnection(wsServer, connectionOptions);\n let done = false;\n twilioConnection.once('close', () => {\n if (!done) {\n done = true;\n reject(new SignalingConnectionError());\n }\n });\n\n twilioConnection.on('message', (messageData: {\n code: number;\n message: string;\n ice_servers: RTCIceServer[];\n type: string;\n }) => {\n const { code, message, ice_servers, type } = messageData;\n if ((type === 'iced' || type === 'error') && !done) {\n done = true;\n if (type === 'iced') {\n resolve(ice_servers);\n } else {\n reject(createTwilioError(code, message));\n }\n twilioConnection.close();\n }\n });\n });\n}\n\n", "\nimport type { Stats } from '../../tsdef/PreflightTypes';\n\n/**\n * Computes min, max, average for given array.\n * @param {Array} values\n * @returns {{min: number, max: number: average: number}|null}\n */\nexport function makeStat(values?: number[]|null) : Stats|null {\n if (values && values.length) {\n const min = Math.min(...values);\n const max = Math.max(...values);\n const average = values.reduce((total, value) => total + value, 0) / values.length;\n return { min, max, average };\n }\n return null;\n}\n", "interface MediaStreamAudioDestinationNode extends AudioNode {\n stream: MediaStream;\n}\n\nexport function syntheticAudio(): MediaStreamTrack {\n // NOTE(mpatwardhan): We have to delay require-ing AudioContextFactory, because\n // it exports a default instance whose constructor calls Object.assign.\n const audioContextFactory = require('../webaudio/audiocontext');\n const holder = {};\n const audioContext = audioContextFactory.getOrCreate(holder);\n const oscillator = audioContext.createOscillator();\n const dst = oscillator.connect(audioContext.createMediaStreamDestination()) as MediaStreamAudioDestinationNode;\n oscillator.start();\n const track = dst.stream.getAudioTracks()[0];\n const originalStop = track.stop;\n track.stop = () => {\n originalStop.call(track);\n audioContextFactory.release(holder);\n };\n return track;\n}\n", "interface CanvasElement extends HTMLCanvasElement {\n captureStream(frameRate?: number): MediaStream;\n}\n\nexport function syntheticVideo({ width = 640, height = 480 } = {}): MediaStreamTrack {\n const canvas = Object.assign(\n document.createElement('canvas'), { width, height }\n ) as CanvasElement;\n\n let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;\n ctx.fillStyle = 'green';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n let stopped = false;\n requestAnimationFrame(function animate() {\n if (!stopped) {\n // draw random rect/circle.\n const r = Math.round(Math.random() * 255);\n const g = Math.round(Math.random() * 255);\n const b = Math.round(Math.random() * 255);\n const a = Math.round(Math.random() * 255);\n ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a})`;\n ctx.fillRect(Math.random() * width, Math.random() * height, 50, 50);\n requestAnimationFrame(animate);\n }\n });\n const stream = canvas.captureStream(30);\n const track = stream.getTracks()[0];\n const originalStop = track.stop;\n track.stop = () => {\n stopped = true;\n originalStop.call(track);\n };\n\n return track;\n}\n\n", "'use strict';\n\n/**\n * Calculates the moving average delta for the given pair ofsamples. A sample (S)\n * consists of a numerator (Sn) and a denominator (Sd).The moving average delta is\n * calculated as follows:\n *\n * MovingAvgDelta = (Sn[1] - Sn[0]) / (Sd[1] - Sd[0])\n */\nclass MovingAverageDelta {\n /**\n * Constructor.\n */\n constructor() {\n Object.defineProperties(this, {\n _samples: {\n value: [\n { denominator: 0, numerator: 0 },\n { denominator: 0, numerator: 0 }\n ],\n }\n });\n }\n\n /**\n * Get the moving average delta.\n * @returns {number}\n */\n get() {\n const { _samples: samples } = this;\n const denominatorDelta = (samples[1].denominator - samples[0].denominator) || Infinity;\n const numeratorDelta = samples[1].numerator - samples[0].numerator;\n return numeratorDelta / denominatorDelta;\n }\n\n /**\n * Put a sample and get rid of the older sample to maintain sample size of 2.\n * @param numerator\n * @param denominator\n */\n putSample(numerator, denominator) {\n const { _samples: samples } = this;\n samples.shift();\n samples.push({ denominator, numerator });\n }\n}\n\nmodule.exports = MovingAverageDelta;\n", "/* eslint-disable no-console */\n'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst VALID_GROUPS = [\n 'signaling',\n 'room',\n 'media',\n 'quality',\n 'video-processor',\n 'preflight'\n];\n\nconst VALID_LEVELS = [\n 'debug',\n 'error',\n 'info',\n 'warning'\n];\n\n/**\n * EventObserver listens to SDK events and re-emits them on the\n * @link EventListener} with some additional information.\n * @extends EventEmitter\n * @emits EventObserver#event\n */\nclass EventObserver extends EventEmitter {\n /**\n * Constructor.\n * @param {InsightsPublisher} publisher\n * @param {number} connectTimestamp\n * @param {Log} log\n * @param {EventListener} [eventListener]\n */\n constructor(publisher, connectTimestamp, log, eventListener = null) {\n super();\n this.on('event', ({ name, group, level, payload }) => {\n if (typeof name !== 'string') {\n log.error('Unexpected name: ', name);\n throw new Error('Unexpected name: ', name);\n }\n\n if (!VALID_GROUPS.includes(group)) {\n log.error('Unexpected group: ', group);\n throw new Error('Unexpected group: ', group);\n }\n\n if (!VALID_LEVELS.includes(level)) {\n log.error('Unexpected level: ', level);\n throw new Error('Unexpected level: ', level);\n }\n\n const timestamp = Date.now();\n const elapsedTime = timestamp - connectTimestamp;\n\n const publisherPayload = Object.assign({ elapsedTime, level }, payload ? payload : {});\n publisher.publish(group, name, publisherPayload);\n\n const event = Object.assign({\n elapsedTime,\n group,\n level,\n name,\n timestamp\n }, payload ? { payload } : {});\n\n const logLevel = {\n debug: 'debug',\n error: 'error',\n info: 'info',\n warning: 'warn',\n }[level];\n log[logLevel]('event', event);\n\n if (eventListener && group === 'signaling') {\n eventListener.emit('event', event);\n }\n });\n }\n}\n\n/**\n * An SDK event.\n * @event EventObserver#event\n * @param {{name: string, payload: *}} event\n */\n\nmodule.exports = EventObserver;\n", "/* eslint-disable camelcase */\n'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\nconst { getUserAgent } = require('..');\n\nconst MAX_RECONNECT_ATTEMPTS = 5;\nconst RECONNECT_INTERVAL_MS = 50;\nconst WS_CLOSE_NORMAL = 1000;\n\nconst toplevel = globalThis;\nconst WebSocket = toplevel.WebSocket ? toplevel.WebSocket : require('ws');\nconst { hardwareDevicePublisheriPad, hardwareDevicePublisheriPhone } = require('../constants');\nconst util = require('../../util');\nconst browserdetection = require('../browserdetection');\n\n/**\n * Publish events to the Insights gateway.\n * @extends EventEmitter\n * @emits InsightsPublisher#connected\n * @emits InsightsPublisher#disconnected\n * @emits InsightsPublisher#reconnecting\n */\nclass InsightsPublisher extends EventEmitter {\n /**\n * @param {string} token - Insights gateway token\n * @param {string} sdkName - Name of the SDK using the {@link InsightsPublisher}\n * @param {string} sdkVersion - Version of the SDK using the {@link InsightsPublisher}\n * @param {string} environment - One of 'dev', 'stage' or 'prod'\n * @param {string} realm - Region identifier\n * @param {InsightsPublisherOptions} options - Override default behavior\n */\n constructor(token, sdkName, sdkVersion, environment, realm, options) {\n super();\n\n options = Object.assign({\n gateway: `${createGateway(environment, realm)}/v1/VideoEvents`,\n maxReconnectAttempts: MAX_RECONNECT_ATTEMPTS,\n reconnectIntervalMs: RECONNECT_INTERVAL_MS,\n userAgent: getUserAgent(),\n WebSocket,\n }, options);\n\n Object.defineProperties(this, {\n _connectTimestamp: {\n value: 0,\n writable: true\n },\n _eventQueue: {\n value: []\n },\n _readyToConnect: {\n value: util.defer()\n },\n _reconnectAttemptsLeft: {\n value: options.maxReconnectAttempts,\n writable: true\n },\n _ws: {\n value: null,\n writable: true\n },\n _WebSocket: {\n value: options.WebSocket\n }\n });\n\n this._readyToConnect.promise.then(({ roomSid, participantSid }) => {\n const self = this;\n this.on('disconnected', function maybeReconnect(error) {\n self._session = null;\n if (error && self._reconnectAttemptsLeft > 0) {\n self.emit('reconnecting');\n reconnect(self, token, sdkName, sdkVersion, roomSid, participantSid, options);\n return;\n }\n self.removeListener('disconnected', maybeReconnect);\n });\n connect(this, token, sdkName, sdkVersion, roomSid, participantSid, options);\n }).catch(() => {\n // ignore failures to connect\n });\n }\n\n /**\n * Start connecting to the Insights gateway.\n * @param {string} roomSid\n * @param {string} participantSid\n * @returns {void}\n */\n connect(roomSid, participantSid) {\n this._readyToConnect.resolve({ roomSid, participantSid });\n }\n\n /**\n * Publish an event to the Insights gateway.\n * @private\n * @param {*} event\n */\n _publish(event) {\n event.session = this._session;\n this._ws.send(JSON.stringify(event));\n }\n\n /**\n * Disconnect from the Insights gateway.\n * @returns {boolean} true if called when connecting/open, false if not\n */\n disconnect() {\n if (this._ws === null\n || this._ws.readyState === this._WebSocket.CLOSING\n || this._ws.readyState === this._WebSocket.CLOSED) {\n return false;\n }\n\n try {\n this._ws.close();\n } catch (error) {\n // Do nothing.\n }\n this.emit('disconnected');\n\n return true;\n }\n\n /**\n * Publish (or queue, if not connected) an event to the Insights gateway.\n * @param {string} groupName - Event group name\n * @param {string} eventName - Event name\n * @param {object} payload - Event payload\n * @returns {boolean} true if queued or published, false if disconnect() called\n */\n publish(groupName, eventName, payload) {\n if (this._ws !== null\n && (this._ws.readyState === this._WebSocket.CLOSING\n || this._ws.readyState === this._WebSocket.CLOSED)) {\n return false;\n }\n\n const publishOrEnqueue = typeof this._session === 'string'\n ? this._publish.bind(this)\n : this._eventQueue.push.bind(this._eventQueue);\n\n publishOrEnqueue({\n group: groupName,\n name: eventName,\n payload,\n timestamp: Date.now(),\n type: 'event',\n version: 1\n });\n\n return true;\n }\n}\n\n/**\n * Start connecting to the Insights gateway.\n * @private\n * @param {InsightsPublisher} publisher\n * @param {string} name\n * @param {string} token\n * @param {string} sdkName\n * @param {string} sdkVersion\n * @param {string} roomSid\n * @param {string} participantSid\n * @param {InsightsPublisherOptions} options\n */\nfunction connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options) {\n publisher._connectTimestamp = Date.now();\n publisher._reconnectAttemptsLeft--;\n publisher._ws = new options.WebSocket(options.gateway);\n const ws = publisher._ws;\n\n ws.addEventListener('close', event => {\n if (event.code === WS_CLOSE_NORMAL) {\n publisher.emit('disconnected');\n return;\n }\n publisher.emit('disconnected', new Error(`WebSocket Error ${event.code}: ${event.reason}`));\n });\n\n ws.addEventListener('message', message => {\n handleConnectResponse(publisher, JSON.parse(message.data), options);\n });\n\n ws.addEventListener('open', () => {\n const connectRequest = {\n type: 'connect',\n token,\n version: 1\n };\n\n connectRequest.publisher = {\n name: sdkName,\n sdkVersion,\n userAgent: options.userAgent,\n participantSid: participantSid,\n roomSid: roomSid,\n };\n\n if (browserdetection.isIpad()) {\n connectRequest.publisher = { ...connectRequest.publisher, ...hardwareDevicePublisheriPad };\n } else if (browserdetection.isIphone()) {\n connectRequest.publisher = { ...connectRequest.publisher, ...hardwareDevicePublisheriPhone };\n }\n\n ws.send(JSON.stringify(connectRequest));\n });\n}\n\n/**\n * Create the Insights Websocket gateway URL.\n * @param {string} environment\n * @param {string} realm\n * @returns {string}\n */\nfunction createGateway(environment, realm) {\n return environment === 'prod' ? `wss://sdkgw.${realm}.twilio.com`\n : `wss://sdkgw.${environment}-${realm}.twilio.com`;\n}\n\n/**\n * Handle connect response from the Insights gateway.\n * @param {InsightsPublisher} publisher\n * @param {*} response\n * @param {InsightsPublisherOptions} options\n */\nfunction handleConnectResponse(publisher, response, options) {\n switch (response.type) {\n case 'connected':\n publisher._session = response.session;\n publisher._reconnectAttemptsLeft = options.maxReconnectAttempts;\n publisher._eventQueue.splice(0).forEach(publisher._publish, publisher);\n publisher.emit('connected');\n break;\n case 'error':\n publisher._ws.close();\n publisher.emit('disconnected', new Error(response.message));\n break;\n }\n}\n\n/**\n * Start re-connecting to the Insights gateway with an appropriate delay based\n * on InsightsPublisherOptions#reconnectIntervalMs.\n * @private\n * @param {InsightsPublisher} publisher\n * @param {string} token\n * @param {string} sdkName\n * @param {string} sdkVersion\n * @param {string} roomSid\n * @param {string} participantSid\n * @param {InsightsPublisherOptions} options\n */\nfunction reconnect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options) {\n const connectInterval = Date.now() - publisher._connectTimestamp;\n const timeToWait = options.reconnectIntervalMs - connectInterval;\n\n if (timeToWait > 0) {\n setTimeout(() => {\n connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options);\n }, timeToWait);\n return;\n }\n\n connect(publisher, token, sdkName, sdkVersion, roomSid, participantSid, options);\n}\n\n/**\n * The {@link InsightsPublisher} is connected to the gateway.\n * @event InsightsPublisher#connected\n */\n\n/**\n * The {@link InsightsPublisher} is disconnected from the gateway.\n * @event InsightsPublisher#disconnected\n * @param {Error} [error] - Optional error if disconnected unintentionally\n */\n\n/**\n * The {@link InsightsPublisher} is re-connecting to the gateway.\n * @event InsightsPublisher#reconnecting\n */\n\n/**\n * {@link InsightsPublisher} options.\n * @typedef {object} InsightsPublisherOptions\n * @property {string} [gateway=sdkgw.{environment}-{realm}.twilio.com] - Insights WebSocket gateway url\n * @property {number} [maxReconnectAttempts=5] - Max re-connect attempts\n * @property {number} [reconnectIntervalMs=50] - Re-connect interval in ms\n */\n\nmodule.exports = InsightsPublisher;\n", "import { DEFAULT_ENVIRONMENT, DEFAULT_LOGGER_NAME, DEFAULT_LOG_LEVEL, DEFAULT_REALM, SDK_NAME, SDK_VERSION } from '../util/constants';\nimport { PreflightOptions, PreflightTestReport, ProgressEvent, RTCIceCandidateStats, SelectedIceCandidatePairStats, Stats } from '../../tsdef/PreflightTypes';\nimport { StatsReport } from '../../tsdef/types';\nimport { Timer } from './timer';\nimport { TwilioError } from '../../tsdef/TwilioError';\nimport { calculateMOS } from './mos';\nimport { getCombinedConnectionStats } from './getCombinedConnectionStats';\nimport { getTurnCredentials } from './getturncredentials';\nimport { makeStat } from './makestat';\nimport { syntheticAudio } from './syntheticaudio';\nimport { syntheticVideo } from './syntheticvideo';\nimport { waitForSometime } from '../util/index';\n\nconst { WS_SERVER } = require('../util/constants');\nconst Log = require('../util/log');\nconst EventEmitter = require('../eventemitter');\nconst MovingAverageDelta = require('../util/movingaveragedelta');\nconst EventObserver = require('../util/eventobserver');\nconst InsightsPublisher = require('../util/insightspublisher');\nconst { createSID, sessionSID } = require('../util/sid');\nconst {\n SignalingConnectionTimeoutError,\n MediaConnectionError\n} = require('../util/twilio-video-errors');\n\nconst SECOND = 1000;\nconst DEFAULT_TEST_DURATION = 10 * SECOND;\n\n/**\n * progress values that are sent by {@link PreflightTest#event:progress}\n * @enum {string}\n */\nconst PreflightProgress = {\n /**\n * {@link PreflightTest} has successfully generated synthetic tracks\n */\n mediaAcquired: 'mediaAcquired',\n\n /**\n * {@link PreflightTest} has successfully connected to twilio server and obtained turn credentials\n */\n connected: 'connected',\n\n /**\n * SubscriberParticipant successfully subscribed to media tracks.\n */\n mediaSubscribed: 'mediaSubscribed',\n\n /**\n * Media flow was detected.\n */\n mediaStarted: 'mediaStarted',\n\n /**\n * Established DTLS connection. This is measured from RTCDtlsTransport `connecting` to `connected` state.\n * On Safari, Support for measuring this is missing, this event will be not be emitted on Safari.\n */\n dtlsConnected: 'dtlsConnected',\n\n /**\n * Established a PeerConnection, This is measured from PeerConnection `connecting` to `connected` state.\n * On Firefox, Support for measuring this is missing, this event will be not be emitted on Firefox.\n */\n peerConnectionConnected: 'peerConnectionConnected',\n\n /**\n * Established ICE connection. This is measured from ICE connection `checking` to `connected` state.\n */\n iceConnected: 'iceConnected'\n};\n\ndeclare interface PreflightStats {\n jitter: number[],\n rtt: number[],\n outgoingBitrate: number[],\n incomingBitrate: number[],\n packetLoss: number[], // fraction of packets lost.\n mos: number[],\n selectedIceCandidatePairStats: SelectedIceCandidatePairStats | null,\n iceCandidateStats: RTCIceCandidateStats[],\n}\n\ndeclare interface PreflightTestReportInternal extends PreflightTestReport {\n error?: string,\n mos?: Stats|null\n}\n\ndeclare interface PreflightOptionsInternal extends PreflightOptions {\n environment?: string;\n wsServer?: string;\n}\n\nfunction notEmpty(value: TValue | null | undefined): value is TValue {\n return value !== null && typeof value !== 'undefined';\n}\n\nlet nInstances = 0;\n\n/**\n * A {@link PreflightTest} monitors progress of an ongoing preflight test.\n *

\n * Instance of {@link PreflightTest} is returned by calling {@link module:twilio-video.runPreflight}\n * @extends EventEmitter\n * @emits PreflightTest#completed\n * @emits PreflightTest#failed\n * @emits PreflightTest#progress\n */\nexport class PreflightTest extends EventEmitter {\n\n private _testTiming = new Timer();\n private _dtlsTiming = new Timer();\n private _iceTiming = new Timer();\n private _peerConnectionTiming = new Timer();\n private _mediaTiming = new Timer();\n private _connectTiming = new Timer();\n private _sentBytesMovingAverage = new MovingAverageDelta();\n private _packetLossMovingAverage = new MovingAverageDelta();\n private _progressEvents: ProgressEvent[] = [];\n private _receivedBytesMovingAverage = new MovingAverageDelta();\n private _log: typeof Log;\n private _testDuration: number;\n private _instanceId: number;\n\n /**\n * Constructs {@link PreflightTest}.\n * @param {string} token\n * @param {?PreflightOptions} [options]\n */\n constructor(token: string, options: PreflightOptions) {\n super();\n const internalOptions = options as PreflightOptionsInternal;\n const { environment = 'prod', region = 'gll', duration = DEFAULT_TEST_DURATION } = internalOptions;\n // eslint-disable-next-line new-cap\n const wsServer = internalOptions.wsServer || WS_SERVER(environment, region);\n\n this._log = new Log('default', this, DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME);\n this._testDuration = duration;\n this._instanceId = nInstances++;\n this._testTiming.start();\n this._runPreflightTest(token, environment, wsServer);\n }\n\n toString(): string {\n return `[Preflight #${this._instanceId}]`;\n }\n\n /**\n * stops ongoing tests and emits error\n */\n stop():void {\n this._stopped = true;\n }\n\n private _generatePreflightReport(collectedStats?: PreflightStats) : PreflightTestReportInternal {\n this._testTiming.stop();\n return {\n testTiming: this._testTiming.getTimeMeasurement(),\n networkTiming: {\n dtls: this._dtlsTiming.getTimeMeasurement(),\n ice: this._iceTiming.getTimeMeasurement(),\n peerConnection: this._peerConnectionTiming.getTimeMeasurement(),\n connect: this._connectTiming.getTimeMeasurement(),\n media: this._mediaTiming.getTimeMeasurement()\n },\n stats: {\n jitter: makeStat(collectedStats?.jitter),\n rtt: makeStat(collectedStats?.rtt),\n packetLoss: makeStat(collectedStats?.packetLoss),\n },\n selectedIceCandidatePairStats: collectedStats ? collectedStats.selectedIceCandidatePairStats : null,\n iceCandidateStats: collectedStats ? collectedStats.iceCandidateStats : [],\n progressEvents: this._progressEvents,\n // NOTE(mpatwardhan): internal properties.\n mos: makeStat(collectedStats?.mos),\n };\n }\n\n private async _executePreflightStep(stepName: string, step: () => T|Promise, timeoutError?: TwilioError|Error) : Promise {\n this._log.debug('Executing step: ', stepName);\n const MAX_STEP_DURATION = this._testDuration + 10 * SECOND;\n if (this._stopped) {\n throw new Error('stopped');\n }\n\n const stepPromise = Promise.resolve().then(step);\n let timer: number | null = null;\n const timeoutPromise = new Promise((_resolve, reject) => {\n timer = setTimeout(() => {\n reject(timeoutError || new Error(`${stepName} timeout.`));\n }, MAX_STEP_DURATION) as unknown as number;\n });\n try {\n const result = await Promise.race([timeoutPromise, stepPromise]);\n return result as T;\n } finally {\n if (timer !== null) {\n clearTimeout(timer);\n }\n }\n }\n\n private _collectNetworkTimings(pc: RTCPeerConnection): Promise {\n return new Promise(resolve => {\n let dtlsTransport: RTCDtlsTransport;\n\n pc.addEventListener('iceconnectionstatechange', () => {\n if (pc.iceConnectionState === 'checking') {\n this._iceTiming.start();\n }\n if (pc.iceConnectionState === 'connected') {\n this._iceTiming.stop();\n this._updateProgress(PreflightProgress.iceConnected);\n if (!dtlsTransport || dtlsTransport && dtlsTransport.state === 'connected') {\n resolve();\n }\n }\n });\n\n // firefox does not support connectionstatechange.\n pc.addEventListener('connectionstatechange', () => {\n if (pc.connectionState === 'connecting') {\n this._peerConnectionTiming.start();\n }\n if (pc.connectionState === 'connected') {\n this._peerConnectionTiming.stop();\n this._updateProgress(PreflightProgress.peerConnectionConnected);\n }\n });\n\n // Safari does not expose sender.transport.\n let senders = pc.getSenders();\n let transport = senders.map(sender => sender.transport).find(notEmpty);\n if (typeof transport !== 'undefined') {\n dtlsTransport = transport as RTCDtlsTransport;\n dtlsTransport.addEventListener('statechange', () => {\n if (dtlsTransport.state === 'connecting') {\n this._dtlsTiming.start();\n }\n if (dtlsTransport.state === 'connected') {\n this._dtlsTiming.stop();\n this._updateProgress(PreflightProgress.dtlsConnected);\n if (pc.iceConnectionState === 'connected') {\n resolve();\n }\n }\n });\n }\n });\n }\n\n private _setupInsights({ token, environment = DEFAULT_ENVIRONMENT, realm = DEFAULT_REALM } : {\n token: string,\n environment?: string,\n realm?: string\n }) {\n const eventPublisherOptions = {};\n const eventPublisher = new InsightsPublisher(\n token,\n SDK_NAME,\n SDK_VERSION,\n environment,\n realm,\n eventPublisherOptions);\n\n // event publisher requires room sid/participant sid. supply fake ones.\n eventPublisher.connect('PREFLIGHT_ROOM_SID', 'PREFLIGHT_PARTICIPANT');\n const eventObserver = new EventObserver(eventPublisher, Date.now(), this._log);\n\n // eslint-disable-next-line no-undefined\n const undefinedValue = undefined;\n return {\n reportToInsights: ({ report }: { report: PreflightTestReportInternal }) => {\n const jitterStats = report.stats.jitter || undefinedValue;\n const rttStats = report.stats.rtt || undefinedValue;\n const packetLossStats = report.stats.packetLoss || undefinedValue;\n const mosStats = report.mos || undefinedValue;\n\n // stringify important info from ice candidates.\n const candidateTypeToProtocols = new Map();\n report.iceCandidateStats.forEach(candidateStats => {\n if (candidateStats.candidateType && candidateStats.protocol) {\n let protocols = candidateTypeToProtocols.get(candidateStats.candidateType) || [];\n if (protocols.indexOf(candidateStats.protocol) < 0) {\n protocols.push(candidateStats.protocol);\n }\n candidateTypeToProtocols.set(candidateStats.candidateType, protocols);\n }\n });\n const iceCandidateStats = JSON.stringify(Object.fromEntries(candidateTypeToProtocols));\n\n const insightsReport = {\n name: 'report',\n group: 'preflight',\n level: report.error ? 'error' : 'info',\n payload: {\n sessionSID,\n preflightSID: createSID('PF'),\n progressEvents: JSON.stringify(report.progressEvents),\n testTiming: report.testTiming,\n dtlsTiming: report.networkTiming.dtls,\n iceTiming: report.networkTiming.ice,\n peerConnectionTiming: report.networkTiming.peerConnection,\n connectTiming: report.networkTiming.connect,\n mediaTiming: report.networkTiming.media,\n selectedLocalCandidate: report.selectedIceCandidatePairStats?.localCandidate,\n selectedRemoteCandidate: report.selectedIceCandidatePairStats?.remoteCandidate,\n iceCandidateStats,\n jitterStats,\n rttStats,\n packetLossStats,\n mosStats,\n error: report.error\n }\n };\n eventObserver.emit('event', insightsReport);\n setTimeout(() => eventPublisher.disconnect(), 2000);\n }\n };\n }\n\n private async _runPreflightTest(token: string, environment: string, wsServer: string) {\n let localTracks: MediaStreamTrack[] = [];\n let pcs: RTCPeerConnection[] = [];\n const { reportToInsights } = this._setupInsights({ token, environment });\n try {\n let elements = [];\n localTracks = await this._executePreflightStep('Acquire media', () => [syntheticAudio(), syntheticVideo({ width: 640, height: 480 })]);\n\n this._updateProgress(PreflightProgress.mediaAcquired);\n this.emit('debug', { localTracks });\n\n this._connectTiming.start();\n let iceServers = await this._executePreflightStep('Get turn credentials', () => getTurnCredentials(token, wsServer), new SignalingConnectionTimeoutError());\n\n this._connectTiming.stop();\n this._updateProgress(PreflightProgress.connected);\n\n const senderPC: RTCPeerConnection = new RTCPeerConnection({ iceServers, iceTransportPolicy: 'relay', bundlePolicy: 'max-bundle' });\n const receiverPC: RTCPeerConnection = new RTCPeerConnection({ iceServers, bundlePolicy: 'max-bundle' });\n pcs.push(senderPC);\n pcs.push(receiverPC);\n\n this._mediaTiming.start();\n const remoteTracks = await this._executePreflightStep('Setup Peer Connections', async () => {\n senderPC.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => event.candidate && receiverPC.addIceCandidate(event.candidate));\n receiverPC.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => event.candidate && senderPC.addIceCandidate(event.candidate));\n\n localTracks.forEach(track => senderPC.addTrack(track));\n\n const remoteTracksPromise: Promise = new Promise(resolve => {\n let remoteTracks: MediaStreamTrack[] = [];\n receiverPC.addEventListener('track', event => {\n remoteTracks.push(event.track);\n if (remoteTracks.length === localTracks.length) {\n resolve(remoteTracks);\n }\n });\n });\n\n const offer = await senderPC.createOffer();\n const updatedOffer = offer;\n await senderPC.setLocalDescription(updatedOffer);\n await receiverPC.setRemoteDescription(updatedOffer);\n\n const answer = await receiverPC.createAnswer();\n await receiverPC.setLocalDescription(answer);\n await senderPC.setRemoteDescription(answer);\n await this._collectNetworkTimings(senderPC);\n\n return remoteTracksPromise;\n }, new MediaConnectionError());\n this.emit('debug', { remoteTracks });\n remoteTracks.forEach(track => {\n track.addEventListener('ended', () => this._log.warn(track.kind + ':ended'));\n track.addEventListener('mute', () => this._log.warn(track.kind + ':muted'));\n track.addEventListener('unmute', () => this._log.warn(track.kind + ':unmuted'));\n });\n this._updateProgress(PreflightProgress.mediaSubscribed);\n\n await this._executePreflightStep('Wait for tracks to start', () => {\n return new Promise(resolve => {\n const element = document.createElement('video');\n element.autoplay = true;\n element.playsInline = true;\n element.muted = true;\n element.srcObject = new MediaStream(remoteTracks);\n elements.push(element);\n this.emit('debugElement', element);\n element.oncanplay = resolve;\n });\n }, new MediaConnectionError());\n this._mediaTiming.stop();\n this._updateProgress(PreflightProgress.mediaStarted);\n\n const collectedStats = await this._executePreflightStep('Collect stats for duration',\n () => this._collectRTCStatsForDuration(this._testDuration, initCollectedStats(), senderPC, receiverPC));\n\n const report = await this._executePreflightStep('Generate report', () => this._generatePreflightReport(collectedStats));\n reportToInsights({ report });\n this.emit('completed', report);\n\n } catch (error) {\n const preflightReport = this._generatePreflightReport();\n reportToInsights({ report: { ...preflightReport, error: error?.toString() } });\n this.emit('failed', error, preflightReport);\n } finally {\n pcs.forEach(pc => pc.close());\n localTracks.forEach(track => track.stop());\n }\n }\n\n private async _collectRTCStats(collectedStats: PreflightStats, senderPC: RTCPeerConnection, receiverPC: RTCPeerConnection) {\n const combinedStats = await getCombinedConnectionStats({ publisher: senderPC, subscriber: receiverPC });\n const { timestamp, bytesSent, bytesReceived, packets, packetsLost, roundTripTime, jitter, selectedIceCandidatePairStats, iceCandidateStats } = combinedStats;\n const hasLastData = collectedStats.jitter.length > 0;\n collectedStats.jitter.push(jitter);\n collectedStats.rtt.push(roundTripTime);\n\n this._sentBytesMovingAverage.putSample(bytesSent, timestamp);\n this._receivedBytesMovingAverage.putSample(bytesReceived, timestamp);\n this._packetLossMovingAverage.putSample(packetsLost, packets);\n if (hasLastData) {\n // convert BytesMovingAverage which is in bytes/millisecond to bits/second\n collectedStats.outgoingBitrate.push(this._sentBytesMovingAverage.get() * 1000 * 8);\n collectedStats.incomingBitrate.push(this._receivedBytesMovingAverage.get() * 1000 * 8);\n const fractionPacketLost = this._packetLossMovingAverage.get();\n const percentPacketsLost = Math.min(100, fractionPacketLost * 100);\n\n collectedStats.packetLoss.push(percentPacketsLost);\n\n const score = calculateMOS(roundTripTime, jitter, fractionPacketLost);\n collectedStats.mos.push(score);\n }\n\n if (!collectedStats.selectedIceCandidatePairStats) {\n collectedStats.selectedIceCandidatePairStats = selectedIceCandidatePairStats;\n }\n\n if (collectedStats.iceCandidateStats.length === 0) {\n collectedStats.iceCandidateStats = iceCandidateStats;\n }\n }\n\n private async _collectRTCStatsForDuration(duration: number, collectedStats: PreflightStats, senderPC: RTCPeerConnection, receiverPC: RTCPeerConnection) : Promise {\n const startTime = Date.now();\n const STAT_INTERVAL = Math.min(1000, duration);\n\n await waitForSometime(STAT_INTERVAL);\n\n await this._collectRTCStats(collectedStats, senderPC, receiverPC);\n\n const remainingDuration = duration - (Date.now() - startTime);\n\n if (remainingDuration > 0) {\n collectedStats = await this._collectRTCStatsForDuration(remainingDuration, collectedStats, senderPC, receiverPC);\n }\n return collectedStats;\n }\n\n private _updateProgress(name: string): void {\n const duration = Date.now() - this._testTiming.getTimeMeasurement().start;\n this._progressEvents.push({ duration, name });\n this.emit('progress', name);\n }\n}\n\n\nexport interface InternalStatsReport extends StatsReport {\n activeIceCandidatePair: {\n timestamp: number;\n bytesSent: number;\n bytesReceived: number;\n currentRoundTripTime?: number;\n localCandidate: RTCIceCandidateStats;\n remoteCandidate: RTCIceCandidateStats;\n }\n}\n\nfunction initCollectedStats() : PreflightStats {\n return {\n mos: [],\n jitter: [],\n rtt: [],\n outgoingBitrate: [],\n incomingBitrate: [],\n packetLoss: [],\n selectedIceCandidatePairStats: null,\n iceCandidateStats: [],\n };\n}\n\n/**\n * Represents network timing measurements captured during preflight test\n * @typedef {object} NetworkTiming\n * @property {TimeMeasurement} [connect] - Time to establish signaling connection and acquire turn credentials\n * @property {TimeMeasurement} [media] - Time to start media. This is measured from calling connect to remote media getting started.\n * @property {TimeMeasurement} [dtls] - Time to establish dtls connection. This is measured from RTCDtlsTransport `connecting` to `connected` state. (Not available on Safari)\n * @property {TimeMeasurement} [ice] - Time to establish ice connectivity. This is measured from ICE connection `checking` to `connected` state.\n * @property {TimeMeasurement} [peerConnection] - Time to establish peer connectivity. This is measured from PeerConnection `connecting` to `connected` state. (Not available on Firefox)\n */\n\n/**\n * Represents stats for a numerical metric.\n * @typedef {object} Stats\n * @property {number} [average] - Average value observed.\n * @property {number} [max] - Max value observed.\n * @property {number} [min] - Min value observed.\n */\n\n/**\n * Represents stats for a numerical metric.\n * @typedef {object} SelectedIceCandidatePairStats\n * @property {RTCIceCandidateStats} [localCandidate] - Selected local ice candidate\n * @property {RTCIceCandidateStats} [remoteCandidate] - Selected local ice candidate\n */\n\n/**\n * Represents RTC related stats that were observed during preflight test\n * @typedef {object} PreflightReportStats\n * @property {Stats} [jitter] - Packet delay variation in seconds\n * @property {Stats} [rtt] - Round trip time, to the server back to the client in milliseconds.\n * @property {Stats} [packetLoss] - Packet loss as a percent of total packets sent.\n*/\n\n/**\n * A {@link PreflightProgress} event with timing information.\n * @typedef {object} ProgressEvent\n * @property {number} [duration] - The duration of the event, measured from the start of the test.\n * @property {string} [name] - The {@link PreflightProgress} event name.\n */\n\n/**\n * Represents report generated by {@link PreflightTest}.\n * @typedef {object} PreflightTestReport\n * @property {TimeMeasurement} [testTiming] - Time measurements of test run time.\n * @property {NetworkTiming} [networkTiming] - Network related time measurements.\n * @property {PreflightReportStats} [stats] - RTC related stats captured during the test.\n * @property {Array} [iceCandidateStats] - List of gathered ice candidates.\n * @property {SelectedIceCandidatePairStats} selectedIceCandidatePairStats - Stats for the ice candidates that were used for the connection.\n * @property {Array} [progressEvents] - {@link ProgressEvent} events detected during the test.\n * Use this information to determine which steps were completed and which ones were not.\n */\n\n/**\n * You may pass these options to {@link module:twilio-video.testPreflight} in order to override the\n * default behavior.\n * @typedef {object} PreflightOptions\n * @property {string} [region='gll'] - Preferred signaling region; By default, you will be connected to the\n * nearest signaling server determined by latency based routing. Setting a value other\n * than gll bypasses routing and guarantees that signaling traffic will be\n * terminated in the region that you prefer. Please refer to this table\n * for the list of supported signaling regions.\n * @property {number} [duration=10000] - number of milliseconds to run test for.\n * once connected test will run for this duration before generating the stats report.\n */\n\n/**\n * Preflight test has completed successfully.\n * @param {PreflightTestReport} report - Results of the test.\n * @event PreflightTest#completed\n */\n\n/**\n * Preflight test has encountered a failure and is now stopped.\n * @param {TwilioError|Error} error - A TwilioError or a DOMException.\n * Possible TwilioErrors include Signaling and Media related errors which can be found\n * here.\n * @param {PreflightTestReport} report - Partial results gathered during the test. Use this information to help determine the cause of failure.\n * @event PreflightTest#failed\n */\n\n/**\n * Emitted to indicate progress of the test\n * @param {PreflightProgress} progress - Indicates the status completed.\n * @event PreflightTest#progress\n */\n\n/**\n * @method\n * @name runPreflight\n * @description Run a preflight test. This method will start a test to check the quality of network connection.\n * @memberof module:twilio-video\n * @param {string} token - The Access Token string\n * @param {PreflightOptions} options - Options for the test\n * @returns {PreflightTest} preflightTest - An instance to be used to monitor progress of the test.\n * @example\n * var { runPreflight } = require('twilio-video');\n * var preflight = runPreflight(token, preflightOptions);\n * preflightTest.on('progress', progress => {\n * console.log('preflight progress:', progress);\n * });\n *\n * preflightTest.on('failed', (error, report) => {\n * console.error('preflight error:', error, report);\n * });\n *\n * preflightTest.on('completed', report => {\n * console.log('preflight completed:', report));\n * });\n*/\nexport function runPreflight(token: string, options: PreflightOptions = {}): PreflightTest {\n const preflight = new PreflightTest(token, options);\n return preflight;\n}\n\n", "'use strict';\n\n/**\n * A Promise that can be canceled with {@link CancelablePromise#cancel}.\n * @extends Promise\n*/\nclass CancelablePromise {\n /**\n * Construct a new {@link CancelablePromise}.\n * @param {CancelablePromise.OnCreate} onCreate\n * @param {CancelablePromise.OnCancel} onCancel\n *//**\n * A function to be called on {@link CancelablePromise} creation\n * @typedef {function} CancelablePromise.OnCreate\n * @param {function(*)} resolve\n * @param {function(*)} reject\n * @param {function(): boolean} isCanceled\n *//**\n * A function to be called when {@link CancelablePromise#cancel} is called\n * @typedef {function} CancelablePromise.OnCancel\n */\n constructor(onCreate, onCancel) {\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _isCancelable: {\n writable: true,\n value: true\n },\n _isCanceled: {\n writable: true,\n value: false\n },\n _onCancel: {\n value: onCancel\n }\n });\n\n Object.defineProperty(this, '_promise', {\n value: new Promise((resolve, reject) => {\n onCreate(value => {\n this._isCancelable = false;\n resolve(value);\n }, reason => {\n this._isCancelable = false;\n reject(reason);\n }, () => this._isCanceled);\n })\n });\n }\n\n /**\n * Create a synchronously-rejected {@link CancelablePromise}.\n * @param {*} reason\n * @returns {Promise<*>}\n */\n static reject(reason) {\n return new CancelablePromise(function rejected(resolve, reject) {\n reject(reason);\n }, function onCancel() {\n // Do nothing.\n });\n }\n\n /**\n * Create a synchronously-resolved {@link CancelablePromise}.\n * @param {*|Promise<*>|Thenable<*>} result\n * @returns {CancelablePromise<*>}\n */\n static resolve(result) {\n return new CancelablePromise(function resolved(resolve) {\n resolve(result);\n }, function onCancel() {\n // Do nothing.\n });\n }\n\n /**\n * Attempt to cancel the {@link CancelablePromise}.\n * @returns {this}\n */\n cancel() {\n if (this._isCancelable) {\n this._isCanceled = true;\n this._onCancel();\n }\n return this;\n }\n\n /**\n * @param {function} onRejected\n * @returns {CancelablePromise}\n */\n catch() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.catch(...args).then(resolve, reject);\n }, this._onCancel);\n }\n\n /**\n * @param {?function} onResolved\n * @param {function} [onRejected]\n * @returns {CancelablePromise}\n */\n then() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.then(...args).then(resolve, reject);\n }, this._onCancel);\n }\n\n /**\n * @param {?function} onFinally\n * @returns {CancelablePromise}\n */\n finally() {\n const args = [].slice.call(arguments);\n const promise = this._promise;\n return new CancelablePromise(function onCreate(resolve, reject) {\n promise.finally(...args).then(resolve, reject);\n }, this._onCancel);\n }\n}\n\nmodule.exports = CancelablePromise;\n", "'use strict';\n\nconst CancelablePromise = require('./util/cancelablepromise');\n\n/**\n * Create a {@link CancelablePromise}.\n * @param {function(function(Array): CancelablePromise):\n * Promise>} getLocalTracks\n * @param {function(Array): LocalParticipant} createLocalParticipant\n * @param {function(Array): CancelablePromise} createRoomSignaling\n * @param {function(LocalParticipant, RoomSignaling): Room} createRoom\n * @returns CancelablePromise\n */\nfunction createCancelableRoomPromise(getLocalTracks, createLocalParticipant, createRoomSignaling, createRoom) {\n let cancelableRoomSignalingPromise;\n const cancellationError = new Error('Canceled');\n\n return new CancelablePromise(function onCreate(resolve, reject, isCanceled) {\n let localParticipant;\n getLocalTracks(function getLocalTracksSucceeded(localTracks) {\n if (isCanceled()) {\n return CancelablePromise.reject(cancellationError);\n }\n localParticipant = createLocalParticipant(localTracks);\n return createRoomSignaling(localParticipant).then(function createRoomSignalingSucceeded(getCancelableRoomSignalingPromise) {\n if (isCanceled()) {\n throw cancellationError;\n }\n cancelableRoomSignalingPromise = getCancelableRoomSignalingPromise();\n return cancelableRoomSignalingPromise;\n });\n }).then(function roomSignalingConnected(roomSignaling) {\n if (isCanceled()) {\n roomSignaling.disconnect();\n throw cancellationError;\n }\n resolve(createRoom(localParticipant, roomSignaling));\n }).catch(function onError(error) {\n reject(error);\n });\n }, function onCancel() {\n if (cancelableRoomSignalingPromise) {\n cancelableRoomSignalingPromise.cancel();\n }\n });\n}\n\nmodule.exports = createCancelableRoomPromise;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * {@link EncodingParametersImpl} represents an object which notifies its\n * listeners of any changes in the values of its properties.\n * @extends EventEmitter\n * @implements EncodingParameters\n * @emits EncodingParametersImpl#changed\n * @property {?number} maxAudioBitrate\n * @property {?number} maxVideoBitrate\n */\nclass EncodingParametersImpl extends EventEmitter {\n /**\n * Construct an {@link EncodingParametersImpl}.\n * @param {EncodingParamters} encodingParameters - Initial {@link EncodingParameters}\n * @param {Boolean} adaptiveSimulcast - true if adaptive simulcast was enabled by connect options.\n */\n constructor(encodingParameters, adaptiveSimulcast) {\n super();\n\n encodingParameters = Object.assign({\n maxAudioBitrate: null,\n maxVideoBitrate: null\n }, encodingParameters);\n\n Object.defineProperties(this, {\n maxAudioBitrate: {\n value: encodingParameters.maxAudioBitrate,\n writable: true\n },\n maxVideoBitrate: {\n value: encodingParameters.maxVideoBitrate,\n writable: true\n },\n adaptiveSimulcast: {\n value: adaptiveSimulcast\n }\n });\n }\n\n /**\n * Returns the bitrate values in an {@link EncodingParameters}.\n * @returns {EncodingParameters}\n */\n toJSON() {\n return {\n maxAudioBitrate: this.maxAudioBitrate,\n maxVideoBitrate: this.maxVideoBitrate\n };\n }\n\n /**\n * Update the bitrate values with those in the given {@link EncodingParameters}.\n * @param {EncodingParameters} encodingParameters - The new {@link EncodingParameters}\n * @fires EncodingParametersImpl#changed\n */\n update(encodingParameters) {\n encodingParameters = Object.assign({\n maxAudioBitrate: this.maxAudioBitrate,\n maxVideoBitrate: this.maxVideoBitrate\n }, encodingParameters);\n\n const shouldEmitChanged = [\n 'maxAudioBitrate',\n 'maxVideoBitrate'\n ].reduce((shouldEmitChanged, maxKindBitrate) => {\n if (this[maxKindBitrate] !== encodingParameters[maxKindBitrate]) {\n this[maxKindBitrate] = encodingParameters[maxKindBitrate];\n shouldEmitChanged = true;\n }\n return shouldEmitChanged;\n }, false);\n\n if (shouldEmitChanged) {\n this.emit('changed');\n }\n }\n}\n\n/**\n * At least one of the {@link EncodingParametersImpl}'s bitrate values changed.\n * @event EncodingParametersImpl#changed\n */\n\nmodule.exports = EncodingParametersImpl;\n", "'use strict';\n\nconst { isNonArrayObject } = require('./');\nconst { typeErrors: E, clientTrackSwitchOffControl, videoContentPreferencesMode, subscriptionMode, trackPriority, trackSwitchOffMode } = require('./constants');\n\n/**\n * Validate the {@link BandwidthProfileOptions} object.\n * @param {BandwidthProfileOptions} bandwidthProfile\n * @returns {?Error} - null if valid, Error if not.\n */\nfunction validateBandwidthProfile(bandwidthProfile) {\n let error = validateObject(bandwidthProfile, 'options.bandwidthProfile');\n if (!bandwidthProfile || error) {\n return error;\n }\n error = validateObject(bandwidthProfile.video, 'options.bandwidthProfile.video', [\n { prop: 'contentPreferencesMode', values: Object.values(videoContentPreferencesMode) },\n { prop: 'dominantSpeakerPriority', values: Object.values(trackPriority) },\n { prop: 'maxSubscriptionBitrate', type: 'number' },\n { prop: 'maxTracks', type: 'number' },\n { prop: 'mode', values: Object.values(subscriptionMode) },\n { prop: 'clientTrackSwitchOffControl', values: Object.values(clientTrackSwitchOffControl) },\n { prop: 'trackSwitchOffMode', values: Object.values(trackSwitchOffMode) }\n ]);\n\n if (error) {\n return error;\n }\n\n if (bandwidthProfile.video) {\n\n // maxTracks is replaced by clientTrackSwitchOffControl.\n // throw an error if both are specified.\n if ('maxTracks' in bandwidthProfile.video && 'clientTrackSwitchOffControl' in bandwidthProfile.video) {\n return new TypeError('options.bandwidthProfile.video.maxTracks is deprecated. Use options.bandwidthProfile.video.clientTrackSwitchOffControl instead.');\n }\n\n // renderDimensions is replaced by contentPreferencesMode.\n // throw an error if both are specified.\n if ('renderDimensions' in bandwidthProfile.video && 'contentPreferencesMode' in bandwidthProfile.video) {\n return new TypeError('options.bandwidthProfile.video.renderDimensions is deprecated. Use options.bandwidthProfile.video.contentPreferencesMode instead.');\n }\n\n return validateRenderDimensions(bandwidthProfile.video.renderDimensions);\n }\n\n return null;\n}\n\n/**\n * Throw if the given track is not a {@link LocalAudioTrack}, a\n * {@link LocalVideoTrack} or a MediaStreamTrack.\n * @param {*} track\n * @param {object} options\n */\nfunction validateLocalTrack(track, options) {\n if (!(track instanceof options.LocalAudioTrack\n || track instanceof options.LocalDataTrack\n || track instanceof options.LocalVideoTrack\n || track instanceof options.MediaStreamTrack)) {\n /* eslint new-cap:0 */\n throw E.INVALID_TYPE('track', 'LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n}\n\n/**\n * Validate an object. An object is valid if it is undefined or a non-null, non-array\n * object whose properties satisfy the specified data-type or value-range requirements.\n * @param {object} object - the object to be validated\n * @param {string} name - the object name to be used to build the error message, if invalid\n * @param {Array} [propChecks] - optional data-type or value-range requirements\n * for the object's properties\n * @returns {?Error} - null if object is valid, Error if not\n */\nfunction validateObject(object, name, propChecks = []) {\n // NOTE(mmalavalli): We determine that an undefined object is valid because this\n // means the parent object does not contain this object as a property, which is\n // a valid scenario.\n if (typeof object === 'undefined') {\n return null;\n }\n // NOTE(mmalavalli): We determine that if the object is null, or an Array, or\n // any other non-object type, then it is invalid.\n if (object === null || !isNonArrayObject(object)) {\n return E.INVALID_TYPE(name, 'object');\n }\n // NOTE(mmalavalli): We determine that the object is invalid if at least one of\n // its properties does not satisfy its data-type or value-range requirement.\n return propChecks.reduce((error, { prop, type, values }) => {\n if (error || !(prop in object)) {\n return error;\n }\n const value = object[prop];\n if (type && typeof value !== type) {\n return E.INVALID_TYPE(`${name}.${prop}`, type);\n }\n if (type === 'number' && isNaN(value)) {\n return E.INVALID_TYPE(`${name}.${prop}`, type);\n }\n if (Array.isArray(values) && !values.includes(value)) {\n return E.INVALID_VALUE(`${name}.${prop}`, values);\n }\n return error;\n }, null);\n}\n\n/**\n * Validates the renderDimensions field to be \"auto\" or {@link VideoRenderDimensions} object.\n * @param {string|VideoRenderDimensions} renderDimensions\n * @returns {?Error} - null if valid, Error if not.\n */\nfunction validateRenderDimensions(renderDimensions) {\n const name = 'options.bandwidthProfile.video.renderDimensions';\n let error = validateObject(renderDimensions, name);\n return renderDimensions ? error || Object.values(trackPriority).reduce((error, prop) => {\n return error || validateObject(renderDimensions[prop], `${name}.${prop}`, [\n { prop: 'height', type: 'number' },\n { prop: 'width', type: 'number' }\n ]);\n }, null) : error;\n}\n\nexports.validateBandwidthProfile = validateBandwidthProfile;\nexports.validateLocalTrack = validateLocalTrack;\nexports.validateObject = validateObject;\n", "'use strict';\n\nconst EventEmitter = require('../../eventemitter');\nconst { buildLogLevels, valueToJSON } = require('../../util');\nconst { DEFAULT_LOG_LEVEL } = require('../../util/constants');\nconst Log = require('../../util/log');\nlet nInstances = 0;\n\n/**\n * A {@link TrackPublication} represents a {@link Track} that\n * has been published to a {@link Room}.\n * @property {string} trackName - the published {@link Track}'s name\n * @property {Track.SID} trackSid - SID assigned to the published {@link Track}\n * @emits TrackPublication#trackDisabled\n * @emits TrackPublication#trackEnabled\n */\nclass TrackPublication extends EventEmitter {\n /**\n * Construct a {@link TrackPublication}.\n * @param {string} trackName - the published {@link Track}'s name\n * @param {Track.SID} trackSid - SID assigned to the {@link Track}\n * @param {TrackPublicationOptions} options - {@link TrackPublication} options\n */\n constructor(trackName, trackSid, options) {\n super();\n\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n _log: {\n value: options.log ? options.log.createLog('default', this) : new Log('default', this, logLevels, options.loggerName)\n },\n trackName: {\n enumerable: true,\n value: trackName\n },\n trackSid: {\n enumerable: true,\n value: trackSid\n }\n });\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n\n toString() {\n return `[TrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * The published {@link Track} was disabled.\n * @event TrackPublication#trackDisabled\n */\n\n/**\n * The published {@link Track} was enabled.\n * @event TrackPublication#trackEnabled\n */\n\n/**\n * A {@link LocalAudioTrackPublication} or a {@link RemoteAudioTrackPublication}.\n * @typedef {LocalAudioTrackPublication|RemoteAudioTrackPublication} AudioTrackPublication\n */\n\n/**\n * A {@link LocalDataTrackPublication} or a {@link RemoteDataTrackPublication}.\n * @typedef {LocalDataTrackPublication|RemoteDataTrackPublication} DataTrackPublication\n */\n\n/**\n * A {@link LocalVideoTrackPublication} or a {@link RemoteVideoTrackPublication}.\n * @typedef {LocalVideoTrackPublication|RemoteVideoTrackPublication} VideoTrackPublication\n */\n\n/**\n * {@link TrackPublication} options\n * @typedef {object} TrackPublicationOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n */\n\nmodule.exports = TrackPublication;\n", "/* eslint new-cap:0 */\n'use strict';\n\nconst TrackPublication = require('./trackpublication');\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\n\n/**\n * A {@link LocalTrackPublication} is a {@link LocalTrack} that has been\n * published to a {@link Room}.\n * @extends TrackPublication\n * @property {boolean} isTrackEnabled - whether the published {@link LocalTrack}\n * is enabled\n * @property {Track.Kind} kind - kind of the published {@link LocalTrack}\n * @property {Track.Priority} priority - the publish priority of the {@link LocalTrack}\n * @property {LocalTrack} track - the {@link LocalTrack}\n * @emits LocalTrackPublication#warning\n * @emits LocalTrackPublication#warningsCleared\n */\nclass LocalTrackPublication extends TrackPublication {\n /**\n * Construct a {@link LocalTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalTrack} track - The {@link LocalTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication}\n * options\n */\n constructor(signaling, track, unpublish, options) {\n super(track.name, signaling.sid, options);\n\n Object.defineProperties(this, {\n _reemitSignalingEvent: {\n value: (...args) => this.emit(\n args && args.length ? 'warning' : 'warningsCleared',\n ...args\n )\n },\n _reemitTrackEvent: {\n value: () => this.emit(this.isTrackEnabled\n ? 'trackEnabled'\n : 'trackDisabled')\n },\n _signaling: {\n value: signaling\n },\n _unpublish: {\n value: unpublish\n },\n isTrackEnabled: {\n enumerable: true,\n get() {\n return this.track.kind === 'data' ? true : this.track.isEnabled;\n }\n },\n kind: {\n enumerable: true,\n value: track.kind\n },\n priority: {\n enumerable: true,\n get() {\n return signaling.updatedPriority;\n }\n },\n track: {\n enumerable: true,\n value: track\n }\n });\n\n ['disabled', 'enabled'].forEach(name =>\n track.on(name, this._reemitTrackEvent));\n\n ['warning', 'warningsCleared'].forEach(name =>\n signaling.on(name, this._reemitSignalingEvent));\n }\n\n toString() {\n return `[LocalTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n\n /**\n * Update the {@link Track.Priority} of the published {@link LocalTrack}.\n * @param {Track.Priority} priority - the new {@link Track.priority}\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = Object.values(trackPriority);\n if (!priorityValues.includes(priority)) {\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n this._signaling.setPriority(priority);\n return this;\n }\n\n /**\n * Unpublish a {@link LocalTrackPublication}. This means that the media\n * from this {@link LocalTrackPublication} is no longer available to the\n * {@link Room}'s {@link RemoteParticipant}s.\n * @returns {this}\n */\n unpublish() {\n ['disabled', 'enabled'].forEach(name =>\n this.track.removeListener(name, this._reemitTrackEvent));\n\n ['warning', 'warningsCleared'].forEach(name =>\n this._signaling.removeListener(name, this._reemitSignalingEvent));\n\n this._unpublish(this);\n return this;\n }\n}\n\n/**\n * The published {@link LocalTrack} encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @event LocalTrackPublication#warning\n * @param {string} name - The warning that was raised.\n */\n\n/**\n * The published {@link LocalTrack} cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @event LocalTrackPublication#warningsCleared\n */\n\nmodule.exports = LocalTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalAudioTrackPublication} is a {@link LocalAudioTrack} that has\n * been published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"audio\"\n * @property {LocalAudioTrack} track - the {@link LocalAudioTrack}\n */\nclass LocalAudioTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalAudioTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalAudioTrack} track - the {@link LocalAudioTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalAudioTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalAudioTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalDataTrackPublication} is a {@link LocalDataTrack} that has been\n * published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"data\"\n * @property {LocalDataTrack} track - the {@link LocalDataTrack}\n */\nclass LocalDataTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalDataTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalDataTrack} track - the {@link LocalDataTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalDataTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalDataTrackPublication;\n", "'use strict';\n\nconst LocalTrackPublication = require('./localtrackpublication');\n\n/**\n * A {@link LocalVideoTrackPublication} is a {@link LocalVideoTrack} that has\n * been published to a {@link Room}.\n * @extends LocalTrackPublication\n * @property {Track.Kind} kind - \"video\"\n * @property {LocalVideoTrack} track - the {@link LocalVideoTrack}\n */\nclass LocalVideoTrackPublication extends LocalTrackPublication {\n /**\n * Construct a {@link LocalVideoTrackPublication}.\n * @param {LocalTrackPublicationSignaling} signaling - The corresponding\n * {@link LocalTrackPublicationSignaling}\n * @param {LocalVideoTrack} track - the {@link LocalVideoTrack}\n * @param {function(LocalTrackPublication): void} unpublish - The callback\n * that unpublishes the {@link LocalTrackPublication}\n * @param {TrackPublicationOptions} options - {@link LocalTrackPublication} options\n */\n constructor(signaling, track, unpublish, options) {\n super(signaling, track, unpublish, options);\n }\n\n toString() {\n return `[LocalVideoTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\nmodule.exports = LocalVideoTrackPublication;\n", "'use strict';\n\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\nconst { isIOS } = require('../../util/browserdetection');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\n\nfunction mixinRemoteMediaTrack(AudioOrVideoTrack) {\n /**\n * A {@link RemoteMediaTrack} represents a {@link MediaTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @property {boolean} isEnabled - Whether the {@link RemoteMediaTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteMediaTrack} is switched off\n * @property {Track.SID} sid - The SID assigned to the {@link RemoteMediaTrack}\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteMediaTrack}\n * @emits RemoteMediaTrack#disabled\n * @emits RemoteMediaTrack#enabled\n * @emits RemoteMediaTrack#switchedOff\n * @emits RemoteMediaTrack#switchedOn\n */\n return class RemoteMediaTrack extends AudioOrVideoTrack {\n /**\n * Construct a {@link RemoteMediaTrack}.\n * @param {Track.SID} sid\n * @param {MediaTrackReceiver} mediaTrackReceiver\n * @param {boolean} isEnabled\n @param {boolean} isSwitchedOff\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteMediaTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log, name: ?string}} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n options = Object.assign({\n // NOTE(mpatwardhan): WebKit bug: 212780 sometimes causes the audio/video elements to stay paused when safari\n // regains foreground. To workaround it, when safari gains foreground - we will play any elements that were\n // playing before safari lost foreground.\n workaroundWebKitBug212780: isIOS()\n && typeof document === 'object'\n && typeof document.addEventListener === 'function'\n && typeof document.visibilityState === 'string'\n }, options);\n\n super(mediaTrackReceiver, options);\n\n Object.defineProperties(this, {\n _isEnabled: {\n value: isEnabled,\n writable: true\n },\n _isSwitchedOff: {\n value: isSwitchedOff,\n writable: true\n },\n _priority: {\n value: null,\n writable: true\n },\n _setPriority: {\n value: setPriority\n },\n _setRenderHint: {\n value: renderHint => {\n this._log.debug('updating render hint:', renderHint);\n setRenderHint(renderHint);\n }\n },\n isEnabled: {\n enumerable: true,\n get() {\n return this._isEnabled;\n }\n },\n isSwitchedOff: {\n enumerable: true,\n get() {\n return this._isSwitchedOff;\n }\n },\n priority: {\n enumerable: true,\n get() {\n return this._priority;\n }\n },\n sid: {\n enumerable: true,\n value: sid\n },\n _workaroundWebKitBug212780: {\n value: options.workaroundWebKitBug212780\n },\n _workaroundWebKitBug212780Cleanup: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteMediaTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * If null, then the subscribe {@link Track.Priority} is cleared, which\n * means the {@link Track.Priority} set by the publisher is now the effective priority.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = [null, ...Object.values(trackPriority)];\n if (!priorityValues.includes(priority)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n if (this._priority !== priority) {\n this._priority = priority;\n this._setPriority(priority);\n }\n return this;\n }\n\n /**\n * @private\n * @param {boolean} isEnabled\n */\n _setEnabled(isEnabled) {\n if (this._isEnabled !== isEnabled) {\n this._isEnabled = isEnabled;\n this.emit(this._isEnabled ? 'enabled' : 'disabled', this);\n }\n }\n\n /**\n * @private\n * @param {boolean} isSwitchedOff\n */\n _setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit(isSwitchedOff ? 'switchedOff' : 'switchedOn', this);\n }\n }\n\n attach(el) {\n const result = super.attach(el);\n if (this.mediaStreamTrack.enabled !== true) {\n // NOTE(mpatwardhan): we disable mediaStreamTrack when there\n // are no attachments to it (see notes below). Now that there\n // are attachments re-enable the track.\n this.mediaStreamTrack.enabled = true;\n if (this.processedTrack) {\n this.processedTrack.enabled = true;\n }\n\n // NOTE(csantos): since remote tracks disables/enables the mediaStreamTrack,\n // captureFrames stops along with it. We need to start it again after re-enabling.\n // See attach/detach methods in this class and in VideoTrack class.\n if (this.processor) {\n this._captureFrames();\n }\n }\n if (this._workaroundWebKitBug212780) {\n this._workaroundWebKitBug212780Cleanup = this._workaroundWebKitBug212780Cleanup\n || playIfPausedWhileInBackground(this);\n }\n\n return result;\n }\n\n detach(el) {\n const result = super.detach(el);\n if (this._attachments.size === 0) {\n // NOTE(mpatwardhan): chrome continues playing webrtc audio\n // track even after audio element is removed from the DOM.\n // https://bugs.chromium.org/p/chromium/issues/detail?id=749928\n // to workaround: here disable the track when\n // there are no elements attached to it.\n this.mediaStreamTrack.enabled = false;\n if (this.processedTrack) {\n this.processedTrack.enabled = false;\n }\n\n if (this._workaroundWebKitBug212780Cleanup) {\n // unhook visibility change\n this._workaroundWebKitBug212780Cleanup();\n this._workaroundWebKitBug212780Cleanup = null;\n }\n }\n return result;\n }\n };\n}\n\nfunction playIfPausedWhileInBackground(remoteMediaTrack) {\n const { _log: log, kind } = remoteMediaTrack;\n\n function onVisibilityChanged(isVisible) {\n if (!isVisible) {\n return;\n }\n remoteMediaTrack._attachments.forEach(el => {\n const shim = remoteMediaTrack._elShims.get(el);\n const isInadvertentlyPaused = el.paused && shim && !shim.pausedIntentionally();\n if (isInadvertentlyPaused) {\n log.info(`Playing inadvertently paused <${kind}> element`);\n log.debug('Element:', el);\n log.debug('RemoteMediaTrack:', remoteMediaTrack);\n el.play().then(() => {\n log.info(`Successfully played inadvertently paused <${kind}> element`);\n log.debug('Element:', el);\n log.debug('RemoteMediaTrack:', remoteMediaTrack);\n }).catch(err => {\n log.warn(`Error while playing inadvertently paused <${kind}> element:`, { err, el, remoteMediaTrack });\n });\n }\n });\n }\n\n // NOTE(mpatwardhan): listen for document visibility callback on phase 2.\n // this ensures that any LocalMediaTrack's restart (which listen on phase 1) gets executed\n // first. This order is important because we `play` tracks in the callback, and\n // play can fail on safari if audio is not being captured.\n documentVisibilityMonitor.onVisibilityChange(2, onVisibilityChanged);\n return () => {\n documentVisibilityMonitor.offVisibilityChange(2, onVisibilityChanged);\n };\n}\n\n/**\n * A {@link RemoteMediaTrack} was disabled.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * disabled\n * @event RemoteMediaTrack#disabled\n */\n\n/**\n * A {@link RemoteMediaTrack} was enabled.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * enabled\n * @event RemoteMediaTrack#enabled\n */\n\n/**\n * A {@link RemoteMediaTrack} was switched off.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * switched off\n * @event RemoteMediaTrack#switchedOff\n */\n\n/**\n * A {@link RemoteMediaTrack} was switched on.\n * @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was\n * switched on\n * @event RemoteMediaTrack#switchedOn\n */\n\n/**\n * A {@link ClientRenderHint} object specifies track dimensions and /enabled disable state.\n * This state will be used by the server(SFU) to determine bandwidth allocation for the track,\n * and turn it on or off as needed.\n * @typedef {object} ClientRenderHint\n * @property {boolean} [enabled] - track is enabled or disabled. defaults to disabled.\n * @property {VideoTrack.Dimensions} [renderDimensions] - Optional parameter to specify the desired\n * render dimensions of {@link RemoteVideoTrack}s. This property must be specified if enabled=true\n */\n\nmodule.exports = mixinRemoteMediaTrack;\n", "'use strict';\n\nconst AudioTrack = require('./audiotrack');\nconst mixinRemoteMediaTrack = require('./remotemediatrack');\n\nconst RemoteMediaAudioTrack = mixinRemoteMediaTrack(AudioTrack);\n\n/**\n * A {@link RemoteAudioTrack} represents an {@link AudioTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @extends AudioTrack\n * @property {boolean} isEnabled - Whether the {@link RemoteAudioTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteAudioTrack} is switched off\n * @property {Track.SID} sid - The {@link RemoteAudioTrack}'s SID\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteAudioTrack}\n * @emits RemoteAudioTrack#disabled\n * @emits RemoteAudioTrack#enabled\n * @emits RemoteAudioTrack#started\n * @emits RemoteAudioTrack#switchedOff\n * @emits RemoteAudioTrack#switchedOn\n */\nclass RemoteAudioTrack extends RemoteMediaAudioTrack {\n /**\n * Construct a {@link RemoteAudioTrack}.\n * @param {Track.SID} sid - The {@link RemoteAudioTrack}'s SID\n * @param {MediaTrackReceiver} mediaTrackReceiver - An audio MediaStreamTrack container\n * @param {boolean} isEnabled - Whether the {@link RemoteAudioTrack} is enabled\n * @param {boolean} isSwitchedOff - Whether the {@link RemoteAudioTrack} is switched off\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteAudioTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log}} options - The {@link RemoteTrack} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n super(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n }\n\n toString() {\n return `[RemoteAudioTrack #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * @private\n */\n _start() {\n super._start();\n if (this._dummyEl) {\n // NOTE(mpatwardhan): To fix VIDEO-6336, clear dummy element after the\n // RemoteAudioTrack has started.\n this._dummyEl.srcObject = null;\n this._dummyEl = null;\n }\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteAudioTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * Currently setPriority has no effect on audio tracks.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n return super.setPriority(priority);\n }\n}\n\n/**\n * The {@link RemoteAudioTrack} was disabled, i.e. \"muted\".\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * disabled\n * @event RemoteAudioTrack#disabled\n */\n\n/**\n * The {@link RemoteAudioTrack} was enabled, i.e. \"unmuted\".\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * enabled\n * @event RemoteAudioTrack#enabled\n */\n\n/**\n * The {@link RemoteAudioTrack} started. This means there is enough audio data\n * to begin playback.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that started\n * @event RemoteAudioTrack#started\n */\n\n/**\n * A {@link RemoteAudioTrack} was switched off.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * switched off\n * @event RemoteAudioTrack#switchedOff\n */\n\n/**\n * A {@link RemoteAudioTrack} was switched on.\n * @param {RemoteAudioTrack} track - The {@link RemoteAudioTrack} that was\n * switched on\n * @event RemoteAudioTrack#switchedOn\n */\n\nmodule.exports = RemoteAudioTrack;\n", "'use strict';\n\nconst TrackPublication = require('./trackpublication');\n\n/**\n * A {@link RemoteTrackPublication} represents a {@link RemoteTrack} that has\n * been published to a {@link Room}.\n * @extends TrackPublication\n * @property {boolean} isSubscribed - whether the published {@link RemoteTrack}\n * is subscribed to\n * @property {boolean} isTrackEnabled - whether the published\n * {@link RemoteTrack} is enabled\n * @property {Track.Kind} kind - kind of the published {@link RemoteTrack}\n * @property {Track.Priority} publishPriority - the {@link Track.Priority} of the published\n * {@link RemoteTrack} set by the {@link RemoteParticipant}\n * @property {?RemoteTrack} track - Unless you have subscribed to the\n * {@link RemoteTrack}, this property is null\n * @emits RemoteTrackPublication#publishPriorityChanged\n * @emits RemoteTrackPublication#subscribed\n * @emits RemoteTrackPublication#subscriptionFailed\n * @emits RemoteTrackPublication#trackDisabled\n * @emits RemoteTrackPublication#trackEnabled\n * @emits RemoteTrackPublication#trackSwitchedOff\n * @emits RemoteTrackPublication#trackSwitchedOn\n * @emits RemoteTrackPublication#unsubscribed\n *\n */\nclass RemoteTrackPublication extends TrackPublication {\n /**\n * Construct a {@link RemoteTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling.name, signaling.sid, options);\n\n Object.defineProperties(this, {\n _signaling: {\n value: signaling\n },\n _track: {\n value: null,\n writable: true\n },\n isSubscribed: {\n enumerable: true,\n get() {\n return !!this._track;\n }\n },\n isTrackEnabled: {\n enumerable: true,\n get() {\n return signaling.isEnabled;\n }\n },\n kind: {\n enumerable: true,\n value: signaling.kind\n },\n publishPriority: {\n enumerable: true,\n get() {\n return signaling.priority;\n }\n },\n track: {\n enumerable: true,\n get() {\n return this._track;\n }\n }\n });\n\n // remember original state, and fire events only on change.\n let {\n error,\n isEnabled,\n isSwitchedOff,\n priority\n } = signaling;\n\n signaling.on('updated', () => {\n if (error !== signaling.error) {\n error = signaling.error;\n this.emit('subscriptionFailed', signaling.error);\n return;\n }\n if (isEnabled !== signaling.isEnabled) {\n isEnabled = signaling.isEnabled;\n if (this.track) {\n this.track._setEnabled(signaling.isEnabled);\n }\n this.emit(signaling.isEnabled ? 'trackEnabled' : 'trackDisabled');\n }\n if (isSwitchedOff !== signaling.isSwitchedOff) {\n this._log.debug(`${this.trackSid}: ${isSwitchedOff ? 'OFF' : 'ON'} => ${signaling.isSwitchedOff ? 'OFF' : 'ON'}`);\n isSwitchedOff = signaling.isSwitchedOff;\n if (this.track) {\n this.track._setSwitchedOff(signaling.isSwitchedOff);\n this.emit(isSwitchedOff ? 'trackSwitchedOff' : 'trackSwitchedOn', this.track);\n } else if (isSwitchedOff) {\n this._log.warn('Track was not subscribed when switched Off.');\n }\n }\n if (priority !== signaling.priority) {\n priority = signaling.priority;\n this.emit('publishPriorityChanged', priority);\n }\n });\n }\n\n toString() {\n return `[RemoteTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n */\n _subscribed(track) {\n if (!this._track && track) {\n this._track = track;\n this.emit('subscribed', track);\n }\n }\n\n /**\n * @private\n */\n _unsubscribe() {\n if (this._track) {\n const track = this._track;\n this._track = null;\n this.emit('unsubscribed', track);\n }\n }\n}\n\n/**\n * The {@link RemoteTrack}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority}; RemoteTrackPublication#publishPriority is also\n * updated accordingly\n * @event RemoteTrackPublication#publishPriorityChanged\n */\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteTrack}.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was subscribed to\n * @event RemoteTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteTrack} could not be\n * subscribed to\n * @event RemoteTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteTrack} was disabled.\n * @event RemoteTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteTrack} was enabled.\n * @event RemoteTrackPublication#trackEnabled\n */\n\n/**\n * The {@link RemoteTrack} was switched off.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was switched off\n * @event RemoteTrackPublication#trackSwitchedOff\n */\n\n/**\n * The {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was switched on\n * @event RemoteTrackPublication#trackSwitchedOn\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteTrack}.\n * @param {RemoteTrack} track - the {@link RemoteTrack} that was unsubscribed from\n * @event RemoteTrackPublication#unsubscribed\n */\n\n/**\n * {@link RemoteTrackPublication} options\n * @typedef {object} RemoteTrackPublicationOptions\n * @property {LogLevel|LogLevels} logLevel - Log level for 'media' modules\n */\n\nmodule.exports = RemoteTrackPublication;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteAudioTrackPublication} represents a {@link RemoteAudioTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"audio\"\n * @property {?RemoteAudioTrack} track - unless you have subscribed to the\n * {@link RemoteAudioTrack}, this property is null\n * @emits RemoteAudioTrackPublication#subscribed\n * @emits RemoteAudioTrackPublication#subscriptionFailed\n * @emits RemoteAudioTrackPublication#trackDisabled\n * @emits RemoteAudioTrackPublication#trackEnabled\n * @emits RemoteAudioTrackPublication#unsubscribed\n */\nclass RemoteAudioTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteAudioTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteAudioTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteAudioTrack}.\n * @param {RemoteAudioTrack} track - the {@link RemoteAudioTrack} that was subscribed to\n * @event RemoteAudioTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteAudioTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteAudioTrack} could not be\n * subscribed to\n * @event RemoteAudioTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteAudioTrack} was disabled.\n * @event RemoteAudioTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteAudioTrack} was enabled.\n * @event RemoteAudioTrackPublication#trackEnabled\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteAudioTrack}.\n * @param {RemoteAudioTrack} track - the {@link RemoteAudioTrack} that was unsubscribed from\n * @event RemoteAudioTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteAudioTrackPublication;\n", "'use strict';\n\nconst Track = require('./');\nconst { typeErrors: E, trackPriority } = require('../../util/constants');\n\n/**\n * A {@link RemoteDataTrack} represents data published to a {@link Room} by a\n * {@link RemoteParticipant}.\n * @extends Track\n * @property {boolean} isEnabled - true\n * @property {boolean} isSubscribed - Whether the {@link RemoteDataTrack} is\n * subscribed to\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteDataTrack} is\n * switched off\n * @property {Track.Kind} kind - \"data\"\n * @property {?number} maxPacketLifeTime - If non-null, this represents a time\n * limit (in milliseconds) during which data will be transmitted or\n * retransmitted if not acknowledged on the underlying RTCDataChannel.\n * @property {?number} maxRetransmits - If non-null, this represents the number\n * of times the data will be retransmitted if not successfully received on the\n * underlying RTCDataChannel.\n * @property {boolean} ordered - true if data on the {@link RemoteDataTrack} can\n * be received out-of-order.\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteDataTrack}\n * @property {boolean} reliable - This is true if both\n * maxPacketLifeTime and maxRetransmits are set to\n * null. In other words, if this is true, there is no bound on packet lifetime\n * or the number of retransmits that will be attempted, ensuring \"reliable\"\n * transmission.\n * @property {Track.SID} sid - The SID assigned to the {@link RemoteDataTrack}\n * @emits RemoteDataTrack#message\n * @emits RemoteDataTrack#switchedOff\n * @emits RemoteDataTrack#switchedOn\n */\nclass RemoteDataTrack extends Track {\n /**\n * Construct a {@link RemoteDataTrack} from a {@link DataTrackReceiver}.\n * @param {Track.SID} sid\n * @param {DataTrackReceiver} dataTrackReceiver\n * @param {{log: Log, name: ?string}} options\n */\n constructor(sid, dataTrackReceiver, options) {\n super(dataTrackReceiver.id, 'data', options);\n\n Object.defineProperties(this, {\n _isSwitchedOff: {\n value: false,\n writable: true\n },\n _priority: {\n value: null,\n writable: true\n },\n isEnabled: {\n enumerable: true,\n value: true\n },\n isSwitchedOff: {\n enumerable: true,\n get() {\n return this._isSwitchedOff;\n }\n },\n maxPacketLifeTime: {\n enumerable: true,\n value: dataTrackReceiver.maxPacketLifeTime\n },\n maxRetransmits: {\n enumerable: true,\n value: dataTrackReceiver.maxRetransmits\n },\n ordered: {\n enumerable: true,\n value: dataTrackReceiver.ordered\n },\n priority: {\n enumerable: true,\n get() {\n return this._priority;\n }\n },\n reliable: {\n enumerable: true,\n value: dataTrackReceiver.maxPacketLifeTime === null\n && dataTrackReceiver.maxRetransmits === null\n },\n sid: {\n enumerable: true,\n value: sid\n }\n });\n\n dataTrackReceiver.on('message', data => {\n this.emit('message', data, this);\n });\n }\n\n /**\n * Update the subscriber {@link Track.Priority} of the {@link RemoteDataTrack}.\n * @param {?Track.Priority} priority - the new {@link Track.priority};\n * Currently setPriority has no effect on data tracks.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n const priorityValues = [null, ...Object.values(trackPriority)];\n if (!priorityValues.includes(priority)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_VALUE('priority', priorityValues);\n }\n\n // Note: priority has no real effect on the data tracks.\n this._priority = priority;\n return this;\n }\n\n /**\n * @private\n */\n _setEnabled() {\n // Do nothing.\n }\n\n /**\n * @private\n * @param {boolean} isSwitchedOff\n */\n _setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit(isSwitchedOff ? 'switchedOff' : 'switchedOn', this);\n }\n }\n}\n\n/**\n * A message was received over the {@link RemoteDataTrack}.\n * @event RemoteDataTrack#message\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that received\n * the message\n */\n\n/**\n * A {@link RemoteDataTrack} was switched off.\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that was\n * switched off\n * @event RemoteDataTrack#switchedOff\n */\n\n/**\n * A {@link RemoteDataTrack} was switched on.\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} that was\n * switched on\n * @event RemoteDataTrack#switchedOn\n */\n\nmodule.exports = RemoteDataTrack;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteDataTrackPublication} represents a {@link RemoteDataTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"data\"\n * @property {?RemoteDataTrack} track - unless you have subscribed to the\n * {@link RemoteDataTrack}, this property is null\n * @emits RemoteDataTrackPublication#subscribed\n * @emits RemoteDataTrackPublication#subscriptionFailed\n * @emits RemoteDataTrackPublication#unsubscribed\n */\nclass RemoteDataTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteDataTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteDataTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteDataTrack}.\n * @param {RemoteDataTrack} track - the {@link RemoteDataTrack} that was subscribed to\n * @event RemoteDataTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteDataTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteDataTrack} could not be\n * subscribed to\n * @event RemoteDataTrackPublication#subscriptionFailed\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteDataTrack}.\n * @param {RemoteDataTrack} track - the {@link RemoteDataTrack} that was unsubscribed from\n * @event RemoteDataTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteDataTrackPublication;\n", "/* eslint-disable no-console */\n'use strict';\n\nclass NullObserver {\n constructor(callback) {\n Object.defineProperties(this, {\n _callback: {\n value: callback\n }\n });\n }\n\n observe() {\n }\n\n unobserve() {\n }\n\n makeVisible(videoEl) {\n const visibleEntry = this._makeFakeEntry(videoEl, true);\n this._callback([visibleEntry]);\n }\n\n makeInvisible(videoEl) {\n const invisibleEntry = this._makeFakeEntry(videoEl, false);\n this._callback([invisibleEntry]);\n }\n\n _makeFakeEntry(videoElement, isIntersecting) {\n return { target: videoElement, isIntersecting };\n }\n}\n\nclass NullIntersectionObserver extends NullObserver { }\n\nclass NullResizeObserver extends NullObserver {\n resize(videoEl) {\n const entry = this._makeFakeEntry(videoEl, true);\n this._callback([entry]);\n }\n}\n\n\nmodule.exports = { NullIntersectionObserver, NullResizeObserver, NullObserver };\n", "'use strict';\n\nconst mixinRemoteMediaTrack = require('./remotemediatrack');\nconst VideoTrack = require('./videotrack');\nconst documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');\nconst { NullObserver } = require('../../util/nullobserver.js');\nconst Timeout = require('../../util/timeout');\n\nconst RemoteMediaVideoTrack = mixinRemoteMediaTrack(VideoTrack);\nconst TRACK_TURN_OF_DELAY_MS = 50;\n\n/**\n * A {@link RemoteVideoTrack} represents a {@link VideoTrack} published to a\n * {@link Room} by a {@link RemoteParticipant}.\n * @extends VideoTrack\n * @property {boolean} isEnabled - Whether the {@link RemoteVideoTrack} is enabled\n * @property {boolean} isSwitchedOff - Whether the {@link RemoteVideoTrack} is switched off\n * @property {Track.SID} sid - The {@link RemoteVideoTrack}'s SID\n * @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteVideoTrack}\n * @emits RemoteVideoTrack#dimensionsChanged\n * @emits RemoteVideoTrack#disabled\n * @emits RemoteVideoTrack#enabled\n * @emits RemoteVideoTrack#started\n * @emits RemoteVideoTrack#switchedOff\n * @emits RemoteVideoTrack#switchedOn\n */\nclass RemoteVideoTrack extends RemoteMediaVideoTrack {\n /**\n * Construct a {@link RemoteVideoTrack}.\n * @param {Track.SID} sid - The {@link RemoteVideoTrack}'s SID\n * @param {MediaTrackReceiver} mediaTrackReceiver - A video MediaStreamTrack container\n * @param {boolean} isEnabled - whether the {@link RemoteVideoTrack} is enabled\n * @param {boolean} isSwitchedOff - Whether the {@link RemoteVideoTrack} is switched off\n * @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe\n * {@link Track.Priority} of the {@link RemoteVideoTrack}\n * @param {function(ClientRenderHint): void} setRenderHint - Set render hints.\n * @param {{log: Log}} options - The {@link RemoteTrack} options\n */\n constructor(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {\n options = Object.assign({\n clientTrackSwitchOffControl: 'auto',\n contentPreferencesMode: 'auto',\n enableDocumentVisibilityTurnOff: true,\n }, options);\n\n options = Object.assign({\n IntersectionObserver: typeof IntersectionObserver === 'undefined' || options.clientTrackSwitchOffControl !== 'auto' ? NullObserver : IntersectionObserver,\n ResizeObserver: typeof ResizeObserver === 'undefined' || options.contentPreferencesMode !== 'auto' ? NullObserver : ResizeObserver,\n }, options);\n\n super(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n\n Object.defineProperties(this, {\n _enableDocumentVisibilityTurnOff: {\n value: options.enableDocumentVisibilityTurnOff === true && options.clientTrackSwitchOffControl === 'auto',\n },\n _documentVisibilityTurnOffCleanup: {\n value: null,\n writable: true\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl,\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode,\n },\n _invisibleElements: {\n value: new WeakSet(),\n },\n _elToPipCallbacks: {\n value: new WeakMap(),\n },\n _elToPipWindows: {\n value: new WeakMap(),\n },\n _turnOffTimer: {\n value: new Timeout(() => {\n this._setRenderHint({ enabled: false });\n }, TRACK_TURN_OF_DELAY_MS, false),\n },\n _resizeObserver: {\n value: new options.ResizeObserver(entries => {\n // NOTE(mpatwardhan): we ignore elements in _invisibleElements\n // to ensure that ResizeObserver does not end-up turning off a track when a fresh Video element is\n // attached and IntersectionObserver has not had its callback executed yet.\n const visibleElementResized = entries.find(entry => !this._invisibleElements.has(entry.target));\n if (visibleElementResized) {\n maybeUpdateDimensionHint(this);\n }\n })\n },\n _intersectionObserver: {\n value: new options.IntersectionObserver(entries => {\n let shouldSetRenderHint = false;\n entries.forEach(entry => {\n const wasVisible = !this._invisibleElements.has(entry.target);\n if (wasVisible !== entry.isIntersecting) {\n if (entry.isIntersecting) {\n this._log.debug('intersectionObserver detected: Off => On');\n this._invisibleElements.delete(entry.target);\n } else {\n this._log.debug('intersectionObserver detected: On => Off');\n this._invisibleElements.add(entry.target);\n }\n shouldSetRenderHint = true;\n }\n });\n if (shouldSetRenderHint) {\n maybeUpdateEnabledHint(this);\n\n // when visibility of an element changes that may cause the \"biggest\" element to change,\n // update dimensions as well. since dimensions are cached and de-duped at signaling layer,\n // its okay if they got resent.\n maybeUpdateDimensionHint(this);\n }\n }, { threshold: 0.25 })\n },\n });\n }\n\n /**\n * @private\n */\n _start(dummyEl) {\n const result = super._start.call(this, dummyEl);\n // NOTE(mpatwardhan): after emitting started, update turn off track if not visible.\n maybeUpdateEnabledHint(this);\n return result;\n }\n\n /**\n * Request to switch on a {@link RemoteVideoTrack}, This method is applicable only for the group rooms and only when connected with\n * clientTrackSwitchOffControl in video bandwidth profile options set to 'manual'\n * @returns {this}\n */\n switchOn() {\n if (this._clientTrackSwitchOffControl !== 'manual') {\n throw new Error('Invalid state. You can call switchOn only when bandwidthProfile.video.clientTrackSwitchOffControl is set to \"manual\"');\n }\n this._setRenderHint({ enabled: true });\n return this;\n }\n\n /**\n * Request to switch off a {@link RemoteVideoTrack}, This method is applicable only for the group rooms and only when connected with\n * clientTrackSwitchOffControl in video bandwidth profile options set to 'manual'\n * @returns {this}\n */\n switchOff() {\n if (this._clientTrackSwitchOffControl !== 'manual') {\n throw new Error('Invalid state. You can call switchOff only when bandwidthProfile.video.clientTrackSwitchOffControl is set to \"manual\"');\n }\n this._setRenderHint({ enabled: false });\n return this;\n }\n\n /**\n * Set the {@link RemoteVideoTrack}'s content preferences. This method is applicable only for the group rooms and only when connected with\n * videoContentPreferencesMode in video bandwidth profile options set to 'manual'\n * @param {VideoContentPreferences} contentPreferences - requested preferences.\n * @returns {this}\n */\n setContentPreferences(contentPreferences) {\n if (this._contentPreferencesMode !== 'manual') {\n throw new Error('Invalid state. You can call switchOn only when bandwidthProfile.video.contentPreferencesMode is set to \"manual\"');\n }\n\n if (contentPreferences.renderDimensions) {\n this._setRenderHint({ renderDimensions: contentPreferences.renderDimensions });\n }\n return this;\n }\n\n _unObservePip(el) {\n const pipCallbacks = this._elToPipCallbacks.get(el);\n if (pipCallbacks) {\n el.removeEventListener('enterpictureinpicture', pipCallbacks.onEnterPip);\n el.removeEventListener('leavepictureinpicture', pipCallbacks.onLeavePip);\n this._elToPipCallbacks.delete(el);\n }\n }\n\n _observePip(el) {\n const pipCallbacks = this._elToPipCallbacks.get(el);\n if (!pipCallbacks) {\n const onEnterPip = event => this._onEnterPip(event, el);\n const onLeavePip = event => this._onLeavePip(event, el);\n const onResizePip = event => this._onResizePip(event, el);\n\n el.addEventListener('enterpictureinpicture', onEnterPip);\n el.addEventListener('leavepictureinpicture', onLeavePip);\n this._elToPipCallbacks.set(el, { onEnterPip, onLeavePip, onResizePip });\n }\n }\n\n _onEnterPip(event, videoEl) {\n this._log.debug('onEnterPip');\n const pipWindow = event.pictureInPictureWindow;\n this._elToPipWindows.set(videoEl, pipWindow);\n const { onResizePip } = this._elToPipCallbacks.get(videoEl);\n pipWindow.addEventListener('resize', onResizePip);\n maybeUpdateEnabledHint(this);\n }\n\n _onLeavePip(event, videoEl) {\n this._log.debug('onLeavePip');\n this._elToPipWindows.delete(videoEl);\n const { onResizePip } = this._elToPipCallbacks.get(videoEl);\n const pipWindow = event.pictureInPictureWindow;\n pipWindow.removeEventListener('resize', onResizePip);\n maybeUpdateEnabledHint(this);\n }\n\n _onResizePip() {\n maybeUpdateDimensionHint(this);\n }\n\n attach(el) {\n const result = super.attach(el);\n\n if (this._clientTrackSwitchOffControl === 'auto') {\n // start off the element as invisible. will mark it\n // visible (and update render hints) once intersection observer calls back.\n this._invisibleElements.add(result);\n }\n\n this._intersectionObserver.observe(result);\n this._resizeObserver.observe(result);\n\n if (this._enableDocumentVisibilityTurnOff) {\n this._documentVisibilityTurnOffCleanup = this._documentVisibilityTurnOffCleanup || setupDocumentVisibilityTurnOff(this);\n }\n\n this._observePip(result);\n return result;\n }\n\n detach(el) {\n const result = super.detach(el);\n const elements = Array.isArray(result) ? result : [result];\n elements.forEach(element => {\n this._intersectionObserver.unobserve(element);\n this._resizeObserver.unobserve(element);\n this._invisibleElements.delete(element);\n this._unObservePip(element);\n });\n\n if (this._attachments.size === 0) {\n if (this._documentVisibilityTurnOffCleanup) {\n this._documentVisibilityTurnOffCleanup();\n this._documentVisibilityTurnOffCleanup = null;\n }\n }\n\n maybeUpdateEnabledHint(this);\n maybeUpdateDimensionHint(this);\n return result;\n }\n\n /**\n * Add a {@link VideoProcessor} to allow for custom processing of video frames belonging to a VideoTrack.\n * When a Participant un-publishes and re-publishes a VideoTrack, a new RemoteVideoTrack is created and\n * any VideoProcessors attached to the previous RemoteVideoTrack would have to be re-added again.\n * Only Chrome supports this as of now. Calling this API from a non-supported browser will result in a log warning.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to use.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const grayscaleProcessor = new GrayScaleProcessor(100);\n *\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.addProcessor(grayscaleProcessor);\n * });\n */\n addProcessor() {\n return super.addProcessor.apply(this, arguments);\n }\n\n /**\n * Remove the previously added {@link VideoProcessor} using `addProcessor` API.\n * @param {VideoProcessor} processor - The {@link VideoProcessor} to remove.\n * @returns {this}\n * @example\n * class GrayScaleProcessor {\n * constructor(percentage) {\n * this.percentage = percentage;\n * }\n * processFrame(inputFrameBuffer, outputFrameBuffer) {\n * const context = outputFrameBuffer.getContext('2d');\n * context.filter = `grayscale(${this.percentage}%)`;\n * context.drawImage(inputFrameBuffer, 0, 0, inputFrameBuffer.width, inputFrameBuffer.height);\n * }\n * }\n *\n * const grayscaleProcessor = new GrayScaleProcessor(100);\n *\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.addProcessor(grayscaleProcessor);\n * });\n *\n * document.getElementById('remove-button').onclick = () => {\n * Array.from(room.participants.values()).forEach(participant => {\n * const remoteVideoTrack = Array.from(participant.videoTracks.values())[0].track;\n * remoteVideoTrack.removeProcessor(grayscaleProcessor);\n * });\n * }\n */\n removeProcessor() {\n return super.removeProcessor.apply(this, arguments);\n }\n\n toString() {\n return `[RemoteVideoTrack #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * Update the subscribe {@link Track.Priority} of the {@link RemoteVideoTrack}.\n * @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};\n * If null, then the subscribe {@link Track.Priority} is cleared, which\n * means the {@link Track.Priority} set by the publisher is now the effective priority.\n * @returns {this}\n * @throws {RangeError}\n */\n setPriority(priority) {\n return super.setPriority(priority);\n }\n}\n\nfunction setupDocumentVisibilityTurnOff(removeVideoTrack) {\n function onVisibilityChanged() {\n maybeUpdateEnabledHint(removeVideoTrack);\n }\n\n documentVisibilityMonitor.onVisibilityChange(1, onVisibilityChanged);\n return () => {\n documentVisibilityMonitor.offVisibilityChange(1, onVisibilityChanged);\n };\n}\n\nfunction maybeUpdateEnabledHint(remoteVideoTrack) {\n if (remoteVideoTrack._clientTrackSwitchOffControl !== 'auto') {\n return;\n }\n\n const visibleElements = remoteVideoTrack._getAllAttachedElements().filter(el => !remoteVideoTrack._invisibleElements.has(el));\n const pipWindows = remoteVideoTrack._getAllAttachedElements().filter(el => remoteVideoTrack._elToPipWindows.has(el));\n\n // even when document is invisible we may have track playing in pip window.\n const enabled = pipWindows.length > 0 || (document.visibilityState === 'visible' && visibleElements.length > 0);\n\n if (enabled === true) {\n remoteVideoTrack._turnOffTimer.clear();\n remoteVideoTrack._setRenderHint({ enabled: true });\n } else if (!remoteVideoTrack._turnOffTimer.isSet) {\n // set the track to be turned off after some delay.\n remoteVideoTrack._turnOffTimer.start();\n }\n}\n\nfunction maybeUpdateDimensionHint(remoteVideoTrack) {\n if (remoteVideoTrack._contentPreferencesMode !== 'auto') {\n return;\n }\n\n const visibleElements = remoteVideoTrack._getAllAttachedElements().filter(el => !remoteVideoTrack._invisibleElements.has(el));\n const pipElements = remoteVideoTrack._getAllAttachedElements().map(el => {\n const pipWindow = remoteVideoTrack._elToPipWindows.get(el);\n return pipWindow ? { clientHeight: pipWindow.height, clientWidth: pipWindow.width } : { clientHeight: 0, clientWidth: 0 };\n });\n const totalElements = visibleElements.concat(pipElements);\n if (totalElements.length > 0) {\n const [{ clientHeight, clientWidth }] = totalElements.sort((el1, el2) =>\n el2.clientHeight + el2.clientWidth - el1.clientHeight - el1.clientWidth - 1);\n const renderDimensions = { height: clientHeight, width: clientWidth };\n remoteVideoTrack._setRenderHint({ renderDimensions });\n }\n}\n\n/**\n * @typedef {object} VideoContentPreferences\n * @property {VideoTrack.Dimensions} [renderDimensions] - Render Dimensions to request for the {@link RemoteVideoTrack}.\n */\n\n/**\n * The {@link RemoteVideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose\n * dimensions changed\n * @event RemoteVideoTrack#dimensionsChanged\n */\n\n/**\n * The {@link RemoteVideoTrack} was disabled, i.e. \"paused\".\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * disabled\n * @event RemoteVideoTrack#disabled\n */\n\n/**\n * The {@link RemoteVideoTrack} was enabled, i.e. \"resumed\".\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * enabled\n * @event RemoteVideoTrack#enabled\n */\n\n/**\n * The {@link RemoteVideoTrack} started. This means there is enough video data\n * to begin playback.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that started\n * @event RemoteVideoTrack#started\n */\n\n/**\n * A {@link RemoteVideoTrack} was switched off.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * switched off\n * @event RemoteVideoTrack#switchedOff\n */\n\n/**\n * A {@link RemoteVideoTrack} was switched on.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} that was\n * switched on\n * @event RemoteVideoTrack#switchedOn\n */\n\nmodule.exports = RemoteVideoTrack;\n", "'use strict';\n\nconst RemoteTrackPublication = require('./remotetrackpublication');\n\n/**\n * A {@link RemoteVideoTrackPublication} represents a {@link RemoteVideoTrack}\n * that has been published to a {@link Room}.\n * @property {Track.Kind} kind - \"video\"\n * @property {?RemoteVideoTrack} track - unless you have subscribed to the\n * {@link RemoteVideoTrack}, this property is null\n * @emits RemoteVideoTrackPublication#subscribed\n * @emits RemoteVideoTrackPublication#subscriptionFailed\n * @emits RemoteVideoTrackPublication#trackDisabled\n * @emits RemoteVideoTrackPublication#trackEnabled\n * @emits RemoteVideoTrackPublication#unsubscribed\n */\nclass RemoteVideoTrackPublication extends RemoteTrackPublication {\n /**\n * Construct a {@link RemoteVideoTrackPublication}.\n * @param {RemoteTrackPublicationSignaling} signaling - {@link RemoteTrackPublication} signaling\n * @param {RemoteTrackPublicationOptions} options - {@link RemoteTrackPublication}\n * options\n */\n constructor(signaling, options) {\n super(signaling, options);\n }\n\n toString() {\n return `[RemoteVideoTrackPublication #${this._instanceId}: ${this.trackSid}]`;\n }\n}\n\n/**\n * Your {@link LocalParticipant} subscribed to the {@link RemoteVideoTrack}.\n * @param {RemoteVideoTrack} track - the {@link RemoteVideoTrack} that was subscribed to\n * @event RemoteVideoTrackPublication#subscribed\n */\n\n/**\n * Your {@link LocalParticipant} failed to subscribe to the {@link RemoteVideoTrack}.\n * @param {TwilioError} error - the reason the {@link RemoteVideoTrack} could not be\n * subscribed to\n * @event RemoteVideoTrackPublication#subscriptionFailed\n */\n\n/**\n * The {@link RemoteVideoTrack} was disabled.\n * @event RemoteVideoTrackPublication#trackDisabled\n */\n\n/**\n * The {@link RemoteVideoTrack} was enabled.\n * @event RemoteVideoTrackPublication#trackEnabled\n */\n\n/**\n * Your {@link LocalParticipant} unsubscribed from the {@link RemoteVideoTrack}.\n * @param {RemoteVideoTrack} track - the {@link RemoteVideoTrack} that was unsubscribed from\n * @event RemoteVideoTrackPublication#unsubscribed\n */\n\nmodule.exports = RemoteVideoTrackPublication;\n", "'use strict';\n\nconst EventEmitter = require('./eventemitter');\nconst RemoteAudioTrack = require('./media/track/remoteaudiotrack');\nconst RemoteAudioTrackPublication = require('./media/track/remoteaudiotrackpublication');\nconst RemoteDataTrack = require('./media/track/remotedatatrack');\nconst RemoteDataTrackPublication = require('./media/track/remotedatatrackpublication');\nconst RemoteVideoTrack = require('./media/track/remotevideotrack');\nconst RemoteVideoTrackPublication = require('./media/track/remotevideotrackpublication');\nconst util = require('./util');\n\nlet nInstances = 0;\n\n/**\n * {@link NetworkQualityLevel} is a value from 0\u20135, inclusive, representing the\n * quality of a network connection.\n * @typedef {number} NetworkQualityLevel\n */\n\n/**\n * @extends EventEmitter\n * @property {Map} audioTracks -\n * The {@link Participant}'s {@link AudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link Participant}'s {@link DataTrackPublication}s.\n * @property {Participant.Identity} identity - The identity of the {@link Participant}\n * @property {?NetworkQualityLevel} networkQualityLevel - The\n * {@link Participant}'s current {@link NetworkQualityLevel}, if any\n * @property {?NetworkQualityStats} networkQualityStats - The\n * {@link Participant}'s current {@link NetworkQualityStats}, if any\n * @property {Participant.SID} sid - The {@link Participant}'s SID\n * @property {string} state - \"connected\", \"disconnected\" or \"reconnecting\"\n * @property {Map} tracks -\n * The {@link Participant}'s {@link TrackPublication}s\n * @property {Map} videoTracks -\n * The {@link Participant}'s {@link VideoTrackPublication}s\n * @emits Participant#disconnected\n * @emits Participant#networkQualityLevelChanged\n * @emits Participant#reconnected\n * @emits Participant#reconnecting\n * @emits Participant#trackDimensionsChanged\n * @emits Participant#trackStarted\n */\nclass Participant extends EventEmitter {\n /**\n * Construct a {@link Participant}.\n * @param {ParticipantSignaling} signaling\n * @param {object} [options]\n */\n constructor(signaling, options) {\n super();\n\n options = Object.assign({\n RemoteAudioTrack,\n RemoteAudioTrackPublication,\n RemoteDataTrack,\n RemoteDataTrackPublication,\n RemoteVideoTrack,\n RemoteVideoTrackPublication,\n tracks: []\n }, options);\n\n const indexed = indexTracksById(options.tracks);\n const log = options.log.createLog('default', this);\n const audioTracks = new Map(indexed.audioTracks);\n const dataTracks = new Map(indexed.dataTracks);\n const tracks = new Map(indexed.tracks);\n const videoTracks = new Map(indexed.videoTracks);\n\n Object.defineProperties(this, {\n _RemoteAudioTrack: {\n value: options.RemoteAudioTrack\n },\n _RemoteAudioTrackPublication: {\n value: options.RemoteAudioTrackPublication\n },\n _RemoteDataTrack: {\n value: options.RemoteDataTrack\n },\n _RemoteDataTrackPublication: {\n value: options.RemoteDataTrackPublication\n },\n _RemoteVideoTrack: {\n value: options.RemoteVideoTrack\n },\n _RemoteVideoTrackPublication: {\n value: options.RemoteVideoTrackPublication\n },\n _audioTracks: {\n value: audioTracks\n },\n _dataTracks: {\n value: dataTracks\n },\n _instanceId: {\n value: ++nInstances\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl,\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode,\n },\n _log: {\n value: log\n },\n _signaling: {\n value: signaling\n },\n _tracks: {\n value: tracks\n },\n _trackEventReemitters: {\n value: new Map()\n },\n _trackPublicationEventReemitters: {\n value: new Map()\n },\n _trackSignalingUpdatedEventCallbacks: {\n value: new Map()\n },\n _videoTracks: {\n value: videoTracks\n },\n audioTracks: {\n enumerable: true,\n value: new Map()\n },\n dataTracks: {\n enumerable: true,\n value: new Map()\n },\n identity: {\n enumerable: true,\n get() {\n return signaling.identity;\n }\n },\n networkQualityLevel: {\n enumerable: true,\n get() {\n return signaling.networkQualityLevel;\n }\n },\n networkQualityStats: {\n enumerable: true,\n get() {\n return signaling.networkQualityStats;\n }\n },\n sid: {\n enumerable: true,\n get() {\n return signaling.sid;\n }\n },\n state: {\n enumerable: true,\n get() {\n return signaling.state;\n }\n },\n tracks: {\n enumerable: true,\n value: new Map()\n },\n videoTracks: {\n enumerable: true,\n value: new Map()\n }\n });\n\n this._tracks.forEach(reemitTrackEvents.bind(null, this));\n signaling.on('networkQualityLevelChanged', () =>\n this.emit('networkQualityLevelChanged', this.networkQualityLevel,\n this.networkQualityStats &&\n (this.networkQualityStats.audio || this.networkQualityStats.video)\n ? this.networkQualityStats\n : null));\n reemitSignalingStateChangedEvents(this, signaling);\n log.info(`Created a new Participant${this.identity ? `: ${this.identity}` : ''}`);\n }\n\n /**\n * Get the {@link RemoteTrack} events to re-emit.\n * @private\n * @returns {Array>} events\n */\n _getTrackEvents() {\n return [\n ['dimensionsChanged', 'trackDimensionsChanged'],\n ['message', 'trackMessage'],\n ['started', 'trackStarted']\n ];\n }\n\n /**\n * @private\n */\n _getTrackPublicationEvents() {\n return [];\n }\n\n toString() {\n return `[Participant #${this._instanceId}: ${this.sid}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _addTrack(track, id) {\n const log = this._log;\n if (this._tracks.has(id)) {\n return null;\n }\n this._tracks.set(id, track);\n\n const tracksByKind = {\n audio: this._audioTracks,\n video: this._videoTracks,\n data: this._dataTracks\n }[track.kind];\n tracksByKind.set(id, track);\n reemitTrackEvents(this, track, id);\n\n log.info(`Added a new ${util.trackClass(track)}:`, id);\n log.debug(`${util.trackClass(track)}:`, track);\n\n return track;\n }\n\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _addTrackPublication(publication) {\n const log = this._log;\n if (this.tracks.has(publication.trackSid)) {\n return null;\n }\n this.tracks.set(publication.trackSid, publication);\n\n const trackPublicationsByKind = {\n audio: this.audioTracks,\n data: this.dataTracks,\n video: this.videoTracks\n }[publication.kind];\n trackPublicationsByKind.set(publication.trackSid, publication);\n reemitTrackPublicationEvents(this, publication);\n\n log.info(`Added a new ${util.trackPublicationClass(publication)}:`, publication.trackSid);\n log.debug(`${util.trackPublicationClass(publication)}:`, publication);\n return publication;\n }\n\n /**\n * @private\n */\n _handleTrackSignalingEvents() {\n const { _log: log, _clientTrackSwitchOffControl: clientTrackSwitchOffControl, _contentPreferencesMode: contentPreferencesMode } = this;\n const self = this;\n\n if (this.state === 'disconnected') {\n return;\n }\n\n const RemoteAudioTrack = this._RemoteAudioTrack;\n const RemoteAudioTrackPublication = this._RemoteAudioTrackPublication;\n const RemoteVideoTrack = this._RemoteVideoTrack;\n const RemoteVideoTrackPublication = this._RemoteVideoTrackPublication;\n const RemoteDataTrack = this._RemoteDataTrack;\n const RemoteDataTrackPublication = this._RemoteDataTrackPublication;\n const participantSignaling = this._signaling;\n\n function trackSignalingAdded(signaling) {\n const RemoteTrackPublication = {\n audio: RemoteAudioTrackPublication,\n data: RemoteDataTrackPublication,\n video: RemoteVideoTrackPublication\n }[signaling.kind];\n\n const publication = new RemoteTrackPublication(signaling, { log });\n self._addTrackPublication(publication);\n\n let isSubscribed = signaling.isSubscribed;\n if (isSubscribed) {\n trackSignalingSubscribed(signaling);\n }\n\n self._trackSignalingUpdatedEventCallbacks.set(signaling.sid, () => {\n if (isSubscribed !== signaling.isSubscribed) {\n isSubscribed = signaling.isSubscribed;\n if (isSubscribed) {\n trackSignalingSubscribed(signaling);\n return;\n }\n trackSignalingUnsubscribed(signaling);\n }\n });\n signaling.on('updated', self._trackSignalingUpdatedEventCallbacks.get(signaling.sid));\n }\n\n function trackSignalingRemoved(signaling) {\n if (signaling.isSubscribed) {\n signaling.setTrackTransceiver(null);\n }\n const updated = self._trackSignalingUpdatedEventCallbacks.get(signaling.sid);\n if (updated) {\n signaling.removeListener('updated', updated);\n self._trackSignalingUpdatedEventCallbacks.delete(signaling.sid);\n }\n const publication = self.tracks.get(signaling.sid);\n if (publication) {\n self._removeTrackPublication(publication);\n }\n }\n\n function trackSignalingSubscribed(signaling) {\n const { isEnabled, name, kind, sid, trackTransceiver, isSwitchedOff } = signaling;\n const RemoteTrack = {\n audio: RemoteAudioTrack,\n video: RemoteVideoTrack,\n data: RemoteDataTrack\n }[kind];\n\n const publication = self.tracks.get(sid);\n\n // NOTE(mroberts): It should never be the case that the TrackSignaling and\n // MediaStreamTrack or DataTrackReceiver kinds disagree; however, just in\n // case, we handle it here.\n if (!RemoteTrack || kind !== trackTransceiver.kind) {\n return;\n }\n\n const options = { log, name, clientTrackSwitchOffControl, contentPreferencesMode };\n const setPriority = newPriority => participantSignaling.updateSubscriberTrackPriority(sid, newPriority);\n const setRenderHint = renderHint => {\n if (signaling.isSubscribed) {\n participantSignaling.updateTrackRenderHint(sid, renderHint);\n }\n };\n const track = kind === 'data'\n ? new RemoteTrack(sid, trackTransceiver, options)\n : new RemoteTrack(sid, trackTransceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);\n\n self._addTrack(track, publication, trackTransceiver.id);\n }\n\n function trackSignalingUnsubscribed(signaling) {\n const [id, track] = Array.from(self._tracks.entries()).find(([, track]) => track.sid === signaling.sid);\n const publication = self.tracks.get(signaling.sid);\n if (track) {\n self._removeTrack(track, publication, id);\n }\n }\n\n participantSignaling.on('trackAdded', trackSignalingAdded);\n participantSignaling.on('trackRemoved', trackSignalingRemoved);\n\n participantSignaling.tracks.forEach(trackSignalingAdded);\n\n participantSignaling.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n log.debug('Removing event listeners');\n participantSignaling.removeListener('stateChanged', stateChanged);\n participantSignaling.removeListener('trackAdded', trackSignalingAdded);\n participantSignaling.removeListener('trackRemoved', trackSignalingRemoved);\n } else if (state === 'connected') {\n // NOTE(mmalavalli): Any transition to \"connected\" here is a result of\n // successful signaling reconnection, and not a first-time establishment\n // of the signaling connection.\n log.info('reconnected');\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => self.emit('reconnected'), 0);\n\n }\n });\n }\n\n /**\n * @private\n * @param {RemoteTrack} track\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _removeTrack(track, id) {\n if (!this._tracks.has(id)) {\n return null;\n }\n this._tracks.delete(id);\n\n const tracksByKind = {\n audio: this._audioTracks,\n video: this._videoTracks,\n data: this._dataTracks\n }[track.kind];\n tracksByKind.delete(id);\n\n const reemitters = this._trackEventReemitters.get(id) || new Map();\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n\n const log = this._log;\n log.info(`Removed a ${util.trackClass(track)}:`, id);\n log.debug(`${util.trackClass(track)}:`, track);\n return track;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _removeTrackPublication(publication) {\n publication = this.tracks.get(publication.trackSid);\n if (!publication) {\n return null;\n }\n this.tracks.delete(publication.trackSid);\n\n const trackPublicationsByKind = {\n audio: this.audioTracks,\n data: this.dataTracks,\n video: this.videoTracks\n }[publication.kind];\n trackPublicationsByKind.delete(publication.trackSid);\n\n const reemitters = this._trackPublicationEventReemitters.get(publication.trackSid) || new Map();\n reemitters.forEach((reemitter, event) => {\n publication.removeListener(event, reemitter);\n });\n\n const log = this._log;\n log.info(`Removed a ${util.trackPublicationClass(publication)}:`, publication.trackSid);\n log.debug(`${util.trackPublicationClass(publication)}:`, publication);\n return publication;\n }\n\n toJSON() {\n return util.valueToJSON(this);\n }\n}\n\n/**\n * A {@link Participant.SID} is a 34-character string starting with \"PA\"\n * that uniquely identifies a {@link Participant}.\n * @type string\n * @typedef Participant.SID\n */\n\n/**\n * A {@link Participant.Identity} is a string that identifies a\n * {@link Participant}. You can think of it like a name.\n * @typedef {string} Participant.Identity\n */\n\n/**\n * The {@link Participant} has disconnected.\n * @param {Participant} participant - The {@link Participant} that disconnected.\n * @event Participant#disconnected\n */\n\n/**\n * The {@link Participant}'s {@link NetworkQualityLevel} changed.\n * @param {NetworkQualityLevel} networkQualityLevel - The new\n * {@link NetworkQualityLevel}\n * @param {?NetworkQualityStats} networkQualityStats - The {@link NetworkQualityStats}\n * based on which {@link NetworkQualityLevel} is calculated, if any\n * @event Participant#networkQualityLevelChanged\n */\n\n/**\n * The {@link Participant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event Participant#reconnected\n */\n\n/**\n * The {@link Participant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event Participant#reconnecting\n */\n\n/**\n * One of the {@link Participant}'s {@link VideoTrack}'s dimensions changed.\n * @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed\n * @event Participant#trackDimensionsChanged\n */\n\n/**\n * One of the {@link Participant}'s {@link Track}s started.\n * @param {Track} track - The {@link Track} that started\n * @event Participant#trackStarted\n */\n\n/**\n * Indexed {@link Track}s by {@link Track.ID}.\n * @typedef {object} IndexedTracks\n * @property {Array<{0: Track.ID, 1: AudioTrack}>} audioTracks - Indexed\n * {@link AudioTrack}s\n * @property {Array<{0: Track.ID, 1: DataTrack}>} dataTracks - Indexed\n * {@link DataTrack}s\n * @property {Array<{0: Track.ID, 1: Track}>} tracks - Indexed {@link Track}s\n * @property {Array<{0: Track.ID, 1: VideoTrack}>} videoTracks - Indexed\n * {@link VideoTrack}s\n * @private\n */\n\n/**\n * Index tracks by {@link Track.ID}.\n * @param {Array} tracks\n * @returns {IndexedTracks}\n * @private\n */\nfunction indexTracksById(tracks) {\n const indexedTracks = tracks.map(track => [track.id, track]);\n const indexedAudioTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'audio');\n const indexedVideoTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'video');\n const indexedDataTracks = indexedTracks.filter(keyValue => keyValue[1].kind === 'data');\n\n return {\n audioTracks: indexedAudioTracks,\n dataTracks: indexedDataTracks,\n tracks: indexedTracks,\n videoTracks: indexedVideoTracks\n };\n}\n\n/**\n * Re-emit {@link ParticipantSignaling} 'stateChanged' events.\n * @param {Participant} participant\n * @param {ParticipantSignaling} signaling\n * @private\n */\nfunction reemitSignalingStateChangedEvents(participant, signaling) {\n const log = participant._log;\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n // Reemit state transition events from the ParticipantSignaling.\n signaling.on('stateChanged', function stateChanged(state) {\n log.debug('Transitioned to state:', state);\n participant.emit(state, participant);\n if (state === 'disconnected') {\n log.debug('Removing Track event reemitters');\n signaling.removeListener('stateChanged', stateChanged);\n\n participant._tracks.forEach(track => {\n const reemitters = participant._trackEventReemitters.get(track.id);\n if (track && reemitters) {\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n }\n });\n\n // eslint-disable-next-line no-warning-comments\n // TODO(joma): Removing this introduced unit test failures in the RemoteParticipant.\n // Investigate further before removing.\n signaling.tracks.forEach(trackSignaling => {\n const track = participant._tracks.get(trackSignaling.id);\n const reemitters = participant._trackEventReemitters.get(trackSignaling.id);\n if (track && reemitters) {\n reemitters.forEach((reemitter, event) => {\n track.removeListener(event, reemitter);\n });\n }\n });\n\n participant._trackEventReemitters.clear();\n\n participant.tracks.forEach(publication => {\n participant._trackPublicationEventReemitters.get(publication.trackSid)\n .forEach((reemitter, event) => {\n publication.removeListener(event, reemitter);\n });\n });\n participant._trackPublicationEventReemitters.clear();\n }\n });\n}\n\n/**\n * Re-emit {@link Track} events.\n * @param {Participant} participant\n * @param {Track} track\n * @param {Track.ID} id\n * @private\n */\nfunction reemitTrackEvents(participant, track, id) {\n const trackEventReemitters = new Map();\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n participant._getTrackEvents().forEach(eventPair => {\n const trackEvent = eventPair[0];\n const participantEvent = eventPair[1];\n\n trackEventReemitters.set(trackEvent, function() {\n const args = [participantEvent].concat([].slice.call(arguments));\n return participant.emit(...args);\n });\n\n track.on(trackEvent, trackEventReemitters.get(trackEvent));\n });\n\n participant._trackEventReemitters.set(id, trackEventReemitters);\n}\n\n/**\n * Re-emit {@link TrackPublication} events.\n * @private\n * @param {Participant} participant\n * @param {TrackPublication} publication\n */\nfunction reemitTrackPublicationEvents(participant, publication) {\n const publicationEventReemitters = new Map();\n\n if (participant.state === 'disconnected') {\n return;\n }\n\n participant._getTrackPublicationEvents().forEach(([publicationEvent, participantEvent]) => {\n publicationEventReemitters.set(publicationEvent, (...args) => {\n participant.emit(participantEvent, ...args, publication);\n });\n publication.on(publicationEvent, publicationEventReemitters.get(publicationEvent));\n });\n\n participant._trackPublicationEventReemitters.set(publication.trackSid, publicationEventReemitters);\n}\n\nmodule.exports = Participant;\n", "'use strict';\n\nconst { MediaStreamTrack } = require('./webrtc');\nconst { asLocalTrack, asLocalTrackPublication, trackClass } = require('./util');\nconst { typeErrors: E, trackPriority } = require('./util/constants');\nconst { validateLocalTrack } = require('./util/validate');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst LocalAudioTrackPublication = require('./media/track/localaudiotrackpublication');\nconst LocalDataTrackPublication = require('./media/track/localdatatrackpublication');\nconst LocalVideoTrackPublication = require('./media/track/localvideotrackpublication');\nconst Participant = require('./participant');\n\n/**\n * A {@link LocalParticipant} represents the local {@link Participant} in a\n * {@link Room}.\n * @extends Participant\n * @property {Map} audioTracks -\n * The {@link LocalParticipant}'s {@link LocalAudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link LocalParticipant}'s {@link LocalDataTrackPublication}s\n * @property {Map} tracks -\n * The {@link LocalParticipant}'s {@link LocalTrackPublication}s\n * @property {Map} videoTracks -\n * The {@link LocalParticipant}'s {@link LocalVideoTrackPublication}s\n * @property {string} signalingRegion - The geographical region of the\n * signaling edge the {@link LocalParticipant} is connected to.\n *\n * @emits RemoteParticipant#reconnected\n * @emits RemoteParticipant#reconnecting\n * @emits LocalParticipant#trackDimensionsChanged\n * @emits LocalParticipant#trackDisabled\n * @emits LocalParticipant#trackEnabled\n * @emits LocalParticipant#trackPublicationFailed\n * @emits LocalParticipant#trackPublished\n * @emits LocalParticipant#trackStarted\n * @emits LocalParticipant#trackStopped\n * @emits LocalParticipant#trackWarning\n * @emits LocalParticipant#trackWarningsCleared\n */\nclass LocalParticipant extends Participant {\n /**\n * Construct a {@link LocalParticipant}.\n * @param {ParticipantSignaling} signaling\n * @param {Array} localTracks\n * @param {Object} options\n */\n constructor(signaling, localTracks, options) {\n options = Object.assign({\n LocalAudioTrack,\n LocalVideoTrack,\n LocalDataTrack,\n MediaStreamTrack,\n LocalAudioTrackPublication,\n LocalVideoTrackPublication,\n LocalDataTrackPublication,\n shouldStopLocalTracks: false,\n tracks: localTracks\n }, options);\n\n const tracksToStop = options.shouldStopLocalTracks\n ? new Set(localTracks.filter(localTrack => localTrack.kind !== 'data'))\n : new Set();\n\n super(signaling, options);\n\n Object.defineProperties(this, {\n _eventObserver: {\n value: options.eventObserver\n },\n _LocalAudioTrack: {\n value: options.LocalAudioTrack\n },\n _LocalDataTrack: {\n value: options.LocalDataTrack\n },\n _LocalVideoTrack: {\n value: options.LocalVideoTrack\n },\n _MediaStreamTrack: {\n value: options.MediaStreamTrack\n },\n _LocalAudioTrackPublication: {\n value: options.LocalAudioTrackPublication\n },\n _LocalDataTrackPublication: {\n value: options.LocalDataTrackPublication\n },\n _LocalVideoTrackPublication: {\n value: options.LocalVideoTrackPublication\n },\n _tracksToStop: {\n value: tracksToStop\n },\n signalingRegion: {\n enumerable: true,\n get() {\n return signaling.signalingRegion;\n }\n }\n });\n\n this._handleTrackSignalingEvents();\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.ID} id\n * @param {Track.Priority} priority\n * @returns {?LocalTrack}\n */\n _addTrack(track, id, priority) {\n const addedTrack = super._addTrack(track, id);\n if (addedTrack && this.state !== 'disconnected') {\n this._addLocalTrack(track, priority);\n }\n return addedTrack;\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.Priority} priority\n * @returns {void}\n */\n _addLocalTrack(track, priority) {\n // check if track has noise cancellation enabled.\n const vendor = track.noiseCancellation?.vendor;\n this._signaling.addTrack(track._trackSender, track.name, priority, vendor);\n this._log.info(`Added a new ${trackClass(track, true)}:`, track.id);\n this._log.debug(`${trackClass(track, true)}:`, track);\n }\n\n /**\n * @private\n * @param {LocalTrack} track\n * @param {Track.ID} id\n * @returns {?LocalTrack}\n */\n _removeTrack(track, id) {\n const removedTrack = super._removeTrack(track, id);\n if (removedTrack && this.state !== 'disconnected') {\n this._signaling.removeTrack(track._trackSender);\n this._log.info(`Removed a ${trackClass(track, true)}:`, track.id);\n this._log.debug(`${trackClass(track, true)}:`, track);\n }\n return removedTrack;\n }\n\n /**\n * Get the {@link LocalTrack} events to re-emit.\n * @private\n * @returns {Array>} events\n */\n _getTrackEvents() {\n return super._getTrackEvents.call(this).concat([\n ['disabled', 'trackDisabled'],\n ['enabled', 'trackEnabled'],\n ['stopped', 'trackStopped']\n ]);\n }\n\n toString() {\n return `[LocalParticipant #${this._instanceId}${this.sid ? `: ${this.sid}` : ''}]`;\n }\n\n /**\n * @private\n */\n _handleTrackSignalingEvents() {\n const log = this._log;\n\n if (this.state === 'disconnected') {\n return;\n }\n\n const localTrackDisabled = localTrack => {\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.disable();\n log.debug(`Disabled the ${trackClass(localTrack, true)}:`, localTrack.id);\n }\n };\n\n const localTrackEnabled = localTrack => {\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.enable();\n log.debug(`Enabled the ${trackClass(localTrack, true)}:`, localTrack.id);\n }\n };\n\n const localTrackStopped = localTrack => {\n // NOTE(mroberts): We shouldn't need to check for `stop`, since DataTracks\n // do not emit \"stopped\".\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (trackSignaling) {\n trackSignaling.stop();\n }\n return trackSignaling;\n };\n\n const stateChanged = state => {\n log.debug('Transitioned to state:', state);\n if (state === 'disconnected') {\n log.debug('Removing LocalTrack event listeners');\n this._signaling.removeListener('stateChanged', stateChanged);\n this.removeListener('trackDisabled', localTrackDisabled);\n this.removeListener('trackEnabled', localTrackEnabled);\n this.removeListener('trackStopped', localTrackStopped);\n\n // NOTE(mmalavalli): Remove the stale MediaTrackSender clones so that we\n // do not call replaceTrack() on their RTCRtpSenders.\n this._tracks.forEach(track => {\n const trackSignaling = localTrackStopped(track);\n if (trackSignaling) {\n track._trackSender.removeClone(trackSignaling._trackTransceiver);\n }\n });\n\n log.info(`LocalParticipant disconnected. Stopping ${this._tracksToStop.size} automatically-acquired LocalTracks`);\n this._tracksToStop.forEach(track => {\n track.stop();\n });\n } else if (state === 'connected') {\n // NOTE(mmalavalli): Any transition to \"connected\" here is a result of\n // successful signaling reconnection, and not a first-time establishment\n // of the signaling connection.\n log.info('reconnected');\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => this.emit('reconnected'), 0);\n }\n };\n\n this.on('trackDisabled', localTrackDisabled);\n this.on('trackEnabled', localTrackEnabled);\n this.on('trackStopped', localTrackStopped);\n\n this._signaling.on('stateChanged', stateChanged);\n\n this._tracks.forEach(track => {\n this._addLocalTrack(track, trackPriority.PRIORITY_STANDARD);\n this._getOrCreateLocalTrackPublication(track).catch(error => {\n // Just log a warning for now.\n log.warn(`Failed to get or create LocalTrackPublication for ${track}:`, error);\n });\n });\n }\n\n /**\n * @private\n * @param {LocalTrack} localTrack\n * @returns {Promise}\n */\n _getOrCreateLocalTrackPublication(localTrack) {\n let localTrackPublication = getTrackPublication(this.tracks, localTrack);\n if (localTrackPublication) {\n return Promise.resolve(localTrackPublication);\n }\n\n const log = this._log;\n const self = this;\n\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n if (!trackSignaling) {\n return Promise.reject(new Error(`Unexpected error: The ${localTrack} cannot be published`));\n }\n\n return new Promise((resolve, reject) => {\n function updated() {\n const error = trackSignaling.error;\n if (error) {\n trackSignaling.removeListener('updated', updated);\n log.warn(`Failed to publish the ${trackClass(localTrack, true)}: ${error.message}`);\n self._removeTrack(localTrack, localTrack.id);\n setTimeout(() => {\n self.emit('trackPublicationFailed', error, localTrack);\n });\n reject(error);\n return;\n }\n\n if (!self._tracks.has(localTrack.id)) {\n trackSignaling.removeListener('updated', updated);\n reject(new Error(`The ${localTrack} was unpublished`));\n return;\n }\n\n const sid = trackSignaling.sid;\n if (!sid) {\n return;\n }\n\n trackSignaling.removeListener('updated', updated);\n\n const options = {\n log,\n LocalAudioTrackPublication: self._LocalAudioTrackPublication,\n LocalDataTrackPublication: self._LocalDataTrackPublication,\n LocalVideoTrackPublication: self._LocalVideoTrackPublication\n };\n\n localTrackPublication = getTrackPublication(self.tracks, localTrack);\n\n const warningHandler = twilioWarningName =>\n self.emit('trackWarning', twilioWarningName, localTrackPublication);\n\n const warningsClearedHandler = () =>\n self.emit('trackWarningsCleared', localTrackPublication);\n\n const unpublish = publication => {\n localTrackPublication.removeListener('trackWarning', warningHandler);\n localTrackPublication.removeListener('trackWarningsCleared', warningsClearedHandler);\n self.unpublishTrack(publication.track);\n };\n\n if (!localTrackPublication) {\n localTrackPublication = asLocalTrackPublication(localTrack, trackSignaling, unpublish, options);\n self._addTrackPublication(localTrackPublication);\n }\n\n localTrackPublication.on('warning', warningHandler);\n localTrackPublication.on('warningsCleared', warningsClearedHandler);\n\n const { state } = self._signaling;\n if (state === 'connected' || state === 'connecting') {\n if (localTrack._processorEventObserver) {\n localTrack._processorEventObserver.on('event', event => {\n self._eventObserver.emit('event', {\n name: event.name,\n payload: event.data,\n group: 'video-processor',\n level: 'info'\n });\n });\n }\n\n // NOTE(csantos): For tracks created before joining a room or already joined but about to publish it\n if (localTrack.processedTrack) {\n localTrack._captureFrames();\n localTrack._setSenderMediaStreamTrack(true);\n }\n }\n if (state === 'connected') {\n setTimeout(() => {\n self.emit('trackPublished', localTrackPublication);\n });\n }\n resolve(localTrackPublication);\n }\n\n trackSignaling.on('updated', updated);\n });\n }\n\n /**\n * Publishes a {@link LocalTrack} to the {@link Room}.\n * @param {LocalTrack} localTrack - The {@link LocalTrack} to publish\n * @param {LocalTrackPublishOptions} [options] - The {@link LocalTrackPublishOptions}\n * for publishing the {@link LocalTrack}\n * @returns {Promise} - Resolves with the corresponding\n * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if either the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}\n * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16.\n * @throws {TypeError}\n * @throws {RangeError}\n * @example\n * var Video = require('twilio-video');\n *\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * return Video.createLocalVideoTrack({\n * name: 'camera'\n * }).then(function(localVideoTrack) {\n * return room.localParticipant.publishTrack(localVideoTrack, {\n * priority: 'high'\n * });\n * });\n * }).then(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName\n * + '\" was successfully published with priority \"'\n * * publication.priority + '\"');\n * });\n *//**\n * Publishes a MediaStreamTrack to the {@link Room}.\n * @param {MediaStreamTrack} mediaStreamTrack - The MediaStreamTrack\n * to publish; if a corresponding {@link LocalAudioTrack} or\n * {@link LocalVideoTrack} has not yet been published, this method will\n * construct one\n * @param {MediaStreamTrackPublishOptions} [options] - The options for publishing\n * the MediaStreamTrack\n * @returns {Promise} - Resolves with the corresponding\n * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}\n * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16.\n * @throws {TypeError}\n * @throws {RangeError}\n * @example\n * var Video = require('twilio-video');\n *\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * return navigator.mediaDevices.getUserMedia({\n * video: true\n * }).then(function(mediaStream) {\n * var mediaStreamTrack = mediaStream.getTracks()[0];\n * return room.localParticipant.publishTrack(mediaStreamTrack, {\n * name: 'camera',\n * priority: 'high'\n * });\n * });\n * }).then(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName\n * + '\" was successfully published with priority \"'\n * * publication.priority + '\"');\n * });\n */\n publishTrack(localTrackOrMediaStreamTrack, options) {\n const trackPublication = getTrackPublication(this.tracks, localTrackOrMediaStreamTrack);\n if (trackPublication) {\n return Promise.resolve(trackPublication);\n }\n\n options = Object.assign({\n log: this._log,\n priority: trackPriority.PRIORITY_STANDARD,\n LocalAudioTrack: this._LocalAudioTrack,\n LocalDataTrack: this._LocalDataTrack,\n LocalVideoTrack: this._LocalVideoTrack,\n MediaStreamTrack: this._MediaStreamTrack\n }, options);\n\n let localTrack;\n try {\n localTrack = asLocalTrack(localTrackOrMediaStreamTrack, options);\n } catch (error) {\n return Promise.reject(error);\n }\n\n const noiseCancellation = localTrack.noiseCancellation;\n const allowedAudioProcessors = this._signaling.audioProcessors;\n if (noiseCancellation && !allowedAudioProcessors.includes(noiseCancellation.vendor)) {\n this._log.warn(`${noiseCancellation.vendor} is not supported in this room. disabling it permanently`);\n noiseCancellation.disablePermanently();\n }\n\n const priorityValues = Object.values(trackPriority);\n if (!priorityValues.includes(options.priority)) {\n // eslint-disable-next-line new-cap\n return Promise.reject(E.INVALID_VALUE('LocalTrackPublishOptions.priority', priorityValues));\n }\n\n let addedLocalTrack = this._addTrack(localTrack, localTrack.id, options.priority)\n || this._tracks.get(localTrack.id);\n\n return this._getOrCreateLocalTrackPublication(addedLocalTrack);\n }\n\n /**\n * Publishes multiple {@link LocalTrack}s to the {@link Room}.\n * @param {Array} tracks - The {@link LocalTrack}s\n * to publish; for any MediaStreamTracks provided, if a corresponding\n * {@link LocalAudioTrack} or {@link LocalVideoTrack} has not yet been\n * published, this method will construct one\n * @returns {Promise>} - The resulting\n * {@link LocalTrackPublication}s if successful; In a Large Group Room (Maximum\n * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError}\n * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}s\n * along with the published Tracks exceeds 16.\n * @throws {TypeError}\n */\n publishTracks(tracks) {\n if (!Array.isArray(tracks)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n return Promise.all(tracks.map(this.publishTrack, this));\n }\n\n setBandwidthProfile() {\n this._log.warn('setBandwidthProfile is not implemented yet and may be available in future versions of twilio-video.js');\n }\n\n /**\n * Sets the {@link NetworkQualityVerbosity} for the {@link LocalParticipant} and\n * {@link RemoteParticipant}s. It does nothing if Network Quality is not enabled\n * while calling {@link connect}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - The new\n * {@link NetworkQualityConfiguration}; If either or both of the local and\n * remote {@link NetworkQualityVerbosity} values are absent, then the corresponding\n * existing values are retained\n * @returns {this}\n * @example\n * // Update verbosity levels for both LocalParticipant and RemoteParticipants\n * localParticipant.setNetworkQualityConfiguration({\n * local: 1,\n * remote: 2\n * });\n * @example\n * // Update verbosity level for only the LocalParticipant\n * localParticipant.setNetworkQualityConfiguration({\n * local: 1\n * });\n * @example\n * // Update verbosity level for only the RemoteParticipants\n * localParticipant.setNetworkQualityConfiguration({\n * remote: 2\n * });\n */\n setNetworkQualityConfiguration(networkQualityConfiguration) {\n if (typeof networkQualityConfiguration !== 'object'\n || networkQualityConfiguration === null) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('networkQualityConfiguration', 'NetworkQualityConfiguration');\n }\n ['local', 'remote'].forEach(prop => {\n if (prop in networkQualityConfiguration && (typeof networkQualityConfiguration[prop] !== 'number' || isNaN(networkQualityConfiguration[prop]))) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE(`networkQualityConfiguration.${prop}`, 'number');\n }\n });\n this._signaling.setNetworkQualityConfiguration(networkQualityConfiguration);\n return this;\n }\n\n /**\n * Set the {@link LocalParticipant}'s {@link EncodingParameters}.\n * @param {?EncodingParameters} [encodingParameters] - The new\n * {@link EncodingParameters}; If null, then the bitrate limits are removed;\n * If not specified, then the existing bitrate limits are preserved\n * @returns {this}\n * @throws {TypeError}\n */\n setParameters(encodingParameters) {\n if (typeof encodingParameters !== 'undefined'\n && typeof encodingParameters !== 'object') {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('encodingParameters',\n 'EncodingParameters, null or undefined');\n }\n\n if (encodingParameters) {\n if (this._signaling.getParameters().adaptiveSimulcast && encodingParameters.maxVideoBitrate) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('encodingParameters', 'encodingParameters.maxVideoBitrate is not compatible with \"preferredVideoCodecs=auto\"');\n }\n\n ['maxAudioBitrate', 'maxVideoBitrate'].forEach(prop => {\n if (typeof encodingParameters[prop] !== 'undefined'\n && typeof encodingParameters[prop] !== 'number'\n && encodingParameters[prop] !== null) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE(`encodingParameters.${prop}`, 'number, null or undefined');\n }\n });\n } else if (encodingParameters === null) {\n encodingParameters = { maxAudioBitrate: null, maxVideoBitrate: null };\n }\n\n this._signaling.setParameters(encodingParameters);\n return this;\n }\n\n /**\n * Stops publishing a {@link LocalTrack} to the {@link Room}.\n * @param {LocalTrack|MediaStreamTrack} track - The {@link LocalTrack}\n * to stop publishing; if a MediaStreamTrack is provided, this method\n * looks up the corresponding {@link LocalAudioTrack} or\n * {@link LocalVideoTrack} to stop publishing\n * @returns {?LocalTrackPublication} - The corresponding\n * {@link LocalTrackPublication} if the {@link LocalTrack} was previously\n * published, null otherwise\n * @throws {TypeError}\n */\n unpublishTrack(track) {\n validateLocalTrack(track, {\n LocalAudioTrack: this._LocalAudioTrack,\n LocalDataTrack: this._LocalDataTrack,\n LocalVideoTrack: this._LocalVideoTrack,\n MediaStreamTrack: this._MediaStreamTrack\n });\n\n let localTrack = this._tracks.get(track.id);\n if (!localTrack) {\n return null;\n }\n\n const trackSignaling = this._signaling.getPublication(localTrack._trackSender);\n trackSignaling.publishFailed(new Error(`The ${localTrack} was unpublished`));\n\n localTrack = this._removeTrack(localTrack, localTrack.id);\n if (!localTrack) {\n return null;\n }\n\n const localTrackPublication = getTrackPublication(this.tracks, localTrack);\n if (localTrackPublication) {\n this._removeTrackPublication(localTrackPublication);\n }\n return localTrackPublication;\n }\n\n /**\n * Stops publishing multiple {@link LocalTrack}s to the {@link Room}.\n * @param {Array} tracks - The {@link LocalTrack}s\n * to stop publishing; for any MediaStreamTracks provided, this method looks\n * up the corresponding {@link LocalAudioTrack} or {@link LocalVideoTrack} to\n * stop publishing\n * @returns {Array} - The corresponding\n * {@link LocalTrackPublication}s that were successfully unpublished\n * @throws {TypeError}\n */\n unpublishTracks(tracks) {\n if (!Array.isArray(tracks)) {\n // eslint-disable-next-line new-cap\n throw E.INVALID_TYPE('tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack');\n }\n\n return tracks.reduce((unpublishedTracks, track) => {\n const unpublishedTrack = this.unpublishTrack(track);\n return unpublishedTrack ? unpublishedTracks.concat(unpublishedTrack) : unpublishedTracks;\n }, []);\n }\n}\n\n/**\n * The {@link LocalParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event LocalParticipant#reconnected\n */\n\n/**\n * The {@link LocalParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event LocalParticipant#reconnecting\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalVideoTrack}'s dimensions changed.\n * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} whose dimensions changed\n * @event LocalParticipant#trackDimensionsChanged\n */\n\n/**\n * A {@link LocalTrack} was disabled by the {@link LocalParticipant}.\n * @param {LocalTrack} track - The {@link LocalTrack} that was disabled\n * @event LocalParticipant#trackDisabled\n */\n\n/**\n * A {@link LocalTrack} was enabled by the {@link LocalParticipant}.\n * @param {LocalTrack} track - The {@link LocalTrack} that was enabled\n * @event LocalParticipant#trackEnabled\n */\n\n/**\n * A {@link LocalTrack} failed to publish. Check the error message for more\n * information. In a Large Group Room (Maximum Participants greater than 50),\n * this event is raised with a {@link ParticipantMaxTracksExceededError} either\n * when attempting to publish the {@link LocalTrack} will exceed the Maximum Published\n * Tracks limit of 16, or the {@link LocalTrack} is part of a set of {@link LocalTrack}s\n * which along with the published Tracks exceeds 16.\n * @param {TwilioError} error - A {@link TwilioError} explaining why publication\n * failed\n * @param {LocalTrack} localTrack - The {@link LocalTrack} that failed to\n * publish\n * @event LocalParticipant#trackPublicationFailed\n */\n\n/**\n * A {@link LocalTrack} was successfully published.\n * @param {LocalTrackPublication} publication - The resulting\n * {@link LocalTrackPublication} for the published {@link LocalTrack}\n * @event LocalParticipant#trackPublished\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrack}s started.\n * @param {LocalTrack} track - The {@link LocalTrack} that started\n * @event LocalParticipant#trackStarted\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrack}s stopped, either\n * because {@link LocalTrack#stop} was called or because the underlying\n * MediaStreamTrack ended).\n * @param {LocalTrack} track - The {@link LocalTrack} that stopped\n * @event LocalParticipant#trackStopped\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {string} name - The warning that was raised.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that encountered the warning.\n * @event LocalParticipant#trackWarning\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that cleared all warnings.\n * @event LocalParticipant#trackWarningsCleared\n */\n\n/**\n * Outgoing media encoding parameters.\n * @typedef {object} EncodingParameters\n * @property {?number} [maxAudioBitrate] - Max outgoing audio bitrate (bps);\n * If not specified, retains the existing bitrate limit; A null or a\n * 0 value removes any previously set bitrate limit; This value is set\n * as a hint for variable bitrate codecs, but will not take effect for fixed bitrate\n * codecs; Based on our tests, Chrome, Firefox and Safari support a bitrate range of\n * 12000 bps to 256000 bps for Opus codec; This parameter has no effect on iSAC, PCMU\n * and PCMA codecs\n * @property {?number} [maxVideoBitrate] - Max outgoing video bitrate (bps);\n * If not specified, retains the existing bitrate limit; A null or\n * a 0 value removes any previously set bitrate limit; This value is\n * set as a hint for variable bitrate codecs, but will not take effect for fixed\n * bitrate codecs; Based on our tests, Chrome, Firefox and Safari all seem to support\n * an average bitrate range of 20000 bps (20 kbps) to 8000000 bps (8 mbps) for a\n * 720p VideoTrack.\n * Note: this limit is not applied for screen share tracks published on Chrome.\n */\n\n/**\n * Options for publishing a {@link LocalTrack}.\n * @typedef {object} LocalTrackPublishOptions\n * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack}\n * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is\n * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no\n * effect in Peer-to-Peer Rooms; It defaults to \"standard\" when not provided\n */\n\n/**\n * Options for publishing a {@link MediaStreamTrack}.\n * @typedef {LocalTrackOptions} MediaStreamTrackPublishOptions\n * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack}\n * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is\n * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no\n * effect in Peer-to-Peer Rooms; It defaults to \"standard\" when not provided\n */\n\n/**\n * @private\n * @param {Map} trackPublications\n * @param {LocalTrack|MediaStreamTrack} track\n * @returns {?LocalTrackPublication} trackPublication\n */\nfunction getTrackPublication(trackPublications, track) {\n return Array.from(trackPublications.values()).find(trackPublication => trackPublication.track === track\n || trackPublication.track.mediaStreamTrack === track) || null;\n}\n\nmodule.exports = LocalParticipant;\n", "// eslint-disable-next-line no-warning-comments\n// TODO(mroberts): This should be described as implementing some\n// InsightsPublisher interface.\n'use strict';\n\n/**\n * Null Insights publisher.\n */\nclass InsightsPublisher {\n constructor() {\n Object.defineProperties(this, {\n _connected: {\n writable: true,\n value: true\n }\n });\n }\n\n /**\n * Connect\n * @returns {void}\n */\n connect() {\n }\n\n /**\n * Disconnect.\n * @returns {boolean}\n */\n disconnect() {\n if (this._connected) {\n this._connected = false;\n return true;\n }\n return false;\n }\n\n /**\n * Publish.\n * @returns {boolean}\n */\n publish() {\n return this._connected;\n }\n}\n\nmodule.exports = InsightsPublisher;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\nconst {\n DEFAULT_NQ_LEVEL_LOCAL,\n DEFAULT_NQ_LEVEL_REMOTE,\n MAX_NQ_LEVEL\n} = require('./util/constants');\n\nconst { inRange } = require('./util');\n\n/**\n * {@link NetworkQualityConfigurationImpl} represents an object which notifies its\n * listeners of any changes in the values of its properties.\n * @extends EventEmitter\n * @implements NetworkQualityConfiguration\n * @property {?NetworkQualityVerbosity} local - Verbosity level for {@link LocalParticipant}\n * @property {?NetworkQualityVerbosity} remote - Verbosity level for {@link RemoteParticipant}s\n */\nclass NetworkQualityConfigurationImpl extends EventEmitter {\n /**\n * Construct an {@link NetworkQualityConfigurationImpl}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - Initial {@link NetworkQualityConfiguration}\n */\n constructor(networkQualityConfiguration) {\n super();\n\n networkQualityConfiguration = Object.assign({\n local: DEFAULT_NQ_LEVEL_LOCAL,\n remote: DEFAULT_NQ_LEVEL_REMOTE\n }, networkQualityConfiguration);\n\n Object.defineProperties(this, {\n local: {\n value: inRange(networkQualityConfiguration.local, DEFAULT_NQ_LEVEL_LOCAL, MAX_NQ_LEVEL)\n ? networkQualityConfiguration.local\n : DEFAULT_NQ_LEVEL_LOCAL,\n writable: true\n },\n remote: {\n value: inRange(networkQualityConfiguration.remote, DEFAULT_NQ_LEVEL_REMOTE, MAX_NQ_LEVEL)\n ? networkQualityConfiguration.remote\n : DEFAULT_NQ_LEVEL_REMOTE,\n writable: true\n }\n });\n }\n\n /**\n * Update the verbosity levels for network quality information for\n * {@link LocalParticipant} and {@link RemoteParticipant} with those\n * in the given {@link NetworkQualityConfiguration}.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration - The new {@link NetworkQualityConfiguration}\n */\n update(networkQualityConfiguration) {\n networkQualityConfiguration = Object.assign({\n local: this.local,\n remote: this.remote\n }, networkQualityConfiguration);\n\n [\n ['local', DEFAULT_NQ_LEVEL_LOCAL, 3],\n ['remote', DEFAULT_NQ_LEVEL_REMOTE, 3]\n ].forEach(([localOrRemote, min, max]) => {\n this[localOrRemote] = typeof networkQualityConfiguration[localOrRemote] === 'number'\n && inRange(networkQualityConfiguration[localOrRemote], min, max)\n ? networkQualityConfiguration[localOrRemote]\n : min;\n });\n }\n}\n\nmodule.exports = NetworkQualityConfigurationImpl;\n", "'use strict';\n\nconst Participant = require('./participant');\n\n/**\n * A {@link RemoteParticipant} represents a remote {@link Participant} in a\n * {@link Room}.\n * @extends Participant\n * @property {Map} audioTracks -\n * The {@link Participant}'s {@link RemoteAudioTrackPublication}s\n * @property {Map} dataTracks -\n * The {@link Participant}'s {@link RemoteDataTrackPublication}s\n * @property {Map} tracks -\n * The {@link Participant}'s {@link RemoteTrackPublication}s\n * @property {Map} videoTracks -\n * The {@link Participant}'s {@link RemoteVideoTrackPublication}s\n * @emits RemoteParticipant#reconnected\n * @emits RemoteParticipant#reconnecting\n * @emits RemoteParticipant#trackDimensionsChanged\n * @emits RemoteParticipant#trackDisabled\n * @emits RemoteParticipant#trackEnabled\n * @emits RemoteParticipant#trackMessage\n * @emits RemoteParticipant#trackPublished\n * @emits RemoteParticipant#trackPublishPriorityChanged\n * @emits RemoteParticipant#trackStarted\n * @emits RemoteParticipant#trackSubscribed\n * @emits RemoteParticipant#trackSubscriptionFailed\n * @emits RemoteParticipant#trackSwitchedOff\n * @emits RemoteParticipant#trackSwitchedOn\n * @emits RemoteParticipant#trackUnpublished\n * @emits RemoteParticipant#trackUnsubscribed\n */\nclass RemoteParticipant extends Participant {\n /**\n * Construct a {@link RemoteParticipant}.\n * @param {ParticipantSignaling} signaling\n * @param {object} [options]\n */\n constructor(signaling, options) {\n super(signaling, options);\n this._handleTrackSignalingEvents();\n this.once('disconnected', this._unsubscribeTracks.bind(this));\n }\n\n toString() {\n return `[RemoteParticipant #${this._instanceId}${this.sid ? `: ${this.sid}` : ''}]`;\n }\n\n /**\n * @private\n * @param {RemoteTrack} remoteTrack\n * @param {RemoteTrackPublication} publication\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _addTrack(remoteTrack, publication, id) {\n if (!super._addTrack(remoteTrack, id)) {\n return null;\n }\n publication._subscribed(remoteTrack);\n this.emit('trackSubscribed', remoteTrack, publication);\n return remoteTrack;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _addTrackPublication(publication) {\n const addedPublication = super._addTrackPublication(publication);\n if (!addedPublication) {\n return null;\n }\n this.emit('trackPublished', addedPublication);\n return addedPublication;\n }\n /**\n * @private\n */\n _getTrackPublicationEvents() {\n return [\n ...super._getTrackPublicationEvents(),\n ['subscriptionFailed', 'trackSubscriptionFailed'],\n ['trackDisabled', 'trackDisabled'],\n ['trackEnabled', 'trackEnabled'],\n ['publishPriorityChanged', 'trackPublishPriorityChanged'],\n ['trackSwitchedOff', 'trackSwitchedOff'],\n ['trackSwitchedOn', 'trackSwitchedOn']\n ];\n }\n\n /**\n * @private\n */\n _unsubscribeTracks() {\n this.tracks.forEach(publication => {\n if (publication.isSubscribed) {\n const track = publication.track;\n publication._unsubscribe();\n this.emit('trackUnsubscribed', track, publication);\n }\n });\n }\n\n /**\n * @private\n * @param {RemoteTrack} remoteTrack\n * @param {RemoteTrackPublication} publication\n * @param {Track.ID} id\n * @returns {?RemoteTrack}\n */\n _removeTrack(remoteTrack, publication, id) {\n const unsubscribedTrack = this._tracks.get(id);\n if (!unsubscribedTrack) {\n return null;\n }\n\n super._removeTrack(unsubscribedTrack, id);\n publication._unsubscribe();\n this.emit('trackUnsubscribed', unsubscribedTrack, publication);\n return unsubscribedTrack;\n }\n\n /**\n * @private\n * @param {RemoteTrackPublication} publication\n * @returns {?RemoteTrackPublication}\n */\n _removeTrackPublication(publication) {\n this._signaling.clearTrackHint(publication.trackSid);\n const removedPublication = super._removeTrackPublication(publication);\n if (!removedPublication) {\n return null;\n }\n this.emit('trackUnpublished', removedPublication);\n return removedPublication;\n }\n}\n\n/**\n * The {@link RemoteParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @event RemoteParticipant#reconnected\n */\n\n/**\n * The {@link RemoteParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @event RemoteParticipant#reconnecting\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link RemoteVideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose dimensions changed\n * @event RemoteParticipant#trackDimensionsChanged\n */\n\n/**\n * A {@link RemoteTrack} was disabled by the {@link RemoteParticipant}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} associated with the disabled {@link RemoteTrack}\n * @event RemoteParticipant#trackDisabled\n */\n\n/**\n * A {@link RemoteTrack} was enabled by the {@link RemoteParticipant}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} associated with the enabled {@link RemoteTrack}\n * @event RemoteParticipant#trackEnabled\n */\n\n/**\n * A message was received over one of the {@link RemoteParticipant}'s\n * {@link RemoteDataTrack}s.\n * @event RemoteParticipant#trackMessage\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} over which the\n * message was received\n */\n\n/**\n * A {@link RemoteTrack} was published by the {@link RemoteParticipant} after\n * connecting to the {@link Room}. This event is not emitted for\n * {@link RemoteTrack}s that were published while the {@link RemoteParticipant}\n * was connecting to the {@link Room}.\n * @event RemoteParticipant#trackPublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the published {@link RemoteTrack}\n * @example\n * function trackPublished(publication) {\n * console.log(`Track ${publication.trackSid} was published`);\n * }\n *\n * room.on('participantConnected', participant => {\n * // Handle RemoteTracks published while connecting to the Room.\n * participant.trackPublications.forEach(trackPublished);\n *\n * // Handle RemoteTracks published after connecting to the Room.\n * participant.on('trackPublished', trackPublished);\n * });\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link RemoteTrack}s started.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that started\n * @event RemoteParticipant#trackStarted\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was subscribed to\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @event RemoteParticipant#trackSubscribed\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} could not be subscribed to.\n * @param {TwilioError} error - The reason the {@link RemoteTrack} could not be\n * subscribed to\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that could not\n * be subscribed to\n * @event RemoteParticipant#trackSubscriptionFailed\n */\n\n/**\n * The {@link RemoteTrackPublication}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority};\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that changed priority\n * @event RemoteParticipant#trackPublishPriorityChanged\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched off\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was switched off\n * @event RemoteParticipant#trackSwitchedOff\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched on.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was switched on\n * @event RemoteParticipant#trackSwitchedOn\n */\n\n/**\n * A {@link RemoteTrack} was unpublished by the {@link RemoteParticipant}.\n * @event RemoteParticipant#trackUnpublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the unpublished {@link RemoteTrack}\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was unsubscribed from.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was unsubscribed from\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was unsubscribed from\n * @event RemoteParticipant#trackUnsubscribed\n */\n\nmodule.exports = RemoteParticipant;\n", "'use strict';\n\n/**\n * Statistics for a {@link Track}.\n * @property {Track.ID} trackId - The {@link Track} ID\n * @property {Track.SID} trackSid - The {@link Track}'s SID when published in\n * in a {@link Room}\n * @property {number} timestamp - A Unix timestamp in milliseconds indicating\n * when the {@link TrackStats} were gathered\n * @property {string} ssrc - The {@link Track}'s SSRC when transmitted over the\n * RTCPeerConnection\n * @property {?number} packetsLost - The number of packets lost\n * @property {?string} codec - The name of the codec used to encode the\n * {@link Track}'s media\n */\nclass TrackStats {\n /**\n * @param {string} trackId - {@link Track} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n if (typeof trackId !== 'string') {\n throw new Error('Track id must be a string');\n }\n\n Object.defineProperties(this, {\n trackId: {\n value: trackId,\n enumerable: true\n },\n trackSid: {\n value: statsReport.trackSid,\n enumerable: true\n },\n timestamp: {\n value: statsReport.timestamp,\n enumerable: true\n },\n ssrc: {\n value: statsReport.ssrc,\n enumerable: true\n },\n packetsLost: {\n value: typeof statsReport.packetsLost === 'number'\n ? statsReport.packetsLost\n : null,\n enumerable: true\n },\n codec: {\n value: typeof statsReport.codecName === 'string'\n ? statsReport.codecName\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = TrackStats;\n", "'use strict';\n\nconst TrackStats = require('./trackstats');\n\n/**\n * Statistics for a {@link LocalTrack}.\n * @extends TrackStats\n * @property {?number} bytesSent - Number of bytes sent\n * @property {?number} packetsSent - Number of packets sent\n * @property {?number} roundTripTime - Round trip time in milliseconds\n */\nclass LocalTrackStats extends TrackStats {\n /**\n * @param {string} trackId - {@link LocalTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n bytesSent: {\n value: typeof statsReport.bytesSent === 'number'\n ? statsReport.bytesSent\n : prepareForInsights ? 0 : null,\n enumerable: true\n },\n packetsSent: {\n value: typeof statsReport.packetsSent === 'number'\n ? statsReport.packetsSent\n : prepareForInsights ? 0 : null,\n enumerable: true\n },\n roundTripTime: {\n value: typeof statsReport.roundTripTime === 'number'\n ? statsReport.roundTripTime\n : prepareForInsights ? 0 : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = LocalTrackStats;\n", "'use strict';\n\nconst LocalTrackStats = require('./localtrackstats');\n\n/**\n * Statistics for a {@link LocalAudioTrack}.\n * @extends LocalTrackStats\n * @property {?AudioLevel} audioLevel - Input {@link AudioLevel}\n * @property {?number} jitter - Audio jitter in milliseconds\n */\nclass LocalAudioTrackStats extends LocalTrackStats {\n /**\n * @param {string} trackId - {@link LocalAudioTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport, prepareForInsights);\n\n Object.defineProperties(this, {\n audioLevel: {\n value: typeof statsReport.audioInputLevel === 'number'\n ? statsReport.audioInputLevel\n : null,\n enumerable: true\n },\n jitter: {\n value: typeof statsReport.jitter === 'number'\n ? statsReport.jitter\n : null,\n enumerable: true\n }\n });\n }\n}\n\n/**\n * The maximum absolute amplitude of a set of audio samples in the\n * range of 0 to 32767 inclusive.\n * @typedef {number} AudioLevel\n */\n\nmodule.exports = LocalAudioTrackStats;\n", "'use strict';\n\nconst LocalTrackStats = require('./localtrackstats');\n\n/**\n * Statistics for a {@link LocalVideoTrack}.\n * @extends LocalTrackStats\n * @property {?VideoTrack#Dimensions} captureDimensions - Video capture resolution\n * @property {?VideoTrack#Dimensions} dimensions - Video encoding resolution\n * @property {?number} captureFrameRate - Video capture frame rate\n * @property {?number} frameRate - Video encoding frame rate\n */\nclass LocalVideoTrackStats extends LocalTrackStats {\n /**\n * @param {string} trackId - {@link LocalVideoTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n * @param {boolean} prepareForInsights\n */\n constructor(trackId, statsReport, prepareForInsights) {\n super(trackId, statsReport, prepareForInsights);\n\n let captureDimensions = null;\n if (typeof statsReport.frameWidthInput === 'number' &&\n typeof statsReport.frameHeightInput === 'number') {\n captureDimensions = {};\n\n Object.defineProperties(captureDimensions, {\n width: {\n value: statsReport.frameWidthInput,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightInput,\n enumerable: true\n }\n });\n }\n\n let dimensions = null;\n if (typeof statsReport.frameWidthSent === 'number' &&\n typeof statsReport.frameHeightSent === 'number') {\n dimensions = {};\n\n Object.defineProperties(dimensions, {\n width: {\n value: statsReport.frameWidthSent,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightSent,\n enumerable: true\n }\n });\n }\n\n Object.defineProperties(this, {\n captureDimensions: {\n value: captureDimensions,\n enumerable: true\n },\n dimensions: {\n value: dimensions,\n enumerable: true\n },\n captureFrameRate: {\n value: typeof statsReport.frameRateInput === 'number'\n ? statsReport.frameRateInput\n : null,\n enumerable: true\n },\n frameRate: {\n value: typeof statsReport.frameRateSent === 'number'\n ? statsReport.frameRateSent\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = LocalVideoTrackStats;\n", "'use strict';\n\nconst TrackStats = require('./trackstats');\n\n/**\n * Statistics for a remote {@link Track}.\n * @extends TrackStats\n * @property {?number} bytesReceived - Number of bytes received\n * @property {?number} packetsReceived - Number of packets received\n */\nclass RemoteTrackStats extends TrackStats {\n /*\n * @param {string} trackId - {@link Track} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n bytesReceived: {\n value: typeof statsReport.bytesReceived === 'number'\n ? statsReport.bytesReceived\n : null,\n enumerable: true\n },\n packetsReceived: {\n value: typeof statsReport.packetsReceived === 'number'\n ? statsReport.packetsReceived\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteTrackStats;\n", "'use strict';\n\nconst RemoteTrackStats = require('./remotetrackstats');\n\n/**\n * Statistics for an {@link AudioTrack}.\n * @extends RemoteTrackStats\n * @property {?AudioLevel} audioLevel - Output {@link AudioLevel}\n * @property {?number} jitter - Audio jitter in milliseconds\n */\nclass RemoteAudioTrackStats extends RemoteTrackStats {\n /**\n * @param {string} trackId - {@link AudioTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n Object.defineProperties(this, {\n audioLevel: {\n value: typeof statsReport.audioOutputLevel === 'number'\n ? statsReport.audioOutputLevel\n : null,\n enumerable: true\n },\n jitter: {\n value: typeof statsReport.jitter === 'number'\n ? statsReport.jitter\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteAudioTrackStats;\n", "'use strict';\n\nconst RemoteTrackStats = require('./remotetrackstats');\n\n/**\n * Statistics for a {@link VideoTrack}.\n * @extends RemoteTrackStats\n * @property {?VideoTrack#Dimensions} dimensions - Received video resolution\n * @property {?number} frameRate - Received video frame rate\n */\nclass RemoteVideoTrackStats extends RemoteTrackStats {\n /**\n * @param {string} trackId - {@link VideoTrack} ID\n * @param {StandardizedTrackStatsReport} statsReport\n */\n constructor(trackId, statsReport) {\n super(trackId, statsReport);\n\n let dimensions = null;\n if (typeof statsReport.frameWidthReceived === 'number' &&\n typeof statsReport.frameHeightReceived === 'number') {\n dimensions = {};\n\n Object.defineProperties(dimensions, {\n width: {\n value: statsReport.frameWidthReceived,\n enumerable: true\n },\n height: {\n value: statsReport.frameHeightReceived,\n enumerable: true\n }\n });\n }\n\n Object.defineProperties(this, {\n dimensions: {\n value: dimensions,\n enumerable: true\n },\n frameRate: {\n value: typeof statsReport.frameRateReceived === 'number'\n ? statsReport.frameRateReceived\n : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = RemoteVideoTrackStats;\n", "'use strict';\n\nconst LocalAudioTrackStats = require('./localaudiotrackstats');\nconst LocalVideoTrackStats = require('./localvideotrackstats');\nconst RemoteAudioTrackStats = require('./remoteaudiotrackstats');\nconst RemoteVideoTrackStats = require('./remotevideotrackstats');\n\n/**\n * Statistics report for an RTCPeerConnection.\n * @property {string} peerConnectionId - ID of the RTCPeerConnection\n * @property {Array} localAudioTrackStats - List of {@link LocalAudioTrackStats}\n * @property {Array} localVideoTrackStats - List of {@link LocalVideoTrackStats}\n * @property {Array} remoteAudioTrackStats - List of {@link RemoteAudioTrackStats}\n * @property {Array} remoteVideoTrackStats - List of {@link RemoteVideoTrackStats}\n */\nclass StatsReport {\n /**\n * @param {string} peerConnectionId - RTCPeerConnection ID\n * @param {StandardizedStatsResponse} statsResponse\n * @param {boolean} prepareForInsights - if report is being prepared to send to insights.\n */\n constructor(peerConnectionId, statsResponse, prepareForInsights) {\n if (typeof peerConnectionId !== 'string') {\n throw new Error('RTCPeerConnection id must be a string');\n }\n\n Object.defineProperties(this, {\n peerConnectionId: {\n value: peerConnectionId,\n enumerable: true\n },\n localAudioTrackStats: {\n value: statsResponse.localAudioTrackStats.map(report => new LocalAudioTrackStats(report.trackId, report, prepareForInsights)),\n enumerable: true\n },\n localVideoTrackStats: {\n value: statsResponse.localVideoTrackStats.map(report => new LocalVideoTrackStats(report.trackId, report, prepareForInsights)),\n enumerable: true\n },\n remoteAudioTrackStats: {\n value: statsResponse.remoteAudioTrackStats.map(report => new RemoteAudioTrackStats(report.trackId, report)),\n enumerable: true\n },\n remoteVideoTrackStats: {\n value: statsResponse.remoteVideoTrackStats.map(report => new RemoteVideoTrackStats(report.trackId, report)),\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = StatsReport;\n", "'use strict';\n\nconst EventEmitter = require('./eventemitter');\nconst RemoteParticipant = require('./remoteparticipant');\nconst StatsReport = require('./stats/statsreport');\nconst { flatMap, valueToJSON } = require('./util');\n\nlet nInstances = 0;\n\n/**\n * A {@link Room} represents communication between you and one or more\n * {@link RemoteParticipant}s sharing {@link AudioTrack}s and\n * {@link VideoTrack}s.\n *

\n * You can connect to a {@link Room} by calling {@link module:twilio-video.connect}.\n * @extends EventEmitter\n * @property {?RemoteParticipant} dominantSpeaker - The Dominant Speaker in the\n * {@link Room}, if any\n * @property {boolean} isRecording - Whether or not the {@link Room} is being\n * recorded\n * @property {LocalParticipant} localParticipant - Your {@link LocalParticipant}\n * in the {@link Room}\n * @property {string} mediaRegion - String indicating geographical region\n * where media is processed for the {@link Room}.\n * @property {string} name - The {@link Room}'s name\n * @property {Map} participants -\n * The {@link RemoteParticipant}s participating in this {@link Room}\n * @property {Room.SID} sid - The {@link Room}'s SID\n * @property {string} state - \"connected\", \"reconnecting\", or \"disconnected\"\n * @throws {SignalingConnectionDisconnectedError}\n * @emits Room#disconnected\n * @emits Room#participantConnected\n * @emits Room#participantDisconnected\n * @emits Room#participantReconnected\n * @emits Room#participantReconnecting\n * @emits Room#reconnected\n * @emits Room#reconnecting\n * @emits Room#recordingStarted\n * @emits Room#recordingStopped\n * @emits Room#trackDimensionsChanged\n * @emits Room#trackDisabled\n * @emits Room#trackEnabled\n * @emits Room#trackMessage\n * @emits Room#trackPublished\n * @emits Room#trackPublishPriorityChanged\n * @emits Room#trackStarted\n * @emits Room#trackSubscribed\n * @emits Room#trackSwitchedOff\n * @emits Room#trackSwitchedOn\n * @emits Room#trackUnpublished\n * @emits Room#trackUnsubscribed\n * @emits Room#trackWarning\n * @emits Room#trackWarningsCleared\n */\nclass Room extends EventEmitter {\n /**\n * Construct a {@link Room}.\n * @param {RoomSignaling} signaling\n * @param {?object} [options={}]\n */\n constructor(localParticipant, signaling, options) {\n super();\n\n const log = options.log.createLog('default', this);\n const participants = new Map();\n\n /* istanbul ignore next */\n Object.defineProperties(this, {\n _log: {\n value: log\n },\n _clientTrackSwitchOffControl: {\n value: options.clientTrackSwitchOffControl || 'disabled'\n },\n _contentPreferencesMode: {\n value: options.contentPreferencesMode || 'disabled'\n },\n _instanceId: {\n value: ++nInstances\n },\n _options: {\n value: options\n },\n _participants: {\n value: participants\n },\n _signaling: {\n value: signaling\n },\n dominantSpeaker: {\n enumerable: true,\n get() {\n return this.participants.get(signaling.dominantSpeakerSid) || null;\n }\n },\n isRecording: {\n enumerable: true,\n get() {\n return signaling.recording.isEnabled || false;\n }\n },\n localParticipant: {\n enumerable: true,\n value: localParticipant\n },\n name: {\n enumerable: true,\n value: signaling.name\n },\n participants: {\n enumerable: true,\n value: participants\n },\n sid: {\n enumerable: true,\n value: signaling.sid\n },\n state: {\n enumerable: true,\n get() {\n return signaling.state;\n }\n },\n mediaRegion: {\n enumerable: true,\n value: signaling.mediaRegion\n }\n });\n\n handleLocalParticipantEvents(this, localParticipant);\n handleRecordingEvents(this, signaling.recording);\n handleSignalingEvents(this, signaling);\n verifyNoiseCancellation(this);\n\n log.info('Created a new Room:', this.name);\n log.debug('Initial RemoteParticipants:', Array.from(this._participants.values()));\n }\n\n toString() {\n return `[Room #${this._instanceId}: ${this.sid}]`;\n }\n\n\n /**\n * Disconnect from the {@link Room}.\n * @returns {this}\n */\n disconnect() {\n this._log.info('Disconnecting');\n this._signaling.disconnect();\n return this;\n }\n\n /**\n * Get the {@link Room}'s media statistics. This is not supported in Safari 12.0 or below\n * due to this bug : https://bugs.webkit.org/show_bug.cgi?id=192601\n *\n * @returns {Promise.>}\n */\n getStats() {\n return this._signaling.getStats().then(responses =>\n Array.from(responses).map(([id, response]) =>\n new StatsReport(id, Object.assign({}, response, {\n localAudioTrackStats: rewriteLocalTrackIds(this, response.localAudioTrackStats),\n localVideoTrackStats: rewriteLocalTrackIds(this, response.localVideoTrackStats)\n }))\n )\n );\n }\n\n /**\n * Restart the muted local media {@link Track}s and play inadvertently paused HTMLMediaElements\n * that are attached to local and remote media {@link Track}s. This method is useful mainly on\n * mobile browsers (Safari and Chrome on iOS), where there is a possibility that the muted local\n * media {@link Track}s are never unmuted and inadvertently paused HTMLMediaElements are never\n * played again, especially after handling an incoming phone call.\n * @returns {this}\n */\n refreshInactiveMedia() {\n const { tracks: localTrackPublications } = this.localParticipant;\n\n const localMediaTracks = Array.from(localTrackPublications.values())\n .filter(({ track: { kind } }) => kind !== 'data')\n .map(({ track }) => track);\n\n const remoteMediaTracks = flatMap(this.participants, participants => Array.from(participants.tracks.values()))\n .filter(({ track }) => track && track.kind !== 'data')\n .map(({ track }) => track);\n\n const mediaTracks = localMediaTracks.concat(remoteMediaTracks);\n\n const unmuteEvent = new Event('unmute');\n localMediaTracks.forEach(({ isMuted, mediaStreamTrack }) => {\n if (isMuted) {\n mediaStreamTrack.dispatchEvent(unmuteEvent);\n }\n });\n\n const pauseEvent = new Event('pause');\n mediaTracks.forEach(({ _attachments: attachments, _elShims: elShims }) => attachments.forEach(el => {\n const shim = elShims.get(el);\n const isInadvertentlyPaused = el.paused && shim && !shim.pausedIntentionally();\n if (isInadvertentlyPaused) {\n el.dispatchEvent(pauseEvent);\n }\n }));\n\n return this;\n }\n\n toJSON() {\n return valueToJSON(this);\n }\n}\n\nfunction verifyNoiseCancellation(room) {\n const allowedAudioProcessors = room.localParticipant._signaling.audioProcessors;\n room.localParticipant.audioTracks.forEach(({ track }) => {\n const noiseCancellation = track.noiseCancellation;\n if (noiseCancellation && !allowedAudioProcessors.includes(noiseCancellation.vendor)) {\n room._log.warn(`${noiseCancellation.vendor} is not supported in this room. disabling it permanently`);\n noiseCancellation.disablePermanently();\n }\n });\n}\n\nfunction rewriteLocalTrackIds(room, trackStats) {\n const localParticipantSignaling = room.localParticipant._signaling;\n return trackStats.reduce((trackStats, trackStat) => {\n const publication = localParticipantSignaling.tracks.get(trackStat.trackId);\n const trackSender = localParticipantSignaling.getSender(publication);\n return trackSender\n ? [Object.assign({}, trackStat, { trackId: trackSender.id })].concat(trackStats)\n : trackStats;\n }, []);\n}\n\n/**\n * A {@link Room.SID} is a 34-character string starting with \"RM\"\n * that uniquely identifies a {@link Room}.\n * @type string\n * @typedef Room.SID\n */\n\n/**\n * The Dominant Speaker in the {@link Room} changed. Either the Dominant Speaker\n * is a new {@link RemoteParticipant} or the Dominant Speaker has been reset and\n * is now null.\n * @param {?RemoteParticipant} dominantSpeaker - The Dominant Speaker in the\n * {@link Room}, if any\n * @event Room#dominantSpeakerChanged\n */\n\n/**\n * Your {@link LocalParticipant} was disconnected from the {@link Room} and all\n * other {@link RemoteParticipant}s.\n * @param {Room} room - The {@link Room} your\n * {@link LocalParticipant} was disconnected from\n * @param {?TwilioError} error - Present when the {@link LocalParticipant} got\n * disconnected from the {@link Room} unexpectedly\n * @event Room#disconnected\n * @example\n * myRoom.on('disconnected', function(room, error) {\n * if (error) {\n * console.log('Unexpectedly disconnected:', error);\n * }\n * myRoom.localParticipant.tracks.forEach(function(track) {\n * track.stop();\n * track.detach();\n * });\n * });\n */\n\n/**\n * A {@link RemoteParticipant} joined the {@link Room}. In Large Group Rooms (Maximum\n * Participants greater than 50), this event is raised only when a {@link RemoteParticipant}\n * publishes at least one {@link LocalTrack}.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who joined\n * @event Room#participantConnected\n * @example\n * myRoom.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' joined the Room');\n * });\n */\n\n/**\n * A {@link RemoteParticipant} left the {@link Room}. In Large Group Rooms (Maximum\n * Participants greater than 50), this event is raised only when a {@link RemoteParticipant}\n * unpublishes all its {@link LocalTrack}s.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who left\n * @event Room#participantDisconnected\n * @example\n * myRoom.on('participantDisconnected', function(participant) {\n * console.log(participant.identity + ' left the Room');\n * participant.tracks.forEach(function(track) {\n * track.detach().forEach(function(mediaElement) {\n * mediaElement.remove();\n * });\n * });\n * });\n */\n\n/**\n * A {@link RemoteParticipant} has reconnected to the {@link Room} after a signaling connection disruption.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} that has reconnected.\n * @event Room#participantReconnected\n * @example\n * myRoom.on('participantReconnected', participant => {\n * console.log(participant.identity + ' reconnected to the Room');\n * });\n */\n\n/**\n * A {@link RemoteParticipant} is reconnecting to the {@link Room} after a signaling connection disruption.\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} that is reconnecting.\n * @event Room#participantReconnecting\n * @example\n * myRoom.on('participantReconnecting', participant => {\n * console.log(participant.identity + ' is reconnecting to the Room');\n * });\n */\n\n/**\n * Your application successfully reconnected to the {@link Room}. When this\n * event is emitted, the {@link Room} is in state \"connected\".\n * @event Room#reconnected\n * @example\n * myRoom.on('reconnected', () => {\n * console.log('Reconnected!');\n * });\n */\n\n/**\n * Your application is reconnecting to the {@link Room}. This happens when there\n * is a disruption in your signaling connection and/or your media connection. When\n * this event is emitted, the {@link Room} is in state \"reconnecting\". If reconnecting\n * succeeds, the {@link Room} will emit a \"reconnected\" event.\n * @param {MediaConnectionError|SignalingConnectionDisconnectedError} error - A\n * {@link MediaConnectionError} if your application is reconnecting due to a\n * disruption in your media connection, or a {@link SignalingConnectionDisconnectedError}\n * if your application is reconnecting due to a disruption in your signaling connection\n * @event Room#reconnecting\n * @example\n * myRoom.on('reconnecting', error => {\n * if (error.code === 53001) {\n * console.log('Reconnecting your signaling connection!', error.message);\n * } else if (error.code === 53405) {\n * console.log('Reconnecting your media connection!', error.message);\n * }\n * });\n */\n\n/**\n * The {@link Room} is now being recorded\n * @event Room#recordingStarted\n */\n\n/**\n * The {@link Room} is no longer being recorded\n * @event Room#recordingStopped\n */\n\n/**\n * One of the {@link RemoteParticipant}'s {@link VideoTrack}'s dimensions changed.\n * @param {RemoteVideoTrack} track - The {@link RemoteVideoTrack} whose dimensions changed\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteVideoTrack}'s dimensions changed\n * @event Room#trackDimensionsChanged\n */\n\n/**\n * A {@link RemoteTrack} was disabled by a {@link RemoteParticipant} in the {@link Room}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} that represents disabled {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * disabled the {@link RemoteTrack}\n * @event Room#trackDisabled\n */\n\n/**\n * A {@link RemoteTrack} was enabled by a {@link RemoteParticipant} in the {@link Room}.\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication} that represents enabled {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * enabled the {@link RemoteTrack}\n * @event Room#trackEnabled\n */\n\n/**\n * A message was received over one of the {@link RemoteParticipant}'s\n * {@link RemoteDataTrack}'s.\n * @param {string|ArrayBuffer} data\n * @param {RemoteDataTrack} track - The {@link RemoteDataTrack} over which the\n * message was received\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteDataTrack} received the message\n * @event Room#trackMessage\n */\n\n/**\n * A {@link RemoteTrack} was published by a {@link RemoteParticipant} after\n * connecting to the {@link Room}. This event is not emitted for\n * {@link RemoteTrack}s that were published while the {@link RemoteParticipant}\n * was connecting to the {@link Room}.\n * @event Room#trackPublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the published {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * published the {@link RemoteTrack}\n * @example\n * function trackPublished(publication, participant) {\n * console.log(`RemoteParticipant ${participant.sid} published Track ${publication.trackSid}`);\n * }\n *\n * // Handle RemoteTracks published after connecting to the Room.\n * room.on('trackPublished', trackPublished);\n *\n * room.on('participantConnected', participant => {\n * // Handle RemoteTracks published while connecting to the Room.\n * participant.trackPublications.forEach(publication => trackPublished(publication, participant));\n * });\n */\n\n/**\n * One of a {@link RemoteParticipant}'s {@link RemoteTrack}s in the {@link Room} started.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that started\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} started\n * @event Room#trackStarted\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was subscribed to.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was subscribed\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was subscribed\n * @event Room#trackSubscribed\n * @example\n * room.on('trackSubscribed', function(track, publication, participant) {\n * var participantView = document.getElementById('participant-view-' + participant.identity);\n * participantView.appendChild(track.attach());\n * });\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched off.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched off\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was switched off\n * @event Room#trackSwitchedOff\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was switched on.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was switched on\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was switched on\n * @event Room#trackSwitchedOn\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} could not be subscribed to.\n * @param {TwilioError} error - The reason the {@link RemoteTrack} could not be\n * subscribed to\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that could not\n * be subscribed to\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} could not be subscribed to\n * @event Room#trackSubscriptionFailed\n */\n\n/**\n * The {@link RemoteTrack}'s publish {@link Track.Priority} was changed by the\n * {@link RemoteParticipant}.\n * @param {Track.Priority} priority - the {@link RemoteTrack}'s new publish\n * {@link Track.Priority};\n * @param {RemoteTrackPublication} publication - The\n * {@link RemoteTrackPublication} for the {@link RemoteTrack} that changed priority\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} changed priority\n * @event Room#trackPublishPriorityChanged\n */\n\n/**\n * A {@link RemoteTrack} was unpublished by a {@link RemoteParticipant} to the {@link Room}.\n * @event Room#trackUnpublished\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * which represents the unpublished {@link RemoteTrack}\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} who\n * unpublished the {@link RemoteTrack}\n */\n\n/**\n * A {@link RemoteParticipant}'s {@link RemoteTrack} was unsubscribed from.\n * @param {RemoteTrack} track - The {@link RemoteTrack} that was unsubscribed\n * @param {RemoteTrackPublication} publication - The {@link RemoteTrackPublication}\n * for the {@link RemoteTrack} that was unsubscribed from\n * @param {RemoteParticipant} participant - The {@link RemoteParticipant} whose\n * {@link RemoteTrack} was unsubscribed\n * @event Room#trackUnsubscribed\n * @example\n * room.on('trackUnsubscribed', function(track, publication, participant) {\n * track.detach().forEach(function(mediaElement) {\n * mediaElement.remove();\n * });\n * });\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s in the {@link Room} encountered a warning.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {string} name - The warning that was raised.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that encountered the warning.\n * @param {LocalParticipant} participant - The {@link LocalParticipant}\n * @event Room#trackWarning\n * @example\n * room.on('trackWarning', (name, publication, participant) => {\n * if (name === 'recording-media-lost') {\n * log(`LocalTrack ${publication.track.name} is not recording media.`,\n * name, publication, participant);\n *\n * // Wait a reasonable amount of time to clear the warning.\n * const timer = setTimeout(() => {\n * // If the warning is not cleared, you can manually\n * // reconnect to the room, or show a dialog to the user\n * }, 5000);\n *\n * room.once('trackWarningsCleared', (publication, participant) => {\n * log('LocalTrack warnings have cleared!',\n * publication, participant);\n * clearTimeout(timer);\n * });\n * }\n});\n */\n\n/**\n * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s in the {@link Room} cleared all warnings.\n * This event is only raised if you enabled warnings using notifyWarnings in ConnectOptions.\n * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that cleared all warnings.\n * @param {LocalParticipant} participant - The {@link LocalParticipant}\n * @event Room#trackWarningsCleared\n */\n\nfunction connectParticipant(room, participantSignaling) {\n const { _log: log, _clientTrackSwitchOffControl: clientTrackSwitchOffControl, _contentPreferencesMode: contentPreferencesMode } = room;\n const participant = new RemoteParticipant(participantSignaling, { log, clientTrackSwitchOffControl, contentPreferencesMode });\n\n log.info('A new RemoteParticipant connected:', participant);\n room._participants.set(participant.sid, participant);\n room.emit('participantConnected', participant);\n\n // Reemit Track and RemoteParticipant events.\n const eventListeners = [\n ['reconnected', 'participantReconnected'],\n ['reconnecting', 'participantReconnecting'],\n 'trackDimensionsChanged',\n 'trackDisabled',\n 'trackEnabled',\n 'trackMessage',\n 'trackPublished',\n 'trackPublishPriorityChanged',\n 'trackStarted',\n 'trackSubscribed',\n 'trackSubscriptionFailed',\n 'trackSwitchedOff',\n 'trackSwitchedOn',\n 'trackUnpublished',\n 'trackUnsubscribed'\n ].map(eventOrPair => {\n const [event, participantEvent] = Array.isArray(eventOrPair)\n ? eventOrPair\n : [eventOrPair, eventOrPair];\n\n function reemit() {\n const args = [].slice.call(arguments);\n args.unshift(participantEvent);\n args.push(participant);\n room.emit(...args);\n }\n participant.on(event, reemit);\n return [event, reemit];\n });\n\n participant.once('disconnected', function participantDisconnected() {\n const dominantSpeaker = room.dominantSpeaker;\n log.info('RemoteParticipant disconnected:', participant);\n room._participants.delete(participant.sid);\n eventListeners.forEach(args => {\n participant.removeListener(args[0], args[1]);\n });\n room.emit('participantDisconnected', participant);\n if (participant === dominantSpeaker) {\n room.emit('dominantSpeakerChanged', room.dominantSpeaker);\n }\n });\n}\n\nfunction handleLocalParticipantEvents(room, localParticipant) {\n const events = ['trackWarning', 'trackWarningsCleared'].map(event => ({\n eventName: event,\n handler: (...args) => room.emit(event, ...[...args, localParticipant]),\n }));\n\n events.forEach(({ eventName, handler }) =>\n localParticipant.on(eventName, handler));\n\n room.once('disconnected', () =>\n events.forEach(({ eventName, handler }) =>\n localParticipant.removeListener(eventName, handler)));\n}\n\nfunction handleRecordingEvents(room, recording) {\n recording.on('updated', function updated() {\n const started = recording.isEnabled;\n room._log.info(`Recording ${started ? 'started' : 'stopped'}`);\n room.emit(`recording${started ? 'Started' : 'Stopped'}`);\n });\n}\n\nfunction handleSignalingEvents(room, signaling) {\n const log = room._log;\n\n // Reemit RemoteParticipant events from the RoomSignaling.\n log.debug('Creating a new RemoteParticipant for each ParticipantSignaling '\n + 'in the RoomSignaling');\n signaling.participants.forEach(connectParticipant.bind(null, room));\n log.debug('Setting up RemoteParticipant creation for all subsequent '\n + 'ParticipantSignalings that connect to the RoomSignaling');\n signaling.on('participantConnected', connectParticipant.bind(null, room));\n\n signaling.on('dominantSpeakerChanged', () => room.emit('dominantSpeakerChanged', room.dominantSpeaker));\n\n // Reemit state transition events from the RoomSignaling.\n signaling.on('stateChanged', function stateChanged(state, error) {\n log.info('Transitioned to state:', state);\n switch (state) {\n case 'disconnected':\n room.participants.forEach(participant => {\n participant._unsubscribeTracks();\n });\n room.emit(state, room, error);\n room.localParticipant.tracks.forEach(publication => {\n publication.unpublish();\n });\n signaling.removeListener('stateChanged', stateChanged);\n break;\n case 'reconnecting':\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => room.emit('reconnecting', error), 0);\n\n break;\n default:\n\n // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked.\n // Do not signal public events synchronously with lock held.\n setTimeout(() => room.emit('reconnected'), 0);\n }\n });\n}\n\nmodule.exports = Room;\n", "\n/**\n * Expose `Backoff`.\n */\n\nclass Backoff {\n /**\n * Construct a {@link Backoff}.\n * @param {object} options\n * @property {number} min - Initial timeout in milliseconds [100]\n * @property {number} max - Max timeout [10000]\n * @property {boolean} jitter - Apply jitter [0]\n * @property {number} factor - Multiplication factor for Backoff operation [2]\n */\n constructor(options) {\n Object.defineProperties(this, {\n _min: {\n value: options.min || 100\n },\n _max: {\n value: options.max || 10000\n },\n _jitter: {\n value: options.jitter > 0 && options.jitter <= 1 ? options.jitter : 0\n },\n _factor: {\n value: options.factor || 2\n },\n _attempts: {\n value: 0,\n writable: true\n },\n _duration: {\n enumerable: false,\n get() {\n let ms = this._min * Math.pow(this._factor, this._attempts);\n if (this._jitter) {\n const rand = Math.random();\n const deviation = Math.floor(rand * this._jitter * ms);\n ms = (Math.floor(rand * 10) & 1) === 0 ? ms - deviation : ms + deviation;\n }\n return Math.min(ms, this._max) | 0;\n }\n },\n _timeoutID: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Start the backoff operation.\n * @param {function} fn - Function to call\n * @return {void}\n * @api public\n */\n backoff(fn) {\n let duration = this._duration;\n if (this._timeoutID) {\n clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n this._timeoutID = setTimeout(() => {\n this._attempts++;\n fn();\n }, duration);\n }\n\n /**\n * Reset the number of attempts and clear the timer.\n *\n * @return {void}\n * @api public\n */\n reset() {\n this._attempts = 0;\n if (this._timeoutID) {\n clearTimeout(this._timeoutID);\n this._timeoutID = null;\n }\n }\n}\n\nmodule.exports = Backoff;\n", "'use strict';\n\nconst { difference, flatMap } = require('../');\n\n/**\n * Create a random {@link SSRC}.\n * @returns {SSRC}\n */\nfunction createSSRC() {\n const ssrcMax = 0xffffffff;\n return String(Math.floor(Math.random() * ssrcMax));\n}\n\n/**\n * @property {string} cName\n * @property {boolean} isSimulcastEnabled\n * @property {Map} rtxPairs\n * @property {Set} primarySSRCs\n * @property {string} streamId\n * @property {Track.ID} trackId\n */\nclass TrackAttributes {\n /**\n * Construct a {@link MediaStreamTrack} attribute store.\n * @param {Track.ID} trackId - The MediaStreamTrack ID\n * @param {MediaStreamID} streamId - The MediaStream ID\n * @param {string} cName - The MediaStream cname\n */\n constructor(trackId, streamId, cName) {\n Object.defineProperties(this, {\n cName: {\n enumerable: true,\n value: cName\n },\n isSimulcastEnabled: {\n enumerable: true,\n value: false,\n writable: true\n },\n primarySSRCs: {\n enumerable: true,\n value: new Set()\n },\n rtxPairs: {\n enumerable: true,\n value: new Map()\n },\n streamId: {\n enumerable: true,\n value: streamId\n },\n trackId: {\n enumerable: true,\n value: trackId\n }\n });\n }\n\n /**\n * Add {@link SimSSRC}s to the {@link TrackAttributes}.\n * @returns {void}\n */\n addSimulcastSSRCs() {\n if (this.isSimulcastEnabled) {\n return;\n }\n const simulcastSSRCs = [createSSRC(), createSSRC()];\n simulcastSSRCs.forEach(function(ssrc) {\n this.primarySSRCs.add(ssrc);\n }, this);\n\n if (this.rtxPairs.size) {\n simulcastSSRCs.forEach(function(ssrc) {\n this.rtxPairs.set(createSSRC(), ssrc);\n }, this);\n }\n }\n\n /**\n * Add the given {@link PrimarySSRC} or {@link RtxSSRC} to the {@link TrackAttributes}\n * and update the \"isSimulcastEnabled\" flag if it is also a {@link SimSSRC}.\n * @param {SSRC} ssrc - The {@link SSRC} to be added\n * @param {?PrimarySSRC} primarySSRC - The {@link PrimarySSRC}; if the given\n * {@link SSRC} itself is the {@link PrimarySSRC}, then this is set to null\n * @param {boolean} isSimSSRC - true if the given {@link SSRC} is a\n * {@link SimSSRC}; false otherwise\n * @returns {void}\n */\n addSSRC(ssrc, primarySSRC, isSimSSRC) {\n if (primarySSRC) {\n this.rtxPairs.set(ssrc, primarySSRC);\n } else {\n this.primarySSRCs.add(ssrc);\n }\n this.isSimulcastEnabled = this.isSimulcastEnabled || isSimSSRC;\n }\n\n /**\n * Construct the SDP lines for the {@link TrackAttributes}.\n * @param {boolean} [excludeRtx=false]\n * @returns {Array} Array of SDP lines\n */\n toSdpLines(excludeRtx) {\n const rtxPairs = excludeRtx\n ? []\n : Array.from(this.rtxPairs.entries()).map(rtxPair => rtxPair.reverse());\n\n const simSSRCs = Array.from(this.primarySSRCs.values());\n const ssrcs = rtxPairs.length ? flatMap(rtxPairs) : simSSRCs;\n\n const attrLines = flatMap(ssrcs, ssrc => [\n `a=ssrc:${ssrc} cname:${this.cName}`,\n `a=ssrc:${ssrc} msid:${this.streamId} ${this.trackId}`\n ]);\n const rtxPairLines = rtxPairs.map(rtxPair => `a=ssrc-group:FID ${rtxPair.join(' ')}`);\n const simGroupLines = [\n `a=ssrc-group:SIM ${simSSRCs.join(' ')}`\n ];\n\n return rtxPairLines.concat(attrLines).concat(simGroupLines);\n }\n}\n\n/**\n * Get the matches for a given RegEx pattern.\n * @param {string} section - SDP media section\n * @param {string} pattern - RegEx pattern\n * @returns {Array>} - Array of pattern matches\n */\nfunction getMatches(section, pattern) {\n const matches = section.match(new RegExp(pattern, 'gm')) || [];\n return matches.map(match => {\n const matches = match.match(new RegExp(pattern)) || [];\n return matches.slice(1);\n });\n}\n\n/**\n * Get the {@link SimSSRC}s that belong to a simulcast group.\n * @param {string} section - SDP media section\n * @returns {Set} Set of simulcast {@link SSRC}s\n */\nfunction getSimulcastSSRCs(section) {\n const simGroupPattern = '^a=ssrc-group:SIM ([0-9]+) ([0-9]+) ([0-9]+)$';\n return new Set(flatMap(getMatches(section, simGroupPattern)));\n}\n\n/**\n * Get the value of the given attribute for an SSRC.\n * @param {string} section - SDP media section\n * @param {SSRC} ssrc - {@link SSRC} whose attribute's value is to be determinded\n * @param {string} attribute - {@link SSRC} attribute name\n * @param {string} - {@link SSRC} attribute value\n */\nfunction getSSRCAttribute(section, ssrc, attribute) {\n const pattern = `a=ssrc:${ssrc} ${attribute}:(.+)`;\n return section.match(new RegExp(pattern))[1];\n}\n\n/**\n * Create a Map of {@link PrimarySSRC}s and their {@link RtxSSRC}s.\n * @param {string} section - SDP media section\n * @returns {Map} - Map of {@link RtxSSRC}s and their\n * corresponding {@link PrimarySSRC}s\n */\nfunction getSSRCRtxPairs(section) {\n const rtxPairPattern = '^a=ssrc-group:FID ([0-9]+) ([0-9]+)$';\n return new Map(getMatches(section, rtxPairPattern).map(pair => pair.reverse()));\n}\n\n/**\n * Create SSRC attribute tuples.\n * @param {string} section\n * @returns {Array<[SSRC, MediaStreamID, Track.ID]>}\n */\nfunction createSSRCAttributeTuples(section) {\n const [streamId, trackId] = flatMap(getMatches(section, '^a=msid:(.+) (.+)$'));\n const ssrcs = flatMap(getMatches(section, '^a=ssrc:(.+) cname:.+$'));\n return ssrcs.map(ssrc => [ssrc, streamId, trackId]);\n}\n\n/**\n * Create a Map of MediaStreamTrack IDs and their {@link TrackAttributes}.\n * @param {string} section - SDP media section\n * @returns {Map}\n */\nfunction createTrackIdsToAttributes(section) {\n const simSSRCs = getSimulcastSSRCs(section);\n const rtxPairs = getSSRCRtxPairs(section);\n const ssrcAttrTuples = createSSRCAttributeTuples(section);\n\n return ssrcAttrTuples.reduce((trackIdsToSSRCs, tuple) => {\n const ssrc = tuple[0];\n const streamId = tuple[1];\n const trackId = tuple[2];\n\n const trackAttributes = trackIdsToSSRCs.get(trackId) || new TrackAttributes(\n trackId,\n streamId,\n getSSRCAttribute(section, ssrc, 'cname'));\n\n const primarySSRC = rtxPairs.get(ssrc) || null;\n trackAttributes.addSSRC(ssrc, primarySSRC, simSSRCs.has(ssrc));\n return trackIdsToSSRCs.set(trackId, trackAttributes);\n }, new Map());\n}\n\n/**\n * Apply simulcast settings to the given SDP media section.\n * @param {string} section - SDP media section\n * @param {Map} trackIdsToAttributes - Existing\n * map which will be updated for new MediaStreamTrack IDs\n * @returns {string} - The transformed SDP media section\n */\nfunction setSimulcastInMediaSection(section, trackIdsToAttributes) {\n const newTrackIdsToAttributes = createTrackIdsToAttributes(section);\n const newTrackIds = Array.from(newTrackIdsToAttributes.keys());\n let trackIds = Array.from(trackIdsToAttributes.keys());\n const trackIdsToAdd = difference(newTrackIds, trackIds);\n const trackIdsToIgnore = difference(trackIds, newTrackIds);\n\n // Update \"trackIdsToAttributes\" with TrackAttributes for new\n // MediaStreamTrack IDs.\n const trackAttributesToAdd = flatMap(trackIdsToAdd, trackId => newTrackIdsToAttributes.get(trackId));\n trackAttributesToAdd.forEach(trackAttributes => {\n trackAttributes.addSimulcastSSRCs();\n trackIdsToAttributes.set(trackAttributes.trackId, trackAttributes);\n });\n\n // Get the SDP lines of the relevant MediaStreamTrack IDs from\n // \"trackIdsToAttributes\".\n trackIds = Array.from(trackIdsToAttributes.keys());\n const relevantTrackIds = difference(trackIds, trackIdsToIgnore);\n const relevantTrackAttributes = flatMap(relevantTrackIds, trackId => trackIdsToAttributes.get(trackId));\n const excludeRtx = !section.match(/a=rtpmap:[0-9]+ rtx/);\n const relevantSdpLines = flatMap(relevantTrackAttributes, trackAttributes => trackAttributes.toSdpLines(excludeRtx));\n\n // Add the simulcast SSRC SDP lines to the media section. The Set ensures\n // that the duplicates of the SSRC SDP lines that are in both \"section\" and\n // \"relevantSdpLines\" are removed.\n const sectionLines = flatMap(new Set(section.split('\\r\\n').concat(relevantSdpLines)));\n\n const xGoogleFlagConference = 'a=x-google-flag:conference';\n if (!section.match(xGoogleFlagConference)) {\n sectionLines.push(xGoogleFlagConference);\n }\n\n return sectionLines.join('\\r\\n');\n}\n\n/**\n * String representing a MediaStream ID.\n * @typedef {string} MediaStreamID\n */\n\n/**\n * String representing the SSRC of a MediaStreamTrack.\n * @typedef {string} SSRC\n */\n\n/**\n * Primary SSRC.\n * @typedef {SSRC} PrimarySSRC\n */\n\n/**\n * Retransmission SSRC.\n * @typedef {SSRC} RtxSSRC\n */\n\n/**\n * Simulcast SSRC.\n * @typedef {SSRC} SimSSRC\n */\n\nmodule.exports = setSimulcastInMediaSection;\n", "'use strict';\n\nconst { difference, flatMap } = require('../');\nconst setSimulcastInMediaSection = require('./simulcast');\n\nconst ptToFixedBitrateAudioCodecName = {\n 0: 'PCMU',\n 8: 'PCMA'\n};\n\n/**\n * A payload type\n * @typedef {number} PT\n */\n\n/**\n * An {@link AudioCodec} or {@link VideoCodec}\n * @typedef {AudioCodec|VideoCodec} Codec\n */\n\n/**\n * Create a Codec Map for the given m= section.\n * @param {string} section - The given m= section\n * @returns {Map>}\n */\nfunction createCodecMapForMediaSection(section) {\n return Array.from(createPtToCodecName(section)).reduce((codecMap, pair) => {\n const pt = pair[0];\n const codecName = pair[1];\n const pts = codecMap.get(codecName) || [];\n return codecMap.set(codecName, pts.concat(pt));\n }, new Map());\n}\n\n/**\n * Create a Map of MIDs to m= sections for the given SDP.\n * @param {string} sdp\n * @returns {Map}\n */\nfunction createMidToMediaSectionMap(sdp) {\n return getMediaSections(sdp).reduce((midsToMediaSections, mediaSection) => {\n const mid = getMidForMediaSection(mediaSection);\n return mid ? midsToMediaSections.set(mid, mediaSection) : midsToMediaSections;\n }, new Map());\n}\n\n/**\n * Create a Map from PTs to codec names for the given m= section.\n * @param {string} mediaSection - The given m= section.\n * @returns {Map} ptToCodecName\n */\nfunction createPtToCodecName(mediaSection) {\n return getPayloadTypesInMediaSection(mediaSection).reduce((ptToCodecName, pt) => {\n const rtpmapPattern = new RegExp(`a=rtpmap:${pt} ([^/]+)`);\n const matches = mediaSection.match(rtpmapPattern);\n const codecName = matches\n ? matches[1].toLowerCase()\n : ptToFixedBitrateAudioCodecName[pt]\n ? ptToFixedBitrateAudioCodecName[pt].toLowerCase()\n : '';\n return ptToCodecName.set(pt, codecName);\n }, new Map());\n}\n\n/**\n * Get the associated fmtp attributes for the given Payload Type in an m= section.\n * @param {PT} pt\n * @param {string} mediaSection\n * @returns {?object}\n */\nfunction getFmtpAttributesForPt(pt, mediaSection) {\n // In \"a=fmtp: =[;=]*\", the regex matches the codec\n // profile parameters expressed as name/value pairs separated by \";\".\n const fmtpRegex = new RegExp(`^a=fmtp:${pt} (.+)$`, 'm');\n const matches = mediaSection.match(fmtpRegex);\n return matches && matches[1].split(';').reduce((attrs, nvPair) => {\n const [name, value] = nvPair.split('=');\n attrs[name] = isNaN(value) ? value : parseInt(value, 10);\n return attrs;\n }, {});\n}\n\n/**\n * Get the MID for the given m= section.\n * @param {string} mediaSection\n * @return {?string}\n */\nfunction getMidForMediaSection(mediaSection) {\n // In \"a=mid:\", the regex matches .\n const midMatches = mediaSection.match(/^a=mid:(.+)$/m);\n return midMatches && midMatches[1];\n}\n\n/**\n * Get the m= sections of a particular kind and direction from an sdp.\n * @param {string} sdp - SDP string\n * @param {string} [kind] - Pattern for matching kind\n * @param {string} [direction] - Pattern for matching direction\n * @returns {Array} mediaSections\n */\nfunction getMediaSections(sdp, kind, direction) {\n return sdp.replace(/\\r\\n\\r\\n$/, '\\r\\n').split('\\r\\nm=').slice(1).map(mediaSection => `m=${mediaSection}`).filter(mediaSection => {\n const kindPattern = new RegExp(`m=${kind || '.*'}`, 'gm');\n const directionPattern = new RegExp(`a=${direction || '.*'}`, 'gm');\n return kindPattern.test(mediaSection) && directionPattern.test(mediaSection);\n });\n}\n\n/**\n * Get the Codec Payload Types present in the first line of the given m= section\n * @param {string} section - The m= section\n * @returns {Array} Payload Types\n */\nfunction getPayloadTypesInMediaSection(section) {\n const mLine = section.split('\\r\\n')[0];\n\n // In \"m= ... \",\n // the regex matches and the Payload Types.\n const matches = mLine.match(/([0-9]+)/g);\n\n // This should not happen, but in case there are no Payload Types in\n // the m= line, return an empty array.\n if (!matches) {\n return [];\n }\n\n // Since only the Payload Types are needed, we discard the .\n return matches.slice(1).map(match => parseInt(match, 10));\n}\n\n/**\n * Create the reordered Codec Payload Types based on the preferred Codec Names.\n * @param {Map>} codecMap - Codec Map\n * @param {Array} preferredCodecs - Preferred Codecs\n * @returns {Array} Reordered Payload Types\n */\nfunction getReorderedPayloadTypes(codecMap, preferredCodecs) {\n preferredCodecs = preferredCodecs.map(({ codec }) => codec.toLowerCase());\n const preferredPayloadTypes = flatMap(preferredCodecs, codecName => codecMap.get(codecName) || []);\n const remainingCodecs = difference(Array.from(codecMap.keys()), preferredCodecs);\n const remainingPayloadTypes = flatMap(remainingCodecs, codecName => codecMap.get(codecName));\n return preferredPayloadTypes.concat(remainingPayloadTypes);\n}\n\n/**\n * Set the given Codec Payload Types in the first line of the given m= section.\n * @param {Array} payloadTypes - Payload Types\n * @param {string} section - Given m= section\n * @returns {string} - Updated m= section\n */\nfunction setPayloadTypesInMediaSection(payloadTypes, section) {\n const lines = section.split('\\r\\n');\n let mLine = lines[0];\n const otherLines = lines.slice(1);\n mLine = mLine.replace(/([0-9]+\\s?)+$/, payloadTypes.join(' '));\n return [mLine].concat(otherLines).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string with the re-ordered codec preferences.\n * @param {string} sdp\n * @param {Array} preferredAudioCodecs - If empty, the existing order\n * of audio codecs is preserved\n * @param {Array} preferredVideoCodecs - If empty, the\n * existing order of video codecs is preserved\n * @returns {string} Updated SDP string\n */\nfunction setCodecPreferences(sdp, preferredAudioCodecs, preferredVideoCodecs) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n // Codec preferences should not be applied to m=application sections.\n if (!/^m=(audio|video)/.test(section)) {\n return section;\n }\n const kind = section.match(/^m=(audio|video)/)[1];\n const codecMap = createCodecMapForMediaSection(section);\n const preferredCodecs = kind === 'audio' ? preferredAudioCodecs : preferredVideoCodecs;\n const payloadTypes = getReorderedPayloadTypes(codecMap, preferredCodecs);\n const newSection = setPayloadTypesInMediaSection(payloadTypes, section);\n\n const pcmaPayloadTypes = codecMap.get('pcma') || [];\n const pcmuPayloadTypes = codecMap.get('pcmu') || [];\n const fixedBitratePayloadTypes = kind === 'audio'\n ? new Set(pcmaPayloadTypes.concat(pcmuPayloadTypes))\n : new Set();\n\n return fixedBitratePayloadTypes.has(payloadTypes[0])\n ? newSection.replace(/\\r\\nb=(AS|TIAS):([0-9]+)/g, '')\n : newSection;\n })).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string with simulcast settings.\n * @param {string} sdp\n * @param {Map} trackIdsToAttributes\n * @returns {string} Updated SDP string\n */\nfunction setSimulcast(sdp, trackIdsToAttributes) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n section = section.replace(/\\r\\n$/, '');\n if (!/^m=video/.test(section)) {\n return section;\n }\n const codecMap = createCodecMapForMediaSection(section);\n const payloadTypes = getPayloadTypesInMediaSection(section);\n const vp8PayloadTypes = new Set(codecMap.get('vp8') || []);\n\n const hasVP8PayloadType = payloadTypes.some(payloadType => vp8PayloadTypes.has(payloadType));\n return hasVP8PayloadType\n ? setSimulcastInMediaSection(section, trackIdsToAttributes)\n : section;\n })).concat('').join('\\r\\n');\n}\n\n/**\n * Get the matching Payload Types in an m= section for a particular peer codec.\n * @param {Codec} peerCodec\n * @param {PT} peerPt\n * @param {Map} codecsToPts\n * @param {string} section\n * @param {string} peerSection\n * @returns {Array}\n */\nfunction getMatchingPayloadTypes(peerCodec, peerPt, codecsToPts, section, peerSection) {\n // If there is at most one local Payload Type that matches the remote codec, retain it.\n const matchingPts = codecsToPts.get(peerCodec) || [];\n if (matchingPts.length <= 1) {\n return matchingPts;\n }\n\n // If there are no fmtp attributes for the codec in the peer m= section, then we\n // cannot get a match in the m= section. In that case, retain all matching Payload\n // Types.\n const peerFmtpAttrs = getFmtpAttributesForPt(peerPt, peerSection);\n if (!peerFmtpAttrs) {\n return matchingPts;\n }\n\n // Among the matched local Payload Types, find the one that matches the remote\n // fmtp attributes.\n const matchingPt = matchingPts.find(pt => {\n const fmtpAttrs = getFmtpAttributesForPt(pt, section);\n return fmtpAttrs && Object.keys(peerFmtpAttrs).every(attr => {\n return peerFmtpAttrs[attr] === fmtpAttrs[attr];\n });\n });\n\n // If none of the matched Payload Types also have matching fmtp attributes,\n // then retain all of them, otherwise retain only the Payload Type that\n // matches the peer fmtp attributes.\n return typeof matchingPt === 'number' ? [matchingPt] : matchingPts;\n}\n\n/**\n * Filter codecs in an m= section based on its peer m= section from the other peer.\n * @param {string} section\n * @param {Map} peerMidsToMediaSections\n * @param {Array} codecsToRemove\n * @returns {string}\n */\nfunction filterCodecsInMediaSection(section, peerMidsToMediaSections, codecsToRemove) {\n // Do nothing if the m= section represents neither audio nor video.\n if (!/^m=(audio|video)/.test(section)) {\n return section;\n }\n\n // Do nothing if the m= section does not have an equivalent remote m= section.\n const mid = getMidForMediaSection(section);\n const peerSection = mid && peerMidsToMediaSections.get(mid);\n if (!peerSection) {\n return section;\n }\n\n // Construct a Map of the peer Payload Types to their codec names.\n const peerPtToCodecs = createPtToCodecName(peerSection);\n // Construct a Map of the codec names to their Payload Types.\n const codecsToPts = createCodecMapForMediaSection(section);\n // Maintain a list of non-rtx Payload Types to retain.\n let pts = flatMap(Array.from(peerPtToCodecs), ([peerPt, peerCodec]) =>\n peerCodec !== 'rtx' && !codecsToRemove.includes(peerCodec)\n ? getMatchingPayloadTypes(\n peerCodec,\n peerPt,\n codecsToPts,\n section,\n peerSection)\n : []);\n\n // For each Payload Type that will be retained, retain their corresponding rtx\n // Payload Type if present.\n const rtxPts = codecsToPts.get('rtx') || [];\n // In \"a=fmtp: apt=\", extract the codec PT associated with rtxPt.\n pts = pts.concat(rtxPts.filter(rtxPt => {\n const fmtpAttrs = getFmtpAttributesForPt(rtxPt, section);\n return fmtpAttrs && pts.includes(fmtpAttrs.apt);\n }));\n\n // Filter out the below mentioned attribute lines in the m= section that do not\n // belong to one of the Payload Types that are to be retained.\n // 1. \"a=rtpmap: \"\n // 2. \"a=rtcp-fb: [ ]*\"\n // 3. \"a=fmtp: =[;=]*\"\n const lines = section.split('\\r\\n').filter(line => {\n const ptMatches = line.match(/^a=(rtpmap|fmtp|rtcp-fb):(.+) .+$/);\n const pt = ptMatches && ptMatches[2];\n return !ptMatches || (pt && pts.includes(parseInt(pt, 10)));\n });\n\n // Filter the list of Payload Types in the first line of the m= section.\n const orderedPts = getPayloadTypesInMediaSection(section).filter(pt => pts.includes(pt));\n return setPayloadTypesInMediaSection(orderedPts, lines.join('\\r\\n'));\n}\n\n/**\n * Filter local codecs based on the remote SDP.\n * @param {string} localSdp\n * @param {string} remoteSdp\n * @returns {string} - Updated local SDP\n */\nfunction filterLocalCodecs(localSdp, remoteSdp) {\n const localMediaSections = getMediaSections(localSdp);\n const localSession = localSdp.split('\\r\\nm=')[0];\n const remoteMidsToMediaSections = createMidToMediaSectionMap(remoteSdp);\n return [localSession].concat(localMediaSections.map(localSection => {\n return filterCodecsInMediaSection(localSection, remoteMidsToMediaSections, []);\n })).join('\\r\\n');\n}\n\n/**\n * Return a new SDP string after reverting simulcast for non vp8 sections in remote sdp.\n * @param localSdp - simulcast enabled local sdp\n * @param localSdpWithoutSimulcast - local sdp before simulcast was set\n * @param remoteSdp - remote sdp\n * @param revertForAll - when true simulcast will be reverted for all codecs. when false it will be reverted\n * only for non-vp8 codecs.\n * @return {string} Updated SDP string\n */\nfunction revertSimulcast(localSdp, localSdpWithoutSimulcast, remoteSdp, revertForAll = false) {\n const remoteMidToMediaSections = createMidToMediaSectionMap(remoteSdp);\n const localMidToMediaSectionsWithoutSimulcast = createMidToMediaSectionMap(localSdpWithoutSimulcast);\n const mediaSections = getMediaSections(localSdp);\n const session = localSdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(section => {\n section = section.replace(/\\r\\n$/, '');\n if (!/^m=video/.test(section)) {\n return section;\n }\n const midMatches = section.match(/^a=mid:(.+)$/m);\n const mid = midMatches && midMatches[1];\n if (!mid) {\n return section;\n }\n\n const remoteSection = remoteMidToMediaSections.get(mid);\n const remotePtToCodecs = createPtToCodecName(remoteSection);\n const remotePayloadTypes = getPayloadTypesInMediaSection(remoteSection);\n\n const isVP8ThePreferredCodec = remotePayloadTypes.length && remotePtToCodecs.get(remotePayloadTypes[0]) === 'vp8';\n const shouldRevertSimulcast = revertForAll || !isVP8ThePreferredCodec;\n return shouldRevertSimulcast ? localMidToMediaSectionsWithoutSimulcast.get(mid).replace(/\\r\\n$/, '') : section;\n })).concat('').join('\\r\\n');\n}\n\n/**\n * Add or rewrite MSIDs for new m= sections in the given SDP with their corresponding\n * local MediaStreamTrack IDs. These can be different when previously removed MediaStreamTracks\n * are added back (or Track IDs may not be present in the SDPs at all once browsers implement\n * the latest WebRTC spec).\n * @param {string} sdp\n * @param {Map} activeMidsToTrackIds\n * @param {Map>} trackIdsByKind\n * @returns {string}\n */\nfunction addOrRewriteNewTrackIds(sdp, activeMidsToTrackIds, trackIdsByKind) {\n // NOTE(mmalavalli): The m= sections for the new MediaStreamTracks are usually\n // present after the m= sections for the existing MediaStreamTracks, in order\n // of addition.\n const newMidsToTrackIds = Array.from(trackIdsByKind).reduce((midsToTrackIds, [kind, trackIds]) => {\n const mediaSections = getMediaSections(sdp, kind, 'send(only|recv)');\n const newMids = mediaSections.map(getMidForMediaSection).filter(mid => !activeMidsToTrackIds.has(mid));\n newMids.forEach((mid, i) => midsToTrackIds.set(mid, trackIds[i]));\n return midsToTrackIds;\n }, new Map());\n return addOrRewriteTrackIds(sdp, newMidsToTrackIds);\n}\n\n/**\n * Add or rewrite MSIDs in the given SDP with their corresponding local MediaStreamTrack IDs.\n * These IDs need not be the same (or Track IDs may not be present in the SDPs at all once\n * browsers implement the latest WebRTC spec).\n * @param {string} sdp\n * @param {Map} midsToTrackIds\n * @returns {string}\n */\nfunction addOrRewriteTrackIds(sdp, midsToTrackIds) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(mediaSection => {\n // Do nothing if the m= section represents neither audio nor video.\n if (!/^m=(audio|video)/.test(mediaSection)) {\n return mediaSection;\n }\n // This shouldn't happen, but in case there is no MID for the m= section, do nothing.\n const mid = getMidForMediaSection(mediaSection);\n if (!mid) {\n return mediaSection;\n }\n // In case there is no Track ID for the given MID in the map, do nothing.\n const trackId = midsToTrackIds.get(mid);\n if (!trackId) {\n return mediaSection;\n }\n // This shouldn't happen, but in case there is no a=msid: line, do nothing.\n const attributes = (mediaSection.match(/^a=msid:(.+)$/m) || [])[1];\n if (!attributes) {\n return mediaSection;\n }\n // If the a=msid: line contains the \"appdata\" field, then replace it with the Track ID,\n // otherwise append the Track ID.\n const [msid, trackIdToRewrite] = attributes.split(' ');\n const msidRegex = new RegExp(`msid:${msid}${trackIdToRewrite ? ` ${trackIdToRewrite}` : ''}$`, 'gm');\n return mediaSection.replace(msidRegex, `msid:${msid} ${trackId}`);\n })).join('\\r\\n');\n}\n\n/**\n * Removes specified ssrc attributes from given sdp.\n * @param {string} sdp\n * @param {Array} ssrcAttributesToRemove\n * @returns {string}\n */\nfunction removeSSRCAttributes(sdp, ssrcAttributesToRemove) {\n return sdp.split('\\r\\n').filter(line =>\n !ssrcAttributesToRemove.find(srcAttribute => new RegExp('a=ssrc:.*' + srcAttribute + ':', 'g').test(line))\n ).join('\\r\\n');\n}\n\n/**\n * Disable RTX in a given sdp.\n * @param {string} sdp\n * @returns {string} sdp without RTX\n */\nfunction disableRtx(sdp) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session].concat(mediaSections.map(mediaSection => {\n // Do nothing if the m= section does not represent a video track.\n if (!/^m=video/.test(mediaSection)) {\n return mediaSection;\n }\n\n // Create a map of codecs to payload types.\n const codecsToPts = createCodecMapForMediaSection(mediaSection);\n // Get the RTX payload types.\n const rtxPts = codecsToPts.get('rtx');\n\n // Do nothing if there are no RTX payload types.\n if (!rtxPts) {\n return mediaSection;\n }\n\n // Remove the RTX payload types.\n const pts = new Set(getPayloadTypesInMediaSection(mediaSection));\n rtxPts.forEach(rtxPt => pts.delete(rtxPt));\n\n // Get the RTX SSRC.\n const rtxSSRCMatches = mediaSection.match(/a=ssrc-group:FID [0-9]+ ([0-9]+)/);\n const rtxSSRC = rtxSSRCMatches && rtxSSRCMatches[1];\n\n // Remove the following lines associated with the RTX payload types:\n // 1. \"a=fmtp: apt=\"\n // 2. \"a=rtpmap: rtx/...\"\n // 3. \"a=ssrc: cname:...\"\n // 4. \"a=ssrc-group:FID \"\n const filterRegexes = [\n /^a=fmtp:.+ apt=.+$/,\n /^a=rtpmap:.+ rtx\\/.+$/,\n /^a=ssrc-group:.+$/\n ].concat(rtxSSRC\n ? [new RegExp(`^a=ssrc:${rtxSSRC} .+$`)]\n : []);\n\n mediaSection = mediaSection.split('\\r\\n')\n .filter(line => filterRegexes.every(regex => !regex.test(line)))\n .join('\\r\\n');\n\n // Reconstruct the m= section without the RTX payload types.\n return setPayloadTypesInMediaSection(Array.from(pts), mediaSection);\n })).join('\\r\\n');\n}\n\n/**\n * Generate an a=fmtp: line from the given payload type and attributes.\n * @param {PT} pt\n * @param {*} fmtpAttrs\n * @returns {string}\n */\nfunction generateFmtpLineFromPtAndAttributes(pt, fmtpAttrs) {\n const serializedFmtpAttrs = Object.entries(fmtpAttrs).map(([name, value]) => {\n return `${name}=${value}`;\n }).join(';');\n return `a=fmtp:${pt} ${serializedFmtpAttrs}`;\n}\n\n/**\n * Enable DTX for opus in the m= sections for the given MIDs.`\n * @param {string} sdp\n * @param {Array} [mids] - If not specified, enables opus DTX for all\n * audio m= lines.\n * @returns {string}\n */\nfunction enableDtxForOpus(sdp, mids) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n\n mids = mids || mediaSections\n .filter(section => /^m=audio/.test(section))\n .map(getMidForMediaSection);\n\n return [session].concat(mediaSections.map(section => {\n // Do nothing if the m= section is not audio.\n if (!/^m=audio/.test(section)) {\n return section;\n }\n\n // Build a map codecs to payload types.\n const codecsToPts = createCodecMapForMediaSection(section);\n\n // Do nothing if a payload type for opus does not exist.\n const opusPt = codecsToPts.get('opus');\n if (!opusPt) {\n return section;\n }\n\n // If no fmtp attributes are found for opus, do nothing.\n const opusFmtpAttrs = getFmtpAttributesForPt(opusPt, section);\n if (!opusFmtpAttrs) {\n return section;\n }\n\n // Add usedtx=1 to the a=fmtp: line for opus.\n const origOpusFmtpLine = generateFmtpLineFromPtAndAttributes(opusPt, opusFmtpAttrs);\n const origOpusFmtpRegex = new RegExp(origOpusFmtpLine);\n\n // If the m= section's MID is in the list of MIDs, then enable dtx. Otherwise disable it.\n const mid = getMidForMediaSection(section);\n if (mids.includes(mid)) {\n opusFmtpAttrs.usedtx = 1;\n } else {\n delete opusFmtpAttrs.usedtx;\n }\n\n const opusFmtpLineWithDtx = generateFmtpLineFromPtAndAttributes(opusPt, opusFmtpAttrs);\n return section.replace(origOpusFmtpRegex, opusFmtpLineWithDtx);\n })).join('\\r\\n');\n}\n\nexports.addOrRewriteNewTrackIds = addOrRewriteNewTrackIds;\nexports.addOrRewriteTrackIds = addOrRewriteTrackIds;\nexports.createCodecMapForMediaSection = createCodecMapForMediaSection;\nexports.createPtToCodecName = createPtToCodecName;\nexports.disableRtx = disableRtx;\nexports.enableDtxForOpus = enableDtxForOpus;\nexports.filterLocalCodecs = filterLocalCodecs;\nexports.getMediaSections = getMediaSections;\nexports.removeSSRCAttributes = removeSSRCAttributes;\nexports.revertSimulcast = revertSimulcast;\nexports.setCodecPreferences = setCodecPreferences;\nexports.setSimulcast = setSimulcast;\n", "'use strict';\n\nclass Filter {\n constructor(options) {\n options = Object.assign({\n getKey: function defaultGetKey(a) { return a; },\n getValue: function defaultGetValue(a) { return a; },\n isLessThanOrEqualTo: function defaultIsLessThanOrEqualTo(a, b) { return a <= b; }\n }, options);\n Object.defineProperties(this, {\n _getKey: {\n value: options.getKey\n },\n _getValue: {\n value: options.getValue\n },\n _isLessThanOrEqualTo: {\n value: options.isLessThanOrEqualTo\n },\n _map: {\n value: new Map()\n }\n });\n }\n\n toMap() {\n return new Map(this._map);\n }\n\n updateAndFilter(entries) {\n return entries.filter(this.update, this);\n }\n\n update(entry) {\n const key = this._getKey(entry);\n const value = this._getValue(entry);\n if (this._map.has(key) &&\n this._isLessThanOrEqualTo(value, this._map.get(key))) {\n return false;\n }\n this._map.set(key, value);\n return true;\n }\n}\n\nmodule.exports = Filter;\n", "'use strict';\n\nconst Filter = require('../../util/filter');\n\n/**\n * An {@link IceBox} stores trickled ICE candidates. Candidates added to the\n * {@link IceBox} via {@link IceBox#update} are compared against previously\n * trickled candidates and only new candidates will be returned (assuming they\n * match the current ICE username fragment set by {@link IceBox#setUfrag}).\n * @property {?string} ufrag\n */\nclass IceBox {\n /**\n * Construct an {@link IceBox}.\n */\n constructor() {\n Object.defineProperties(this, {\n _filter: {\n value: new Filter({\n getKey: function getKey(iceState) {\n return iceState.ufrag;\n },\n isLessThanOrEqualTo: function isLessThanOrEqualTo(a, b) {\n return a.revision <= b.revision;\n }\n })\n },\n _ufrag: {\n writable: true,\n value: null\n },\n ufrag: {\n enumerable: true,\n get() {\n return this._ufrag;\n }\n }\n });\n }\n\n /**\n * Set the ICE username fragment on the {@link IceBox}. This method returns any\n * ICE candidates associated with the username fragment.\n * @param {string} ufrag\n * @returns {Array}\n */\n setUfrag(ufrag) {\n this._ufrag = ufrag;\n const ice = this._filter.toMap().get(ufrag);\n return ice ? ice.candidates : [];\n }\n\n /**\n * Update the {@link IceBox}. This method returns any new ICE candidates\n * associated with the current username fragment.\n * @param {object} iceState\n * @returns {Array}\n */\n update(iceState) {\n // NOTE(mroberts): The Server sometimes does not set the candidates property.\n iceState.candidates = iceState.candidates || [];\n const oldIceState = this._filter.toMap().get(iceState.ufrag);\n const oldCandidates = oldIceState ? oldIceState.candidates : [];\n return this._filter.update(iceState) && this._ufrag === iceState.ufrag\n ? iceState.candidates.slice(oldCandidates.length)\n : [];\n }\n}\n\nmodule.exports = IceBox;\n", "'use strict';\n\nconst { ICE_ACTIVITY_CHECK_PERIOD_MS, ICE_INACTIVITY_THRESHOLD_MS } = require('../../util/constants');\n\n/**\n * Monitors a {@link RTCPeerConnection}'s stats and notifies\n * caller when inactivity is detected.\n */\nclass IceConnectionMonitor {\n /**\n * Construct an {@link IceConnectionMonitor}.\n * @param {RTCPeerConnection} peerConnection\n * @param {object} [options]\n */\n constructor(peerConnection, options) {\n options = Object.assign({\n activityCheckPeriodMs: ICE_ACTIVITY_CHECK_PERIOD_MS,\n inactivityThresholdMs: ICE_INACTIVITY_THRESHOLD_MS,\n }, options);\n\n Object.defineProperties(this, {\n _activityCheckPeriodMs: {\n value: options.activityCheckPeriodMs\n },\n _inactivityThresholdMs: {\n value: options.inactivityThresholdMs\n },\n _lastActivity: {\n value: null,\n writable: true\n },\n _peerConnection: {\n value: peerConnection\n },\n _timer: {\n value: null,\n writable: true,\n },\n _onIceConnectionStateChanged: {\n value: null,\n writable: true\n }\n });\n }\n\n _getActivePairStat(stats) {\n const statsArray = Array.from(stats.values());\n const activePairStats = statsArray.find(stat => stat.type === 'candidate-pair' && stat.nominated);\n // NOTE(mpatwardhan): sometimes (JSDK-2667) after getting disconnected while switching network\n // we may not find active pair. Treat this as 0 bytesReceived so that we count it towards inactivity.\n return activePairStats || {\n bytesReceived: 0,\n timestamp: Math.round((new Date()).getTime())\n };\n }\n\n /**\n * Get ICE connection stats, and extract received and send bytes.\n * @returns Promise\n */\n _getIceConnectionStats() {\n return this._peerConnection.getStats().then(stats => this._getActivePairStat(stats)).catch(() => {\n return null;\n });\n }\n\n /**\n * schedules/un-schedules inactivity callback.\n */\n _scheduleInactivityCallback(callback) {\n if (callback && this._onIceConnectionStateChanged === null) {\n // schedule callback\n this._onIceConnectionStateChanged = () => {\n if (this._peerConnection.iceConnectionState === 'disconnected') {\n // eslint-disable-next-line callback-return\n callback();\n }\n };\n this._peerConnection.addEventListener('iceconnectionstatechange', this._onIceConnectionStateChanged);\n } else if (!callback && this._onIceConnectionStateChanged) {\n // unschedule callback\n this._peerConnection.removeEventListener('iceconnectionstatechange', this._onIceConnectionStateChanged);\n this._onIceConnectionStateChanged = null;\n }\n }\n\n /**\n * Start monitoring the ICE connection.\n * Monitors bytes received on active ice connection pair,\n * invokes onIceConnectionInactive when inactivity is detected.\n * @param {function} onIceConnectionInactive\n */\n start(onIceConnectionInactive) {\n this.stop();\n\n this._timer = setInterval(() => {\n this._getIceConnectionStats().then(iceStats => {\n if (!iceStats) {\n return;\n }\n\n // NOTE(mpatwardhan): We look at bytesReceived on active candidate pair as an indication of active ice connection.\n // As per spec (https://w3c.github.io/webrtc-stats/#dom-rtcicecandidatepairstats-bytesreceived) this value\n // includes RTCP traffic and is +ve even when there are no tracks subscribed to.\n if (!this._lastActivity || this._lastActivity.bytesReceived !== iceStats.bytesReceived) {\n this._lastActivity = iceStats;\n // detected activity, cancel scheduled callback if any.\n this._scheduleInactivityCallback(null);\n }\n\n if (iceStats.timestamp - this._lastActivity.timestamp >= this._inactivityThresholdMs) {\n // detected inactivity.\n if (this._peerConnection.iceConnectionState === 'disconnected') {\n onIceConnectionInactive();\n } else if (this._onIceConnectionStateChanged === null) {\n this._scheduleInactivityCallback(onIceConnectionInactive);\n }\n }\n });\n }, this._activityCheckPeriodMs);\n }\n\n /**\n * Stop monitoring the ICE connection state.\n * @returns {void}\n */\n stop() {\n this._scheduleInactivityCallback(null);\n if (this._timer !== null) {\n clearInterval(this._timer);\n this._timer = null;\n this._lastActivity = null;\n }\n }\n}\n\nmodule.exports = IceConnectionMonitor;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\n/**\n * @classdesc A {@link DataTransport} implements {@link MediaSignalingTransport}\n * in terms of an RTCDataChannel.\n * @extends EventEmitter\n * @implements MediaSignalingTransport\n * @emits DataTransport#message\n */\nclass DataTransport extends EventEmitter {\n /**\n * Construct a {@link DataTransport}.\n * @param {RTCDataChannel} dataChannel\n */\n constructor(dataChannel) {\n super();\n\n Object.defineProperties(this, {\n _dataChannel: {\n value: dataChannel\n },\n _messageQueue: {\n value: []\n }\n });\n\n dataChannel.addEventListener('open', () => {\n this._messageQueue.splice(0).forEach(message => this._publish(message));\n });\n\n dataChannel.addEventListener('message', ({ data }) => {\n try {\n const message = JSON.parse(data);\n this.emit('message', message);\n } catch (error) {\n // Do nothing.\n }\n });\n\n this.publish({ type: 'ready' });\n }\n\n /**\n * @param message\n * @private\n */\n _publish(message) {\n const data = JSON.stringify(message);\n try {\n this._dataChannel.send(data);\n } catch (error) {\n // Do nothing.\n }\n }\n\n /**\n * Publish a message. Returns true if calling the method resulted in\n * publishing (or eventually publishing) the update.\n * @param {object} message\n * @returns {boolean}\n */\n publish(message) {\n const dataChannel = this._dataChannel;\n if (dataChannel.readyState === 'closing' || dataChannel.readyState === 'closed') {\n return false;\n }\n if (dataChannel.readyState === 'connecting') {\n this._messageQueue.push(message);\n return true;\n }\n this._publish(message);\n return true;\n }\n}\n\n/**\n * The {@link DataTransport} received a message.\n * @event DataTransport#message\n * @param {object} message\n */\n\nmodule.exports = DataTransport;\n", "'use strict';\n\nconst DataTrackTransceiver = require('./transceiver');\nconst DataTransport = require('./transport');\n\n/**\n * A {@link DataTrackReceiver} represents a {@link DataTrackTransceiver} over\n * which data can be received. Internally, it users a single RTCDataChannel to\n * receive data.\n * @extends DataTrackTransceiver\n * @emits DataTrackReceiver#message\n * @emits DataTrackReceiver#close\n */\nclass DataTrackReceiver extends DataTrackTransceiver {\n /**\n * Construct an {@link DataTrackReceiver}.\n * @param {RTCDataChannel} dataChannel\n */\n constructor(dataChannel) {\n super(\n dataChannel.label,\n dataChannel.maxPacketLifeTime,\n dataChannel.maxRetransmits,\n dataChannel.ordered\n );\n\n Object.defineProperties(this, {\n _dataChannel: {\n value: dataChannel\n }\n });\n\n // NOTE(mmalavalli): In Firefox, the default value for \"binaryType\" is \"blob\".\n // So, we set it to \"arraybuffer\" to ensure that it is consistent with Chrome\n // and Safari.\n dataChannel.binaryType = 'arraybuffer';\n\n dataChannel.addEventListener('message', event => {\n this.emit('message', event.data);\n });\n\n dataChannel.addEventListener('close', () => {\n this.emit('close');\n });\n }\n\n stop() {\n this._dataChannel.close();\n super.stop();\n }\n\n /**\n * Create a {@link DataTransport} from the {@link DataTrackReceiver}.\n * @returns {DataTransport}\n */\n toDataTransport() {\n return new DataTransport(this._dataChannel);\n }\n}\n\n/**\n * @event DataTrackReceiver#message\n * @param {string|ArrayBuffer} data\n */\n\n/**\n * @event DataTrackReceiver#close\n */\n\nmodule.exports = DataTrackReceiver;\n", "'use strict';\n\nconst MediaTrackTransceiver = require('./transceiver');\n\n/**\n * A {@link MediaTrackReceiver} represents a remote MediaStreamTrack.\n * @extends MediaTrackTransceiver\n */\nclass MediaTrackReceiver extends MediaTrackTransceiver {\n /**\n * Construct a {@link MediaTrackReceiver}.\n * @param {Track.ID} id - The MediaStreamTrack ID signaled through RSP/SDP\n * @param {MediaStreamTrack} mediaStreamTrack - The remote MediaStreamTrack\n */\n constructor(id, mediaStreamTrack) {\n super(id, mediaStreamTrack);\n }\n}\n\nmodule.exports = MediaTrackReceiver;\n", "'use strict';\n\nconst { getMediaSections } = require('./');\n\n/**\n * An {@link TrackMatcher} matches an RTCTrackEvent with a MediaStreamTrack\n * ID based on the MID of the underlying RTCRtpTransceiver.\n */\nclass TrackMatcher {\n /**\n * Construct an {@link TrackMatcher}.\n */\n constructor() {\n Object.defineProperties(this, {\n _midsToTrackIds: {\n value: new Map(),\n writable: true\n }\n });\n }\n\n /**\n * Match a given MediaStreamTrack with its ID.\n * @param {RTCTrackEvent} event\n * @returns {?Track.ID}\n */\n match(event) {\n return this._midsToTrackIds.get(event.transceiver.mid) || null;\n }\n\n /**\n * Update the {@link TrackMatcher} with a new SDP.\n * @param {string} sdp\n */\n update(sdp) {\n const sections = getMediaSections(sdp, '(audio|video)');\n this._midsToTrackIds = sections.reduce((midsToTrackIds, section) => {\n const midMatches = section.match(/^a=mid:(.+)$/m) || [];\n const trackIdMatches = section.match(/^a=msid:.+ (.+)$/m) || [];\n const mid = midMatches[1];\n const trackId = trackIdMatches[1];\n return mid && trackId ? midsToTrackIds.set(mid, trackId) : midsToTrackIds;\n }, this._midsToTrackIds);\n }\n}\n\nmodule.exports = TrackMatcher;\n", "'use strict';\n\nconst { RTCSessionDescription } = require('../../webrtc');\n\nconst { createPtToCodecName, getMediaSections } = require('./');\n\n/**\n * An RTX payload type\n * @typedef {PT} RtxPT\n */\n\n/**\n * A non-RTX payload type\n * @typedef {PT} NonRtxPT\n */\n\n/**\n * A Set with at least one element\n * @typedef {Set} NonEmptySet\n */\n\n/**\n * Apply the workaround for Issue 8329 to an RTCSessionDescriptionInit.\n * @param {RTCSessionDescriptionInit} description\n * @returns {RTCSessionDescription} newDescription\n */\nfunction workaround(description) {\n const descriptionInit = { type: description.type };\n if (description.type !== 'rollback') {\n descriptionInit.sdp = sdpWorkaround(description.sdp);\n }\n return new RTCSessionDescription(descriptionInit);\n}\n\n/**\n * @param {string} sdp\n * @returns {string} newSdp\n */\nfunction sdpWorkaround(sdp) {\n const mediaSections = getMediaSections(sdp);\n const session = sdp.split('\\r\\nm=')[0];\n return [session]\n .concat(mediaSections.map(mediaSectionWorkaround))\n .join('\\r\\n');\n}\n\n/**\n * @param {string} mediaSection\n * @returns {string} newMediaSection\n */\nfunction mediaSectionWorkaround(mediaSection) {\n const ptToCodecName = createPtToCodecName(mediaSection);\n mediaSection = deleteDuplicateRtxPts(mediaSection, ptToCodecName);\n const codecNameToPts = createCodecNameToPts(ptToCodecName);\n const rtxPts = codecNameToPts.get('rtx') || new Set();\n\n const invalidRtxPts = new Set();\n const rtxPtToAssociatedPt = createRtxPtToAssociatedPt(\n mediaSection, ptToCodecName, rtxPts, invalidRtxPts);\n const associatedPtToRtxPt = createAssociatedPtToRtxPt(\n rtxPtToAssociatedPt, invalidRtxPts);\n\n const unassociatedRtxPts = Array.from(invalidRtxPts);\n\n // NOTE(mroberts): We normalize to lowercase.\n const knownCodecNames = ['h264', 'vp8', 'vp9'];\n const unassociatedPts = knownCodecNames.reduce((unassociatedPts, codecName) => {\n const pts = codecNameToPts.get(codecName) || new Set();\n return Array.from(pts).reduce((unassociatedPts, pt) => associatedPtToRtxPt.has(pt)\n ? unassociatedPts\n : unassociatedPts.add(pt), unassociatedPts);\n }, new Set());\n\n unassociatedPts.forEach(pt => {\n if (unassociatedRtxPts.length) {\n const rtxPt = unassociatedRtxPts.shift();\n mediaSection = deleteFmtpAttributesForRtxPt(mediaSection, rtxPt);\n mediaSection = addFmtpAttributeForRtxPt(mediaSection, rtxPt, pt);\n }\n });\n\n unassociatedRtxPts.forEach(rtxPt => {\n mediaSection = deleteFmtpAttributesForRtxPt(mediaSection, rtxPt);\n mediaSection = deleteRtpmapAttributesForRtxPt(mediaSection, rtxPt);\n });\n\n return mediaSection;\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} ptToCodecName\n * @returns {string} newMediaSection\n */\nfunction deleteDuplicateRtxPts(mediaSection, ptToCodecName) {\n // NOTE(syerrapragada): In some cases Chrome produces an offer/answer\n // with duplicate \"rtx\" payload mapping in media section. When applied,\n // Chrome rejects the SDP. We workaround this by deleting duplicate\n // \"rtx\" mappings found in SDP.\n return Array.from(ptToCodecName.keys()).reduce((section, pt) => {\n const rtpmapRegex = new RegExp(`^a=rtpmap:${pt} rtx.+$`, 'gm');\n return (section.match(rtpmapRegex) || []).slice(ptToCodecName.get(pt) === 'rtx' ? 1 : 0).reduce((section, rtpmap) => {\n const rtpmapRegex = new RegExp(`\\r\\n${rtpmap}`);\n const fmtpmapRegex = new RegExp(`\\r\\na=fmtp:${pt} apt=[0-9]+`);\n return section.replace(rtpmapRegex, '').replace(fmtpmapRegex, '');\n }, section);\n }, mediaSection);\n}\n\n/**\n * @param {Map} ptToCodecName\n * @returns {Map>} codecNameToPts\n */\nfunction createCodecNameToPts(ptToCodecName) {\n const codecNameToPts = new Map();\n ptToCodecName.forEach((codecName, pt) => {\n const pts = codecNameToPts.get(codecName) || new Set();\n return codecNameToPts.set(codecName, pts.add(pt));\n });\n return codecNameToPts;\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} ptToCodecName\n * @param {Set} rtxPts\n * @param {Set} invalidRtxPts\n * @returns {Map} rtxPtToAssociatedPt\n */\nfunction createRtxPtToAssociatedPt(mediaSection, ptToCodecName, rtxPts, invalidRtxPts) {\n return Array.from(rtxPts).reduce((rtxPtToAssociatedPt, rtxPt) => {\n const fmtpPattern = new RegExp(`a=fmtp:${rtxPt} apt=(\\\\d+)`);\n const matches = mediaSection.match(fmtpPattern);\n if (!matches) {\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n const pt = Number.parseInt(matches[1]);\n if (!ptToCodecName.has(pt)) {\n // This is Issue 8329.\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n const codecName = ptToCodecName.get(pt);\n if (codecName === 'rtx') {\n // Strange\n invalidRtxPts.add(rtxPt);\n return rtxPtToAssociatedPt;\n }\n\n return rtxPtToAssociatedPt.set(rtxPt, pt);\n }, new Map());\n}\n\n/**\n * @param {string} mediaSection\n * @param {Map} rtxPtToAssociatedPt\n * @param {Set} invalidRtxPts\n * @returns {Map} associatedPtToRtxPt\n */\nfunction createAssociatedPtToRtxPt(rtxPtToAssociatedPt, invalidRtxPts) {\n // First, we construct a Map>.\n const associatedPtToRtxPts = Array.from(rtxPtToAssociatedPt).reduce((associatedPtToRtxPts, pair) => {\n const rtxPt = pair[0];\n const pt = pair[1];\n const rtxPts = associatedPtToRtxPts.get(pt) || new Set();\n return associatedPtToRtxPts.set(pt, rtxPts.add(rtxPt));\n }, new Map());\n\n // Then, we filter down to a Map. Any RtxPTs that map to the\n // same NonRtxPT are removed and added to invalidRtxPts.\n return Array.from(associatedPtToRtxPts).reduce((associatedPtToRtxPt, pair) => {\n const pt = pair[0];\n const rtxPts = Array.from(pair[1]);\n if (rtxPts.length > 1) {\n rtxPts.forEach(rtxPt => {\n invalidRtxPts.add(rtxPt);\n });\n return associatedPtToRtxPt;\n }\n return associatedPtToRtxPt.set(pt, rtxPts[0]);\n }, new Map());\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @returns {string} newMediaSection\n */\nfunction deleteFmtpAttributesForRtxPt(mediaSection, rtxPt) {\n const pattern = new RegExp(`a=fmtp:${rtxPt}.*\\r\\n`, 'gm');\n return mediaSection.replace(pattern, '');\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @returns {string} newMediaSection\n */\nfunction deleteRtpmapAttributesForRtxPt(mediaSection, rtxPt) {\n const pattern = new RegExp(`a=rtpmap:${rtxPt}.*\\r\\n`, 'gm');\n return mediaSection.replace(pattern, '');\n}\n\n/**\n * @param {string} mediaSection\n * @param {RtxPT} rtxPt\n * @param {NonRtxPT} pt\n * @returns {string} newMediaSection\n */\nfunction addFmtpAttributeForRtxPt(mediaSection, rtxPt, pt) {\n return mediaSection.endsWith('\\r\\n')\n ? `${mediaSection}a=fmtp:${rtxPt} apt=${pt}\\r\\n`\n : `${mediaSection}\\r\\na=fmtp:${rtxPt} apt=${pt}`;\n}\n\nmodule.exports = workaround;\n", "'use strict';\n\nconst DefaultBackoff = require('../../util/backoff');\nconst {\n RTCIceCandidate: DefaultRTCIceCandidate,\n RTCPeerConnection: DefaultRTCPeerConnection,\n RTCSessionDescription: DefaultRTCSessionDescription,\n getStats: getStatistics\n} = require('../../webrtc');\n\nconst util = require('../../webrtc/util');\n\nconst {\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n DEFAULT_LOG_LEVEL,\n DEFAULT_SESSION_TIMEOUT_SEC,\n iceRestartBackoffConfig\n} = require('../../util/constants');\n\nconst {\n addOrRewriteNewTrackIds,\n addOrRewriteTrackIds,\n createCodecMapForMediaSection,\n disableRtx,\n enableDtxForOpus,\n filterLocalCodecs,\n getMediaSections,\n removeSSRCAttributes,\n revertSimulcast,\n setCodecPreferences,\n setSimulcast\n} = require('../../util/sdp');\n\nconst DefaultTimeout = require('../../util/timeout');\n\nconst {\n MediaClientLocalDescFailedError,\n MediaClientRemoteDescFailedError\n} = require('../../util/twilio-video-errors');\n\nconst {\n buildLogLevels,\n getPlatform,\n isChromeScreenShareTrack,\n oncePerTick,\n defer\n} = require('../../util');\n\nconst IceBox = require('./icebox');\nconst DefaultIceConnectionMonitor = require('./iceconnectionmonitor.js');\nconst DataTrackReceiver = require('../../data/receiver');\nconst MediaTrackReceiver = require('../../media/track/receiver');\nconst StateMachine = require('../../statemachine');\nconst Log = require('../../util/log');\nconst TrackMatcher = require('../../util/sdp/trackmatcher');\nconst workaroundIssue8329 = require('../../util/sdp/issue8329');\n\nconst guess = util.guessBrowser();\nconst platform = getPlatform();\nconst isAndroid = /android/.test(platform);\nconst isChrome = guess === 'chrome';\nconst isFirefox = guess === 'firefox';\nconst isSafari = guess === 'safari';\n\nlet nInstances = 0;\n\n/*\nPeerConnectionV2 States\n-----------------------\n\n +------+ +--------+\n | | | |\n | open |--->| closed |\n | | | |\n +------+ +--------+\n | ^ ^\n | | |\n | | |\n v | |\n +----------+ |\n | | |\n | updating |------+\n | |\n +----------+\n\n*/\n\nconst states = {\n open: [\n 'closed',\n 'updating'\n ],\n updating: [\n 'closed',\n 'open'\n ],\n closed: []\n};\n\n/**\n * @extends StateMachine\n * @property {id}\n * @emits PeerConnectionV2#connectionStateChanged\n * @emits PeerConnectionV2#iceConnectionStateChanged\n * @emits PeerConnectionV2#candidates\n * @emits PeerConnectionV2#description\n */\nclass PeerConnectionV2 extends StateMachine {\n /**\n * Construct a {@link PeerConnectionV2}.\n * @param {string} id\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} [options]\n */\n constructor(id, encodingParameters, preferredCodecs, options) {\n super('open', states);\n options = Object.assign({\n enableDscp: false,\n dummyAudioMediaStreamTrack: null,\n isChromeScreenShareTrack,\n iceServers: [],\n logLevel: DEFAULT_LOG_LEVEL,\n offerOptions: {},\n revertSimulcast,\n sessionTimeout: DEFAULT_SESSION_TIMEOUT_SEC * 1000,\n setCodecPreferences,\n setSimulcast,\n Backoff: DefaultBackoff,\n IceConnectionMonitor: DefaultIceConnectionMonitor,\n RTCIceCandidate: DefaultRTCIceCandidate,\n RTCPeerConnection: DefaultRTCPeerConnection,\n RTCSessionDescription: DefaultRTCSessionDescription,\n Timeout: DefaultTimeout\n }, options);\n\n const configuration = getConfiguration(options);\n const logLevels = buildLogLevels(options.logLevel);\n const RTCPeerConnection = options.RTCPeerConnection;\n\n if (options.enableDscp === true) {\n options.chromeSpecificConstraints = options.chromeSpecificConstraints || {};\n options.chromeSpecificConstraints.optional = options.chromeSpecificConstraints.optional || [];\n options.chromeSpecificConstraints.optional.push({ googDscp: true });\n }\n\n const log = options.log ? options.log.createLog('webrtc', this) : new Log('webrtc', this, logLevels, options.loggerName);\n const peerConnection = new RTCPeerConnection(configuration, options.chromeSpecificConstraints);\n\n if (options.dummyAudioMediaStreamTrack) {\n peerConnection.addTrack(options.dummyAudioMediaStreamTrack);\n }\n\n Object.defineProperties(this, {\n _appliedTrackIdsToAttributes: {\n value: new Map(),\n writable: true\n },\n _dataChannels: {\n value: new Map()\n },\n _dataTrackReceivers: {\n value: new Set()\n },\n _descriptionRevision: {\n writable: true,\n value: 0\n },\n _didGenerateLocalCandidates: {\n writable: true,\n value: false\n },\n _enableDscp: {\n value: options.enableDscp\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _isChromeScreenShareTrack: {\n value: options.isChromeScreenShareTrack,\n },\n _iceGatheringFailed: {\n value: false,\n writable: true\n },\n _iceGatheringTimeout: {\n value: new options.Timeout(\n () => this._handleIceGatheringTimeout(),\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n false)\n },\n _iceRestartBackoff: {\n // eslint-disable-next-line new-cap\n value: new options.Backoff(iceRestartBackoffConfig)\n },\n _instanceId: {\n value: ++nInstances\n },\n _isIceConnectionInactive: {\n writable: true,\n value: false\n },\n _isIceLite: {\n writable: true,\n value: false\n },\n _isIceRestartBackoffInProgress: {\n writable: true,\n value: false\n },\n _isRestartingIce: {\n writable: true,\n value: false\n },\n _lastIceConnectionState: {\n writable: true,\n value: null\n },\n _lastStableDescriptionRevision: {\n writable: true,\n value: 0\n },\n _localCandidates: {\n writable: true,\n value: []\n },\n _localCodecs: {\n value: new Set()\n },\n _localCandidatesRevision: {\n writable: true,\n value: 1\n },\n _localDescriptionWithoutSimulcast: {\n writable: true,\n value: null\n },\n _localDescription: {\n writable: true,\n value: null\n },\n _localUfrag: {\n writable: true,\n value: null\n },\n _log: {\n value: log\n },\n _eventObserver: {\n value: options.eventObserver\n },\n _remoteCodecMaps: {\n value: new Map()\n },\n _rtpSenders: {\n value: new Map()\n },\n _rtpNewSenders: {\n value: new Set()\n },\n _iceConnectionMonitor: {\n value: new options.IceConnectionMonitor(peerConnection)\n },\n _mediaTrackReceivers: {\n value: new Set()\n },\n _needsAnswer: {\n writable: true,\n value: false\n },\n _negotiationRole: {\n writable: true,\n value: null\n },\n _offerOptions: {\n writable: true,\n value: options.offerOptions\n },\n _onEncodingParametersChanged: {\n value: oncePerTick(() => {\n if (!this._needsAnswer) {\n updateEncodingParameters(this);\n }\n })\n },\n _peerConnection: {\n value: peerConnection\n },\n _preferredAudioCodecs: {\n value: preferredCodecs.audio\n },\n _preferredVideoCodecs: {\n value: preferredCodecs.video\n },\n _shouldApplyDtx: {\n value: preferredCodecs.audio.every(({ codec }) => codec !== 'opus')\n || preferredCodecs.audio.some(({ codec, dtx }) => codec === 'opus' && dtx)\n },\n _queuedDescription: {\n writable: true,\n value: null\n },\n _iceReconnectTimeout: {\n value: new options.Timeout(() => {\n log.debug('ICE reconnect timed out');\n this.close();\n }, options.sessionTimeout, false)\n },\n _recycledTransceivers: {\n value: {\n audio: [],\n video: []\n }\n },\n _replaceTrackPromises: {\n value: new Map()\n },\n _remoteCandidates: {\n writable: true,\n value: new IceBox()\n },\n _setCodecPreferences: {\n // NOTE(mmalavalli): Re-ordering payload types in order to make sure a non-H264\n // preferred codec is selected does not work on Android Firefox due to this behavior:\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1683258. So, we work around this by\n // not applying any non-H264 preferred video codec.\n value: isFirefox && isAndroid && preferredCodecs.video[0] && preferredCodecs.video[0].codec.toLowerCase() !== 'h264'\n ? sdp => sdp\n : options.setCodecPreferences\n },\n _setSimulcast: {\n value: options.setSimulcast\n },\n _revertSimulcast: {\n value: options.revertSimulcast\n },\n _RTCIceCandidate: {\n value: options.RTCIceCandidate\n },\n _RTCPeerConnection: {\n value: options.RTCPeerConnection\n },\n _RTCSessionDescription: {\n value: options.RTCSessionDescription\n },\n _shouldOffer: {\n writable: true,\n value: false\n },\n _shouldRestartIce: {\n writable: true,\n value: false\n },\n _trackIdsToAttributes: {\n value: new Map(),\n writable: true\n },\n _trackMatcher: {\n writable: true,\n value: null\n },\n _mediaTrackSenderToPublisherHints: {\n value: new Map()\n },\n id: {\n enumerable: true,\n value: id\n }\n });\n\n encodingParameters.on('changed', this._onEncodingParametersChanged);\n\n peerConnection.addEventListener('connectionstatechange', this._handleConnectionStateChange.bind(this));\n peerConnection.addEventListener('datachannel', this._handleDataChannelEvent.bind(this));\n peerConnection.addEventListener('icecandidate', this._handleIceCandidateEvent.bind(this));\n peerConnection.addEventListener('iceconnectionstatechange', this._handleIceConnectionStateChange.bind(this));\n peerConnection.addEventListener('icegatheringstatechange', this._handleIceGatheringStateChange.bind(this));\n peerConnection.addEventListener('signalingstatechange', this._handleSignalingStateChange.bind(this));\n peerConnection.addEventListener('track', this._handleTrackEvent.bind(this));\n\n const self = this;\n this.on('stateChanged', function stateChanged(state) {\n if (state !== 'closed') {\n return;\n }\n self.removeListener('stateChanged', stateChanged);\n self._dataChannels.forEach((dataChannel, dataTrackSender) => {\n self.removeDataTrackSender(dataTrackSender);\n });\n });\n }\n\n toString() {\n return `[PeerConnectionV2 #${this._instanceId}: ${this.id}]`;\n }\n\n setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast) {\n this._log.debug('Setting setEffectiveAdaptiveSimulcast: ', effectiveAdaptiveSimulcast);\n // clear adaptive simulcast from codec preferences if it was set.\n this._preferredVideoCodecs.forEach(cs => {\n if ('adaptiveSimulcast' in cs) {\n cs.adaptiveSimulcast = effectiveAdaptiveSimulcast;\n }\n });\n }\n\n get _shouldApplySimulcast() {\n if (!isChrome && !isSafari) {\n return false;\n }\n\n // adaptiveSimulcast is set to false after connected message is received if other party does not support it.\n const simulcast = this._preferredVideoCodecs.some(cs => {\n return cs.codec.toLowerCase() === 'vp8' && cs.simulcast && cs.adaptiveSimulcast !== false;\n });\n\n return simulcast;\n }\n\n /**\n * The {@link PeerConnectionV2}'s underlying RTCPeerConnection's RTCPeerConnectionState\n * if supported by the browser, its RTCIceConnectionState otherwise.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this.iceConnectionState === 'failed'\n ? 'failed' : (this._peerConnection.connectionState || this.iceConnectionState);\n }\n\n /**\n * The {@link PeerConnectionV2}'s underlying RTCPeerConnection's\n * RTCIceConnectionState.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return ((this._isIceConnectionInactive && this._peerConnection.iceConnectionState === 'disconnected') || this._iceGatheringFailed)\n ? 'failed' : this._peerConnection.iceConnectionState;\n }\n\n /**\n * Whether the {@link PeerConnectionV2} has negotiated or is in the process\n * of negotiating the application m= section.\n * @returns {boolean}\n */\n get isApplicationSectionNegotiated() {\n if (this._peerConnection.signalingState !== 'closed') {\n // accessing .localDescription in 'closed' state causes it throw exceptions.\n return this._peerConnection.localDescription\n ? getMediaSections(this._peerConnection.localDescription.sdp, 'application').length > 0\n : false;\n }\n return true;\n }\n\n /**\n * Whether adaptive simulcast is enabled.\n * @returns {boolean}\n */\n get _isAdaptiveSimulcastEnabled() {\n const adaptiveSimulcastEntry = this._preferredVideoCodecs.find(cs => 'adaptiveSimulcast' in cs);\n return adaptiveSimulcastEntry && adaptiveSimulcastEntry.adaptiveSimulcast === true;\n }\n\n /**\n * @param {MediaStreamTrack} track\n * @param {Array} encodings\n * @param {boolean} trackReplaced\n * @returns {boolean} true if encodings were updated.\n */\n _maybeUpdateEncodings(track, encodings, trackReplaced = false) {\n if (track.kind !== 'video' || track.readyState === 'ended') {\n return false;\n }\n // NOTE(mmalavalli): There is no guarantee that CanvasCaptureMediaStreamTracks will always have \"width\" and \"height\"\n // in their settings. So, we don't update the encodings if they are not present.\n // Chromium bug: https://bugs.chromium.org/p/chromium/issues/detail?id=1367082\n const { height, width } = track.getSettings();\n if (typeof height !== 'number' || typeof width !== 'number') {\n return false;\n }\n // Note(mpatwardhan): always configure encodings for safari.\n // for chrome only when adaptive simulcast enabled.\n const browser = util.guessBrowser();\n if (browser === 'safari' || (browser === 'chrome' && this._isAdaptiveSimulcastEnabled)) {\n this._updateEncodings(track, encodings, trackReplaced);\n return true;\n }\n\n return false;\n }\n\n /**\n * Configures with default encodings depending on track type and resolution.\n * Default configuration sets some encodings to disabled, and for others set scaleResolutionDownBy\n * values. When trackReplaced is set to true, it will clear 'active' for any encodings that\n * needs to be enabled.\n * @param {MediaStreamTrack} track\n * @param {Array} encodings\n * @param {boolean} trackReplaced\n */\n _updateEncodings(track, encodings, trackReplaced) {\n if (this._isChromeScreenShareTrack(track)) {\n const screenShareActiveLayerConfig = [\n { scaleResolutionDownBy: 1 },\n { scaleResolutionDownBy: 1 }\n ];\n encodings.forEach((encoding, i) => {\n const activeLayerConfig = screenShareActiveLayerConfig[i];\n if (activeLayerConfig) {\n encoding.scaleResolutionDownBy = activeLayerConfig.scaleResolutionDownBy;\n if (trackReplaced) {\n delete encoding.active;\n }\n } else {\n encoding.active = false;\n delete encoding.scaleResolutionDownBy;\n }\n });\n } else {\n const { width, height } = track.getSettings();\n // NOTE(mpatwardhan): for non-screen share tracks\n // enable layers depending on track resolutions\n const pixelsToMaxActiveLayers = [\n { pixels: 960 * 540, maxActiveLayers: 3 },\n { pixels: 480 * 270, maxActiveLayers: 2 },\n { pixels: 0, maxActiveLayers: 1 }\n ];\n\n const trackPixels = width * height;\n const activeLayersInfo = pixelsToMaxActiveLayers.find(layer => trackPixels >= layer.pixels);\n const activeLayers = Math.min(encodings.length, activeLayersInfo.maxActiveLayers);\n encodings.forEach((encoding, i) => {\n const enabled = i < activeLayers;\n if (enabled) {\n encoding.scaleResolutionDownBy = 1 << (activeLayers - i - 1);\n if (trackReplaced) {\n encoding.active = true;\n }\n } else {\n encoding.active = false;\n delete encoding.scaleResolutionDownBy;\n }\n });\n }\n this._log.debug('_updateEncodings:', encodings.map(({ active, scaleResolutionDownBy }, i) => `[${i}: ${active}, ${scaleResolutionDownBy || 0}]`).join(', '));\n }\n\n /**\n * Add an ICE candidate to the {@link PeerConnectionV2}.\n * @private\n * @param {object} candidate\n * @returns {Promise}\n */\n _addIceCandidate(candidate) {\n return Promise.resolve().then(() => {\n candidate = new this._RTCIceCandidate(candidate);\n return this._peerConnection.addIceCandidate(candidate);\n }).catch(error => {\n // NOTE(mmalavalli): Firefox 68+ now generates an RTCIceCandidate with an\n // empty candidate string to signal end-of-candidates, followed by a null\n // candidate. As of now, Chrome and Safari reject this RTCIceCandidate. Since\n // this does not affect the media connection between Firefox 68+ and Chrome/Safari\n // in Peer-to-Peer Rooms, we suppress the Error and log a warning message.\n //\n // Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=978582\n //\n this._log.warn(`Failed to add RTCIceCandidate ${candidate ? `\"${candidate.candidate}\"` : 'null'}: `\n + error.message);\n });\n }\n\n /**\n * Add ICE candidates to the {@link PeerConnectionV2}.\n * @private\n * @param {Array} candidates\n * @returns {Promise}\n */\n _addIceCandidates(candidates) {\n return Promise.all(candidates.map(this._addIceCandidate, this)).then(() => {});\n }\n\n /**\n * Add a new RTCRtpTransceiver or update an existing RTCRtpTransceiver for the\n * given MediaStreamTrack.\n * @private\n * @param {MediaStreamTrack} track\n * @returns {RTCRtpTransceiver}\n */\n _addOrUpdateTransceiver(track) {\n const transceiver = takeRecycledTransceiver(this, track.kind);\n if (transceiver && transceiver.sender) {\n const oldTrackId = transceiver.sender.track ? transceiver.sender.track.id : null;\n if (oldTrackId) {\n this._log.warn(`Reusing transceiver: ${transceiver.mid}] ${oldTrackId} => ${track.id}`);\n }\n // NOTE(mpatwardhan):remember this transceiver while we replace track.\n // we recycle transceivers that are not in use after 'negotiationCompleted', but we want to prevent\n // this one from getting recycled while replaceTrack is pending.\n this._replaceTrackPromises.set(transceiver, transceiver.sender.replaceTrack(track).then(() => {\n transceiver.direction = 'sendrecv';\n }, () => {\n // Do nothing.\n }).finally(() => {\n this._replaceTrackPromises.delete(transceiver);\n }));\n return transceiver;\n }\n return this._peerConnection.addTransceiver(track);\n }\n\n /**\n * Check the {@link IceBox}.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _checkIceBox(description) {\n const ufrag = getUfrag(description);\n if (!ufrag) {\n return Promise.resolve();\n }\n const candidates = this._remoteCandidates.setUfrag(ufrag);\n return this._addIceCandidates(candidates);\n }\n\n /**\n * Create an answer and set it on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _answer(offer) {\n return Promise.resolve().then(() => {\n if (!this._negotiationRole) {\n this._negotiationRole = 'answerer';\n }\n return this._setRemoteDescription(offer);\n }).catch(() => {\n throw new MediaClientRemoteDescFailedError();\n }).then(() => {\n return this._peerConnection.createAnswer();\n }).then(answer => {\n if (isFirefox) {\n // NOTE(mmalavalli): We work around Chromium bug 1106157 by disabling\n // RTX in Firefox 79+. For more details about the bug, please go here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1106157\n answer = new this._RTCSessionDescription({\n sdp: disableRtx(answer.sdp),\n type: answer.type\n });\n } else {\n answer = workaroundIssue8329(answer);\n }\n\n // NOTE(mpatwardhan): Upcoming chrome versions are going to remove ssrc attributes\n // mslabel and label. See this bug https://bugs.chromium.org/p/webrtc/issues/detail?id=7110\n // and PSA: https://groups.google.com/forum/#!searchin/discuss-webrtc/PSA%7Csort:date/discuss-webrtc/jcZO-Wj0Wus/k2XvPCvoAwAJ\n // We are not referencing those attributes, but this changes goes ahead and removes them to see if it works.\n // this also helps reduce bytes on wires\n let updatedSdp = removeSSRCAttributes(answer.sdp, ['mslabel', 'label']);\n\n if (this._shouldApplySimulcast) {\n let sdpWithoutSimulcast = updatedSdp;\n updatedSdp = this._setSimulcast(sdpWithoutSimulcast, this._trackIdsToAttributes);\n // NOTE(syerrapragada): VMS does not support H264 simulcast. So,\n // unset simulcast for sections in local offer where corresponding\n // sections in answer doesn't have vp8 as preferred codec and reapply offer.\n updatedSdp = this._revertSimulcast(updatedSdp, sdpWithoutSimulcast, offer.sdp);\n }\n\n // NOTE(mmalavalli): Work around Chromium bug 1074421.\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1074421\n updatedSdp = updatedSdp.replace(/42e015/g, '42e01f');\n\n return this._setLocalDescription({\n type: answer.type,\n sdp: updatedSdp\n });\n }).then(() => {\n return this._checkIceBox(offer);\n }).then(() => {\n return this._queuedDescription\n && this._updateDescription(this._queuedDescription);\n }).then(() => {\n this._queuedDescription = null;\n return this._maybeReoffer(this._peerConnection.localDescription);\n }).catch(error => {\n const errorToThrow = error instanceof MediaClientRemoteDescFailedError ? error : new MediaClientLocalDescFailedError();\n this._publishMediaWarning({\n message: 'Failed to _answer',\n code: errorToThrow.code,\n error\n });\n throw errorToThrow;\n });\n }\n\n /**\n * Close the underlying RTCPeerConnection. Returns false if the\n * RTCPeerConnection was already closed.\n * @private\n * @returns {boolean}\n */\n _close() {\n this._iceConnectionMonitor.stop();\n if (this._peerConnection.signalingState !== 'closed') {\n this._peerConnection.close();\n this.preempt('closed');\n this._encodingParameters.removeListener('changed', this._onEncodingParametersChanged);\n return true;\n }\n return false;\n }\n\n /**\n * Handle a \"connectionstatechange\" event.\n * @private\n * @returns {void}\n */\n _handleConnectionStateChange() {\n this.emit('connectionStateChanged');\n }\n\n /**\n * Handle a \"datachannel\" event.\n * @private\n * @param {RTCDataChannelEvent} event\n * @returns {void}\n */\n _handleDataChannelEvent(event) {\n const dataChannel = event.channel;\n const dataTrackReceiver = new DataTrackReceiver(dataChannel);\n this._dataTrackReceivers.add(dataTrackReceiver);\n\n dataChannel.addEventListener('close', () => {\n this._dataTrackReceivers.delete(dataTrackReceiver);\n });\n\n this.emit('trackAdded', dataTrackReceiver);\n }\n\n /**\n * Handle a glare scenario on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _handleGlare(offer) {\n this._log.debug('Glare detected; rolling back');\n if (this._isRestartingIce) {\n this._log.debug('An ICE restart was in progress; we\\'ll need to restart ICE again after rolling back');\n this._isRestartingIce = false;\n this._shouldRestartIce = true;\n }\n return Promise.resolve().then(() => {\n this._trackIdsToAttributes = new Map(this._appliedTrackIdsToAttributes);\n return this._setLocalDescription({ type: 'rollback' });\n }).then(() => {\n this._needsAnswer = false;\n return this._answer(offer);\n }).then(didReoffer => {\n return didReoffer ? Promise.resolve() : this._offer();\n });\n }\n\n _publishMediaWarning({ message, code, error, sdp }) {\n this._eventObserver.emit('event', { level: 'warning', name: 'error', group: 'media', payload: {\n message,\n code,\n context: JSON.stringify({ error: error.message, sdp })\n } });\n }\n\n /**\n * Handle an ICE candidate event.\n * @private\n * @param {Event} event\n * @returns {void}\n */\n _handleIceCandidateEvent(event) {\n if (event.candidate) {\n this._log.debug('Clearing ICE gathering timeout');\n this._didGenerateLocalCandidates = true;\n this._iceGatheringTimeout.clear();\n this._localCandidates.push(event.candidate);\n }\n const peerConnectionState = {\n ice: {\n candidates: this._isIceLite ? [] : this._localCandidates.slice(),\n ufrag: this._localUfrag\n },\n id: this.id\n };\n if (!event.candidate) {\n peerConnectionState.ice.complete = true;\n }\n if (!(this._isIceLite && event.candidate)) {\n peerConnectionState.ice.revision = this._localCandidatesRevision++;\n this.emit('candidates', peerConnectionState);\n }\n }\n\n /**\n * Handle an ICE connection state change event.\n * @private\n * @returns {void}\n */\n _handleIceConnectionStateChange() {\n const { iceConnectionState } = this._peerConnection;\n const isIceConnectedOrComplete = ['connected', 'completed'].includes(iceConnectionState);\n const log = this._log;\n\n log.debug(`ICE connection state is \"${iceConnectionState}\"`);\n if (isIceConnectedOrComplete) {\n this._iceReconnectTimeout.clear();\n this._iceRestartBackoff.reset();\n }\n\n if (this._lastIceConnectionState !== 'failed' && iceConnectionState === 'failed' && !this._shouldRestartIce && !this._isRestartingIce) {\n // Case 1: Transition to \"failed\".\n log.warn('ICE failed');\n this._initiateIceRestartBackoff();\n } else if (['disconnected', 'failed'].includes(this._lastIceConnectionState) && isIceConnectedOrComplete) {\n // Case 2: Transition from \"disconnected\" or \"failed\".\n log.debug('ICE reconnected');\n }\n\n // start monitor media when connected, and continue to monitor while state is complete-disconnected-connected.\n if (iceConnectionState === 'connected') {\n this._isIceConnectionInactive = false;\n this._iceConnectionMonitor.start(() => {\n // note: iceConnection monitor waits for iceConnectionState=disconnected for\n // detecting inactivity. Its possible that it may know about disconnected before _handleIceConnectionStateChange\n this._iceConnectionMonitor.stop();\n if (!this._shouldRestartIce && !this._isRestartingIce) {\n log.warn('ICE Connection Monitor detected inactivity');\n this._isIceConnectionInactive = true;\n this._initiateIceRestartBackoff();\n this.emit('iceConnectionStateChanged');\n this.emit('connectionStateChanged');\n }\n });\n } else if (!['disconnected', 'completed'].includes(iceConnectionState)) { // don't stop monitoring for disconnected or completed.\n this._iceConnectionMonitor.stop();\n this._isIceConnectionInactive = false;\n }\n\n this._lastIceConnectionState = iceConnectionState;\n this.emit('iceConnectionStateChanged');\n }\n\n /**\n * Handle ICE gathering timeout.\n * @private\n * @returns {void}\n */\n _handleIceGatheringTimeout() {\n this._log.warn('ICE failed to gather any local candidates');\n this._iceGatheringFailed = true;\n this._initiateIceRestartBackoff();\n this.emit('iceConnectionStateChanged');\n this.emit('connectionStateChanged');\n }\n\n /**\n * Handle an ICE gathering state change event.\n * @private\n * @returns {void}\n */\n _handleIceGatheringStateChange() {\n const { iceGatheringState } = this._peerConnection;\n const log = this._log;\n log.debug(`ICE gathering state is \"${iceGatheringState}\"`);\n\n // NOTE(mmalavalli): Start the ICE gathering timeout only if the RTCPeerConnection\n // has started gathering candidates for the first time since the initial offer/answer\n // or an offer/answer with ICE restart.\n const { delay, isSet } = this._iceGatheringTimeout;\n if (iceGatheringState === 'gathering' && !this._didGenerateLocalCandidates && !isSet) {\n log.debug(`Starting ICE gathering timeout: ${delay}`);\n this._iceGatheringFailed = false;\n this._iceGatheringTimeout.start();\n }\n }\n\n /**\n * Handle a signaling state change event.\n * @private\n * @returns {void}\n */\n _handleSignalingStateChange() {\n if (this._peerConnection.signalingState === 'stable') {\n this._appliedTrackIdsToAttributes = new Map(this._trackIdsToAttributes);\n }\n }\n\n /**\n * Handle a track event.\n * @private\n * @param {RTCTrackEvent} event\n * @returns {void}\n */\n _handleTrackEvent(event) {\n const sdp = this._peerConnection.remoteDescription\n ? this._peerConnection.remoteDescription.sdp\n : null;\n\n this._trackMatcher = this._trackMatcher || new TrackMatcher();\n this._trackMatcher.update(sdp);\n\n const mediaStreamTrack = event.track;\n const signaledTrackId = this._trackMatcher.match(event) || mediaStreamTrack.id;\n const mediaTrackReceiver = new MediaTrackReceiver(signaledTrackId, mediaStreamTrack);\n\n // NOTE(mmalavalli): \"ended\" is not fired on the remote MediaStreamTrack when\n // the remote peer removes a track. So, when this MediaStreamTrack is re-used\n // for a different track due to the remote peer calling RTCRtpSender.replaceTrack(),\n // we delete the previous MediaTrackReceiver that owned this MediaStreamTrack\n // before adding the new MediaTrackReceiver.\n this._mediaTrackReceivers.forEach(trackReceiver => {\n if (trackReceiver.track.id === mediaTrackReceiver.track.id) {\n this._mediaTrackReceivers.delete(trackReceiver);\n }\n });\n\n this._mediaTrackReceivers.add(mediaTrackReceiver);\n mediaStreamTrack.addEventListener('ended', () => this._mediaTrackReceivers.delete(mediaTrackReceiver));\n this.emit('trackAdded', mediaTrackReceiver);\n }\n\n /**\n * Initiate ICE Restart.\n * @private\n * @returns {void}\n */\n _initiateIceRestart() {\n if (this._peerConnection.signalingState === 'closed') {\n return;\n }\n const log = this._log;\n log.warn('Attempting to restart ICE');\n this._didGenerateLocalCandidates = false;\n this._isIceRestartBackoffInProgress = false;\n this._shouldRestartIce = true;\n\n const { delay, isSet } = this._iceReconnectTimeout;\n if (!isSet) {\n log.debug(`Starting ICE reconnect timeout: ${delay}`);\n this._iceReconnectTimeout.start();\n }\n this.offer().catch(ex => {\n log.error(`offer failed in _initiateIceRestart with: ${ex.message}`);\n });\n }\n\n /**\n * Schedule an ICE Restart.\n * @private\n * @returns {void}\n */\n _initiateIceRestartBackoff() {\n if (this._peerConnection.signalingState === 'closed' || this._isIceRestartBackoffInProgress) {\n return;\n }\n this._log.warn('An ICE restart has been scheduled');\n this._isIceRestartBackoffInProgress = true;\n this._iceRestartBackoff.backoff(() => this._initiateIceRestart());\n }\n\n /**\n * Conditionally re-offer.\n * @private\n * @param {?RTCSessionDescriptionInit} localDescription\n * @returns {Promise}\n */\n _maybeReoffer(localDescription) {\n let shouldReoffer = this._shouldOffer;\n\n if (localDescription && localDescription.sdp) {\n // NOTE(mmalavalli): If the local RTCSessionDescription has fewer audio and/or\n // video send* m= lines than the corresponding RTCRtpSenders with non-null\n // MediaStreamTracks, it means that the newly added RTCRtpSenders require\n // renegotiation.\n const senders = this._peerConnection.getSenders().filter(sender => sender.track);\n shouldReoffer = ['audio', 'video'].reduce((shouldOffer, kind) => {\n const mediaSections = getMediaSections(localDescription.sdp, kind, '(sendrecv|sendonly)');\n const sendersOfKind = senders.filter(isSenderOfKind.bind(null, kind));\n return shouldOffer || (mediaSections.length < sendersOfKind.length);\n }, shouldReoffer);\n\n // NOTE(mroberts): We also need to re-offer if we have a DataTrack to share\n // but no m= application section.\n const hasDataTrack = this._dataChannels.size > 0;\n const hasApplicationMediaSection = getMediaSections(localDescription.sdp, 'application').length > 0;\n const needsApplicationMediaSection = hasDataTrack && !hasApplicationMediaSection;\n shouldReoffer = shouldReoffer || needsApplicationMediaSection;\n }\n\n const promise = shouldReoffer ? this._offer() : Promise.resolve();\n return promise.then(() => shouldReoffer);\n }\n\n /**\n * Create an offer and set it on the {@link PeerConnectionV2}.\n * @private\n * @returns {Promise}\n */\n _offer() {\n const offerOptions = Object.assign({}, this._offerOptions);\n this._needsAnswer = true;\n if (this._shouldRestartIce) {\n this._shouldRestartIce = false;\n this._isRestartingIce = true;\n offerOptions.iceRestart = true;\n }\n\n return Promise.all(this._replaceTrackPromises.values()).then(() => {\n return this._peerConnection.createOffer(offerOptions);\n }).catch(error => {\n const errorToThrow = new MediaClientLocalDescFailedError();\n this._publishMediaWarning({\n message: 'Failed to create offer',\n code: errorToThrow.code,\n error\n });\n throw errorToThrow;\n }).then(offer => {\n if (isFirefox) {\n // NOTE(mmalavalli): We work around Chromium bug 1106157 by disabling\n // RTX in Firefox 79+. For more details about the bug, please go here:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=1106157\n offer = new this._RTCSessionDescription({\n sdp: disableRtx(offer.sdp),\n type: offer.type\n });\n } else {\n offer = workaroundIssue8329(offer);\n }\n\n // NOTE(mpatwardhan): upcoming chrome versions are going to remove ssrc attributes\n // mslabel and label. See this bug https://bugs.chromium.org/p/webrtc/issues/detail?id=7110\n // and PSA: https://groups.google.com/forum/#!searchin/discuss-webrtc/PSA%7Csort:date/discuss-webrtc/jcZO-Wj0Wus/k2XvPCvoAwAJ\n // Looks like we are not referencing those attributes, but this changes goes ahead and removes them to see if it works.\n // this also helps reduce bytes on wires\n let sdp = removeSSRCAttributes(offer.sdp, ['mslabel', 'label']);\n sdp = this._peerConnection.remoteDescription\n ? filterLocalCodecs(sdp, this._peerConnection.remoteDescription.sdp)\n : sdp;\n\n let updatedSdp = this._setCodecPreferences(\n sdp,\n this._preferredAudioCodecs,\n this._preferredVideoCodecs);\n\n this._shouldOffer = false;\n if (!this._negotiationRole) {\n this._negotiationRole = 'offerer';\n }\n\n if (this._shouldApplySimulcast) {\n this._localDescriptionWithoutSimulcast = {\n type: 'offer',\n sdp: updatedSdp\n };\n updatedSdp = this._setSimulcast(updatedSdp, this._trackIdsToAttributes);\n }\n return this._setLocalDescription({\n type: 'offer',\n sdp: updatedSdp\n });\n });\n }\n\n /**\n * Get the MediaTrackSender ID of the given MediaStreamTrack ID.\n * Since a MediaTrackSender's underlying MediaStreamTrack can be\n * replaced, the corresponding IDs can mismatch.\n * @private\n * @param {Track.ID} id\n * @returns {Track.ID}\n */\n _getMediaTrackSenderId(trackId) {\n const mediaTrackSender = Array.from(this._rtpSenders.keys()).find(({ track: { id } }) => id === trackId);\n return mediaTrackSender ? mediaTrackSender.id : trackId;\n }\n\n /**\n * Add or rewrite local MediaStreamTrack IDs in the given RTCSessionDescription.\n * @private\n * @param {RTCSessionDescription} description\n * @return {RTCSessionDescription}\n */\n _addOrRewriteLocalTrackIds(description) {\n const transceivers = this._peerConnection.getTransceivers();\n const activeTransceivers = transceivers.filter(({ sender, stopped }) => !stopped && sender && sender.track);\n\n // NOTE(mmalavalli): There is no guarantee that MediaStreamTrack IDs will be present in\n // SDPs, and even if they are, there is no guarantee that they will be the same as the\n // actual MediaStreamTrack IDs. So, we add or re-write the actual MediaStreamTrack IDs\n // to the assigned m= sections here.\n const assignedTransceivers = activeTransceivers.filter(({ mid }) => mid);\n const midsToTrackIds = new Map(assignedTransceivers.map(({ mid, sender }) => [mid, this._getMediaTrackSenderId(sender.track.id)]));\n const sdp1 = addOrRewriteTrackIds(description.sdp, midsToTrackIds);\n\n // NOTE(mmalavalli): Chrome and Safari do not apply the offer until they get an answer.\n // So, we add or re-write the actual MediaStreamTrack IDs to the unassigned m= sections here.\n const unassignedTransceivers = activeTransceivers.filter(({ mid }) => !mid);\n const newTrackIdsByKind = new Map(['audio', 'video'].map(kind => [\n kind,\n unassignedTransceivers.filter(({ sender }) => sender.track.kind === kind).map(({ sender }) => this._getMediaTrackSenderId(sender.track.id))\n ]));\n const sdp2 = addOrRewriteNewTrackIds(sdp1, midsToTrackIds, newTrackIdsByKind);\n\n return new this._RTCSessionDescription({\n sdp: sdp2,\n type: description.type\n });\n }\n\n /**\n * Rollback and apply the given offer.\n * @private\n * @param {RTCSessionDescriptionInit} offer\n * @returns {Promise}\n */\n _rollbackAndApplyOffer(offer) {\n return this._setLocalDescription({ type: 'rollback' }).then(() => this._setLocalDescription(offer));\n }\n\n /**\n * Set a local description on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescription|RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _setLocalDescription(description) {\n if (description.type !== 'rollback' && this._shouldApplyDtx) {\n description = new this._RTCSessionDescription({\n sdp: enableDtxForOpus(description.sdp),\n type: description.type\n });\n }\n return this._peerConnection.setLocalDescription(description).catch(error => {\n this._log.warn(`Calling setLocalDescription with an RTCSessionDescription of type \"${description.type}\" failed with the error \"${error.message}\".`, error);\n\n const errorToThrow = new MediaClientLocalDescFailedError();\n const publishWarning = {\n message: `Calling setLocalDescription with an RTCSessionDescription of type \"${description.type}\" failed`,\n code: errorToThrow.code,\n error\n };\n\n if (description.sdp) {\n this._log.warn(`The SDP was ${description.sdp}`);\n publishWarning.sdp = description.sdp;\n }\n this._publishMediaWarning(publishWarning);\n throw errorToThrow;\n }).then(() => {\n if (description.type !== 'rollback') {\n this._localDescription = this._addOrRewriteLocalTrackIds(description);\n\n // NOTE(mmalavalli): In order for this feature to be backward compatible with older\n // SDK versions which to not support opus DTX, we append \"usedtx=1\" to the local SDP\n // only while applying it. We will not send it over the wire to prevent inadvertent\n // enabling of opus DTX in older SDKs. Newer SDKs will append \"usedtx=1\" by themselves\n // if the developer has requested opus DTX to be enabled. (JSDK-3063)\n if (this._shouldApplyDtx) {\n this._localDescription = new this._RTCSessionDescription({\n sdp: enableDtxForOpus(this._localDescription.sdp, []),\n type: this._localDescription.type\n });\n }\n\n this._localCandidates = [];\n if (description.type === 'offer') {\n this._descriptionRevision++;\n } else if (description.type === 'answer') {\n this._lastStableDescriptionRevision = this._descriptionRevision;\n negotiationCompleted(this);\n }\n this._localUfrag = getUfrag(description);\n this.emit('description', this.getState());\n }\n });\n }\n\n /**\n * Set a remote RTCSessionDescription on the {@link PeerConnectionV2}.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _setRemoteDescription(description) {\n if (description.sdp) {\n description.sdp = this._setCodecPreferences(\n description.sdp,\n this._preferredAudioCodecs,\n this._preferredVideoCodecs);\n\n if (this._shouldApplyDtx) {\n description.sdp = enableDtxForOpus(description.sdp);\n } else {\n // NOTE(mmalavalli): Remove \"usedtx=1\" from opus's fmtp line if present\n // since DTX is disabled.\n description.sdp = enableDtxForOpus(description.sdp, []);\n }\n\n if (isFirefox) {\n // NOTE(mroberts): Do this to reduce our MediaStream count in Firefox. By\n // mapping MediaStream IDs in the SDP to \"-\", we ensure the \"track\" event\n // doesn't include any new MediaStreams in Firefox. Its `streams` member\n // will always be the empty Array.\n description.sdp = filterOutMediaStreamIds(description.sdp);\n }\n if (!this._peerConnection.remoteDescription) {\n this._isIceLite = /a=ice-lite/.test(description.sdp);\n }\n }\n description = new this._RTCSessionDescription(description);\n // eslint-disable-next-line consistent-return\n return Promise.resolve().then(() => {\n // NOTE(syerrapragada): VMS does not support H264 simulcast. So,\n // unset simulcast for sections in local offer where corresponding\n // sections in answer doesn't have vp8 as preferred codec and reapply offer.\n if (description.type === 'answer' && this._localDescriptionWithoutSimulcast) {\n // NOTE(mpatwardhan):if we were using adaptive simulcast, and if its not supported by server\n // revert simulcast even for vp8.\n const adaptiveSimulcastEntry = this._preferredVideoCodecs.find(cs => 'adaptiveSimulcast' in cs);\n const revertForAll = !!adaptiveSimulcastEntry && adaptiveSimulcastEntry.adaptiveSimulcast === false;\n const sdpWithoutSimulcastForNonVP8MediaSections = this._revertSimulcast(\n this._localDescription.sdp,\n this._localDescriptionWithoutSimulcast.sdp,\n description.sdp, revertForAll);\n this._localDescriptionWithoutSimulcast = null;\n if (sdpWithoutSimulcastForNonVP8MediaSections !== this._localDescription.sdp) {\n return this._rollbackAndApplyOffer({\n type: this._localDescription.type,\n sdp: sdpWithoutSimulcastForNonVP8MediaSections\n });\n }\n }\n }).then(() => this._peerConnection.setRemoteDescription(description)).then(() => {\n if (description.type === 'answer') {\n if (this._isRestartingIce) {\n this._log.debug('An ICE restart was in-progress and is now completed');\n this._isRestartingIce = false;\n }\n negotiationCompleted(this);\n }\n }, error => {\n this._log.warn(`Calling setRemoteDescription with an RTCSessionDescription of type \"${description.type}\" failed with the error \"${error.message}\".`, error);\n if (description.sdp) {\n this._log.warn(`The SDP was ${description.sdp}`);\n }\n throw error;\n });\n }\n\n /**\n * Update the {@link PeerConnectionV2}'s description.\n * @private\n * @param {RTCSessionDescriptionInit} description\n * @returns {Promise}\n */\n _updateDescription(description) {\n switch (description.type) {\n case 'answer':\n case 'pranswer':\n if (description.revision !== this._descriptionRevision\n || this._peerConnection.signalingState !== 'have-local-offer') {\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n break;\n case 'close':\n return this._close();\n case 'create-offer':\n if (description.revision <= this._lastStableDescriptionRevision) {\n return Promise.resolve();\n } else if (this._needsAnswer) {\n this._queuedDescription = description;\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n return this._offer();\n case 'offer':\n if (description.revision <= this._lastStableDescriptionRevision\n || this._peerConnection.signalingState === 'closed') {\n return Promise.resolve();\n }\n if (this._peerConnection.signalingState === 'have-local-offer') {\n // NOTE(mpatwardhan): For a peer connection\n // 1) createOffer always generate SDP with `setup:actpass`\n // 2) when remote description is set `setup:active` - the answer generated selects the dtls role of setup:passive\n // 3) when remote description is set `setup:passive` - the answer generated selects the dtls role of setup:active\n // 4) when remote description is set `setup:actpass` - the answer generated uses the previously negotiated role (if not negotiated previously setup:active is used)\n // This test shows the behavior: https://github.com/twilio/twilio-webrtc.js/blob/master/test/integration/spec/rtcpeerconnection.js#L936\n // with glare handling (if dtls role was not negotiated before ) the generated answer will set setup:active.\n // we do not want that. lets wait for \"initial negotiation\" before attempting glare handling.\n if (this._needsAnswer && this._lastStableDescriptionRevision === 0) {\n this._queuedDescription = description;\n return Promise.resolve();\n }\n this._descriptionRevision = description.revision;\n return this._handleGlare(description);\n }\n this._descriptionRevision = description.revision;\n return this._answer(description).then(() => {});\n default:\n // Do nothing.\n }\n\n // Handle answer or pranswer.\n const revision = description.revision;\n return Promise.resolve().then(() => {\n return this._setRemoteDescription(description);\n }).catch(error => {\n const errorToThrow = new MediaClientRemoteDescFailedError();\n this._publishMediaWarning({\n message: `Calling setRemoteDescription with an RTCSessionDescription of type \"${description.type}\" failed`,\n code: errorToThrow.code,\n error,\n sdp: description.sdp\n });\n throw errorToThrow;\n }).then(() => {\n this._lastStableDescriptionRevision = revision;\n this._needsAnswer = false;\n return this._checkIceBox(description);\n }).then(() => {\n return this._queuedDescription\n && this._updateDescription(this._queuedDescription);\n }).then(() => {\n this._queuedDescription = null;\n return this._maybeReoffer(this._peerConnection.localDescription).then(() => {});\n });\n }\n\n /**\n * Update the {@link PeerConnectionV2}'s ICE candidates.\n * @private\n * @param {object} iceState\n * @returns {Promise}\n */\n _updateIce(iceState) {\n const candidates = this._remoteCandidates.update(iceState);\n return this._addIceCandidates(candidates);\n }\n\n /**\n * Add a {@link DataTrackSender} to the {@link PeerConnectionV2}.\n * @param {DataTrackSender} dataTrackSender\n * @returns {void}\n */\n addDataTrackSender(dataTrackSender) {\n if (this._dataChannels.has(dataTrackSender)) {\n return;\n }\n try {\n const dataChannelDict = {\n ordered: dataTrackSender.ordered\n };\n if (dataTrackSender.maxPacketLifeTime !== null) {\n dataChannelDict.maxPacketLifeTime = dataTrackSender.maxPacketLifeTime;\n }\n if (dataTrackSender.maxRetransmits !== null) {\n dataChannelDict.maxRetransmits = dataTrackSender.maxRetransmits;\n }\n const dataChannel = this._peerConnection.createDataChannel(dataTrackSender.id, dataChannelDict);\n dataTrackSender.addDataChannel(dataChannel);\n this._dataChannels.set(dataTrackSender, dataChannel);\n } catch (error) {\n this._log.warn(`Error creating an RTCDataChannel for DataTrack \"${dataTrackSender.id}\": ${error.message}`);\n }\n }\n\n _handleQueuedPublisherHints() {\n if (this._peerConnection.signalingState === 'stable') {\n this._mediaTrackSenderToPublisherHints.forEach(({ deferred, encodings }, mediaTrackSender) => {\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n this._setPublisherHint(mediaTrackSender, encodings)\n .then(result => deferred.resolve(result))\n .catch(error => deferred.reject(error));\n });\n }\n }\n\n /**\n * updates encodings for simulcast layers of given sender.\n * @param {RTCRtpSender} sender\n * @param {Array<{enabled: boolean, layer_index: number}>|null} encodings\n * @returns {Promise} string indicating result of the operation. can be one of\n * \"OK\", \"INVALID_HINT\", \"COULD_NOT_APPLY_HINT\", \"UNKNOWN_TRACK\"\n */\n _setPublisherHint(mediaTrackSender, encodings) {\n if (isFirefox) {\n return Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n if (this._mediaTrackSenderToPublisherHints.has(mediaTrackSender)) {\n // skip any stale hint associated with the mediaTrackSender.\n const queuedHint = this._mediaTrackSenderToPublisherHints.get(mediaTrackSender);\n queuedHint.deferred.resolve('REQUEST_SKIPPED');\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n }\n\n const sender = this._rtpSenders.get(mediaTrackSender);\n if (!sender) {\n this._log.warn('Could not apply publisher hint because RTCRtpSender was not found');\n return Promise.resolve('UNKNOWN_TRACK');\n }\n\n if (this._peerConnection.signalingState === 'closed') {\n this._log.warn('Could not apply publisher hint because signalingState was \"closed\"');\n return Promise.resolve('COULD_NOT_APPLY_HINT');\n }\n\n if (this._peerConnection.signalingState !== 'stable') {\n // enqueue this hint to be applied when pc becomes stable.\n this._log.debug('Queuing up publisher hint because signalingState:', this._peerConnection.signalingState);\n const deferred = defer();\n this._mediaTrackSenderToPublisherHints.set(mediaTrackSender, { deferred, encodings });\n return deferred.promise;\n }\n\n const parameters = sender.getParameters();\n if (encodings !== null) {\n encodings.forEach(({ enabled, layer_index: layerIndex }) => {\n if (parameters.encodings.length > layerIndex) {\n this._log.debug(`layer:${layerIndex}, active:${parameters.encodings[layerIndex].active} => ${enabled}`);\n parameters.encodings[layerIndex].active = enabled;\n } else {\n this._log.warn(`invalid layer:${layerIndex}, active:${enabled}`);\n }\n });\n }\n\n // Note(mpatwardhan): after publisher hints are applied, overwrite with default encodings\n // to disable any encoding that shouldn't have been enabled by publisher_hints.\n // When encodings===null (that is we are asked to reset encodings for replaceTrack)\n // along with disabling encodings, clear active flag for encodings that should not be disabled\n this._maybeUpdateEncodings(sender.track, parameters.encodings, encodings === null /* trackReplaced */);\n\n return sender.setParameters(parameters).then(() => 'OK').catch(error => {\n this._log.error('Failed to apply publisher hints:', error);\n return 'COULD_NOT_APPLY_HINT';\n });\n }\n\n /**\n * Add the {@link MediaTrackSender} to the {@link PeerConnectionV2}.\n * @param {MediaTrackSender} mediaTrackSender\n * @returns {void}\n */\n addMediaTrackSender(mediaTrackSender) {\n if (this._peerConnection.signalingState === 'closed' || this._rtpSenders.has(mediaTrackSender)) {\n return;\n }\n const transceiver = this._addOrUpdateTransceiver(mediaTrackSender.track);\n const { sender } = transceiver;\n mediaTrackSender.addSender(sender, encodings => this._setPublisherHint(mediaTrackSender, encodings));\n this._rtpNewSenders.add(sender);\n this._rtpSenders.set(mediaTrackSender, sender);\n }\n\n /**\n * Close the {@link PeerConnectionV2}.\n * @returns {void}\n */\n close() {\n if (this._close()) {\n this._descriptionRevision++;\n this._localDescription = { type: 'close' };\n this.emit('description', this.getState());\n }\n }\n\n /**\n * Get the {@link DataTrackReceiver}s and the {@link MediaTrackReceiver}s on the\n * {@link PeerConnectionV2}.\n * @returns {Array} trackReceivers\n */\n getTrackReceivers() {\n return Array.from(this._dataTrackReceivers).concat(Array.from(this._mediaTrackReceivers));\n }\n\n /**\n * Get the {@link PeerConnectionV2}'s state (specifically, its description).\n * @returns {?object}\n */\n getState() {\n if (!this._localDescription) {\n return null;\n }\n\n // NOTE(mpatwardhan): Return most recent localDescription. If the most recent local description is an\n // answer, and this method is called for sending a \"sync\" message while the next remote offer is being processed,\n // we need to send the most recent stable description revision instead of the current description revision,\n // which is supposed to be for the next local answer.\n const localDescriptionRevision = this._localDescription.type === 'answer' ? this._lastStableDescriptionRevision : this._descriptionRevision;\n const localDescription = {\n type: this._localDescription.type,\n revision: localDescriptionRevision\n };\n if (this._localDescription.sdp) {\n localDescription.sdp = this._localDescription.sdp;\n }\n return {\n description: localDescription,\n id: this.id\n };\n }\n\n /**\n * Create an offer and set it on the {@link PeerConnectionV2}.\n * @returns {Promise}\n */\n offer() {\n if (this._needsAnswer || this._isRestartingIce) {\n this._shouldOffer = true;\n return Promise.resolve();\n }\n\n return this.bracket('offering', key => {\n this.transition('updating', key);\n const promise = this._needsAnswer || this._isRestartingIce ? Promise.resolve() : this._offer();\n return promise.then(() => {\n this.tryTransition('open', key);\n }, error => {\n this.tryTransition('open', key);\n throw error;\n });\n });\n }\n\n /**\n * Remove a {@link DataTrackSender} from the {@link PeerConnectionV2}.\n * @param {DataTrackSender} dataTrackSender\n * @returns {void}\n */\n removeDataTrackSender(dataTrackSender) {\n const dataChannel = this._dataChannels.get(dataTrackSender);\n if (dataChannel) {\n dataTrackSender.removeDataChannel(dataChannel);\n this._dataChannels.delete(dataTrackSender);\n dataChannel.close();\n }\n }\n\n /**\n * Remove the {@link MediaTrackSender} from the {@link PeerConnectionV2}.\n * @param {MediaTrackSender} mediaTrackSender\n * @returns {void}\n */\n removeMediaTrackSender(mediaTrackSender) {\n const sender = this._rtpSenders.get(mediaTrackSender);\n if (!sender) {\n return;\n }\n if (this._peerConnection.signalingState !== 'closed') {\n this._peerConnection.removeTrack(sender);\n }\n mediaTrackSender.removeSender(sender);\n // clean up any pending publisher hints associated with this mediaTrackSender.\n if (this._mediaTrackSenderToPublisherHints.has(mediaTrackSender)) {\n const queuedHint = this._mediaTrackSenderToPublisherHints.get(mediaTrackSender);\n queuedHint.deferred.resolve('UNKNOWN_TRACK');\n this._mediaTrackSenderToPublisherHints.delete(mediaTrackSender);\n }\n this._rtpNewSenders.delete(sender);\n this._rtpSenders.delete(mediaTrackSender);\n }\n\n /**\n * Set the RTCConfiguration on the underlying RTCPeerConnection.\n * @param {RTCConfiguration} configuration\n * @returns {void}\n */\n setConfiguration(configuration) {\n if (typeof this._peerConnection.setConfiguration === 'function') {\n this._peerConnection.setConfiguration(getConfiguration(configuration));\n }\n }\n\n /**\n * Set the ICE reconnect timeout period.\n * @param {number} period - Period in milliseconds.\n * @returns {this}\n */\n setIceReconnectTimeout(period) {\n this._iceReconnectTimeout.setDelay(period);\n this._log.debug('Updated ICE reconnection timeout period:',\n this._iceReconnectTimeout.delay);\n return this;\n }\n\n /**\n * Update the {@link PeerConnectionV2}.\n * @param {object} peerConnectionState\n * @returns {Promise}\n */\n update(peerConnectionState) {\n return this.bracket('updating', key => {\n if (this.state === 'closed') {\n return Promise.resolve();\n }\n\n this.transition('updating', key);\n\n const updates = [];\n\n if (peerConnectionState.ice) {\n updates.push(this._updateIce(peerConnectionState.ice));\n }\n\n if (peerConnectionState.description) {\n updates.push(this._updateDescription(peerConnectionState.description));\n }\n\n return Promise.all(updates).then(() => {\n this.tryTransition('open', key);\n }, error => {\n this.tryTransition('open', key);\n throw error;\n });\n });\n }\n\n /**\n * Get the {@link PeerConnectionV2}'s media statistics.\n * @returns {Promise}\n */\n getStats() {\n return getStatistics(this._peerConnection).then(response => rewriteTrackIds(this, response));\n }\n}\n\nfunction rewriteLocalTrackId(pcv2, stats) {\n const trackId = pcv2._getMediaTrackSenderId(stats.trackId);\n return Object.assign(stats, { trackId });\n}\n\nfunction rewriteTrackId(pcv2, stats) {\n const receiver = [...pcv2._mediaTrackReceivers]\n .find(receiver => receiver.track.id === stats.trackId);\n const trackId = receiver ? receiver.id : null;\n return Object.assign(stats, { trackId });\n}\n\nfunction rewriteTrackIds(pcv2, response) {\n return Object.assign(response, {\n remoteAudioTrackStats: response.remoteAudioTrackStats.map(stats => rewriteTrackId(pcv2, stats)),\n remoteVideoTrackStats: response.remoteVideoTrackStats.map(stats => rewriteTrackId(pcv2, stats)),\n localAudioTrackStats: response.localAudioTrackStats.map(stats => rewriteLocalTrackId(pcv2, stats)),\n localVideoTrackStats: response.localVideoTrackStats.map(stats => rewriteLocalTrackId(pcv2, stats)),\n });\n}\n\n/**\n * @event PeerConnectionV2#candidates\n * @param {object} candidates\n */\n\n/**\n * @event PeerConnectionV2#connectionStateChanged\n */\n\n/**\n * @event PeerConnectionV2#description\n * @param {object} description\n */\n\n/**\n * @event PeerConnectionV2#iceConnectionStateChanged\n */\n\n/**\n * @event PeerConnectionV2#trackAdded\n * @param {DataTrackReceiver|MediaTrackReceiver} trackReceiver\n */\n\nfunction getUfrag(description) {\n if (description.sdp) {\n const match = description.sdp.match(/^a=ice-ufrag:([a-zA-Z0-9+/]+)/m);\n if (match) {\n return match[1];\n }\n }\n return null;\n}\n\nfunction getConfiguration(configuration) {\n return Object.assign({\n bundlePolicy: 'max-bundle',\n rtcpMuxPolicy: 'require'\n }, configuration);\n}\n\n/**\n * Whether the MediaStreamTrack of the given RTCRTPSender is a non-ended\n * MediaStreamTrack of a given kind.\n * @private\n * @param {string} kind\n * @param {RTCRtpSender} sender\n * @return {boolean}\n */\nfunction isSenderOfKind(kind, sender) {\n const track = sender.track;\n return track && track.kind === kind && track.readyState !== 'ended';\n}\n\n/**\n * Preferred codecs.\n * @typedef {object} PreferredCodecs\n * @property {Array} audio\n * @property {Array} video\n */\n\nfunction filterOutMediaStreamIds(sdp) {\n return sdp.replace(/a=msid:[^ ]+ /g, 'a=msid:- ');\n}\n\n/**\n * Whether an RTCRtpTransceiver can be recycled.\n * @param {RTCRtpTransceiver} transceiver\n * @returns {boolean}\n */\nfunction shouldRecycleTransceiver(transceiver, pcv2) {\n return !transceiver.stopped\n && !pcv2._replaceTrackPromises.has(transceiver)\n && ['inactive', 'recvonly'].includes(transceiver.direction);\n}\n\n/**\n * Take a recycled RTCRtpTransceiver if available.\n * @param {PeerConnectionV2} pcv2\n * @param {Track.Kind} kind\n * @returns {?RTCRtpTransceiver}\n */\nfunction takeRecycledTransceiver(pcv2, kind) {\n const preferredCodecs = {\n audio: pcv2._preferredAudioCodecs.map(({ codec }) => codec.toLowerCase()),\n video: pcv2._preferredVideoCodecs.map(({ codec }) => codec.toLowerCase())\n }[kind];\n\n const recycledTransceivers = pcv2._recycledTransceivers[kind];\n const localCodec = preferredCodecs.find(codec => pcv2._localCodecs.has(codec));\n if (!localCodec) {\n return recycledTransceivers.shift();\n }\n\n const transceiver = recycledTransceivers.find(transceiver => {\n const remoteCodecMap = pcv2._remoteCodecMaps.get(transceiver.mid);\n return remoteCodecMap && remoteCodecMap.has(localCodec);\n });\n\n if (transceiver) {\n recycledTransceivers.splice(recycledTransceivers.indexOf(transceiver), 1);\n }\n return transceiver;\n}\n\n/**\n * Update the set of locally supported {@link Codec}s.\n * @param pcv2\n * @returns {void}\n */\nfunction updateLocalCodecs(pcv2) {\n const description = pcv2._peerConnection.localDescription;\n if (!description || !description.sdp) {\n return;\n }\n getMediaSections(description.sdp).forEach(section => {\n const codecMap = createCodecMapForMediaSection(section);\n codecMap.forEach((pts, codec) => pcv2._localCodecs.add(codec));\n });\n}\n\n/**\n * Update the {@link Codec} maps for all m= sections in the remote {@link RTCSessionDescription}s.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction updateRemoteCodecMaps(pcv2) {\n const description = pcv2._peerConnection.remoteDescription;\n if (!description || !description.sdp) {\n return;\n }\n getMediaSections(description.sdp).forEach(section => {\n const matched = section.match(/^a=mid:(.+)$/m);\n if (!matched || !matched[1]) {\n return;\n }\n const mid = matched[1];\n const codecMap = createCodecMapForMediaSection(section);\n pcv2._remoteCodecMaps.set(mid, codecMap);\n });\n}\n\n/**\n * Update the list of recycled RTCRtpTransceivers.\n * @param {PeerConnectionV2} pcv2\n */\nfunction updateRecycledTransceivers(pcv2) {\n pcv2._recycledTransceivers.audio = [];\n pcv2._recycledTransceivers.video = [];\n pcv2._peerConnection.getTransceivers().forEach(transceiver => {\n if (shouldRecycleTransceiver(transceiver, pcv2)) {\n const track = transceiver.receiver.track;\n pcv2._recycledTransceivers[track.kind].push(transceiver);\n }\n });\n}\n\n/**\n * Perform certain updates after an SDP negotiation is completed.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction negotiationCompleted(pcv2) {\n updateRecycledTransceivers(pcv2);\n updateLocalCodecs(pcv2);\n updateRemoteCodecMaps(pcv2);\n updateEncodingParameters(pcv2).then(() => {\n // if there any any publisher hints queued, apply them now.\n pcv2._handleQueuedPublisherHints();\n });\n}\n\n/**\n * Update the RTCRtpEncodingParameters of all active RTCRtpSenders.\n * @param {PeerConnectionV2} pcv2\n * @returns {void}\n */\nfunction updateEncodingParameters(pcv2) {\n const { maxAudioBitrate, maxVideoBitrate } = pcv2._encodingParameters;\n\n const maxBitrates = new Map([\n ['audio', maxAudioBitrate],\n ['video', maxVideoBitrate]\n ]);\n\n const promises = [];\n pcv2._peerConnection.getSenders().filter(sender => sender.track).forEach(sender => {\n const maxBitrate = maxBitrates.get(sender.track.kind);\n const params = sender.getParameters();\n\n if (maxBitrate === null || maxBitrate === 0) {\n removeMaxBitrate(params);\n } else if (pcv2._isChromeScreenShareTrack(sender.track)) {\n // NOTE(mpatwardhan): Sometimes (JSDK-2557) chrome does not send any bytes on screen track if MaxBitRate is set on it via setParameters,\n // To workaround this issue we will not apply maxBitrate if the track appears to be a screen share track created by chrome\n pcv2._log.warn(`Not setting maxBitrate for ${sender.track.kind} Track ${sender.track.id} because it appears to be screen share track: ${sender.track.label}`);\n } else {\n setMaxBitrate(params, maxBitrate);\n }\n\n if (!isFirefox && params.encodings.length > 0) {\n if (sender.track.kind === 'audio') {\n // NOTE(mmalavalli): \"priority\" is a per-sender property and not\n // a per-encoding-layer property. So, we set the value only on the first\n // encoding layer. Any attempt to set the value on subsequent encoding\n // layers (in the case of simulcast) will result in the Promise returned\n // by RTCRtpSender.setParameters() being rejected. With this, audio encoding\n // is prioritized the most.\n params.encodings[0].priority = 'high';\n } else if (pcv2._isChromeScreenShareTrack(sender.track)) {\n // NOTE(mmalavalli): Screen share encodings are prioritized more than those\n // of the camera.\n params.encodings[0].priority = 'medium';\n }\n if (pcv2._enableDscp) {\n // NOTE(mmalavalli): \"networkPriority\" is a per-sender property and not\n // a per-encoding-layer property. So, we set the value only on the first\n // encoding layer. Any attempt to set the value on subsequent encoding\n // layers (in the case of simulcast) will result in the Promise returned\n // by RTCRtpSender.setParameters() being rejected.\n params.encodings[0].networkPriority = 'high';\n }\n }\n\n // when a sender is reused, delete any active encodings set by server.\n const trackReplaced = pcv2._rtpNewSenders.has(sender);\n pcv2._maybeUpdateEncodings(sender.track, params.encodings, trackReplaced);\n pcv2._rtpNewSenders.delete(sender);\n\n const promise = sender.setParameters(params).catch(error => {\n pcv2._log.warn(`Error while setting encodings parameters for ${sender.track.kind} Track ${sender.track.id}: ${error.message || error.name}`);\n });\n promises.push(promise);\n });\n return Promise.all(promises);\n}\n\n/**\n * Remove maxBitrate from the RTCRtpSendParameters' encodings.\n * @param {RTCRtpSendParameters} params\n * @returns {void}\n */\nfunction removeMaxBitrate(params) {\n if (Array.isArray(params.encodings)) {\n params.encodings.forEach(encoding => delete encoding.maxBitrate);\n }\n}\n\n/**\n * Set the given maxBitrate in the RTCRtpSendParameters' encodings.\n * @param {RTCRtpSendParameters} params\n * @param {number} maxBitrate\n * @returns {void}\n */\nfunction setMaxBitrate(params, maxBitrate) {\n if (isFirefox) {\n params.encodings = [{ maxBitrate }];\n } else {\n params.encodings.forEach(encoding => {\n encoding.maxBitrate = maxBitrate;\n });\n }\n}\nmodule.exports = PeerConnectionV2;\n", "'use strict';\n\nconst { guessBrowser } = require('../../webrtc/util');\nconst PeerConnectionV2 = require('./peerconnection');\nconst MediaTrackSender = require('../../media/track/sender');\nconst QueueingEventEmitter = require('../../queueingeventemitter');\nconst util = require('../../util');\nconst { MediaConnectionError } = require('../../util/twilio-video-errors');\n\nconst isFirefox = guessBrowser() === 'firefox';\n\n/**\n * {@link PeerConnectionManager} manages multiple {@link PeerConnectionV2}s.\n * @extends QueueingEventEmitter\n * @emits PeerConnectionManager#candidates\n * @emits PeerConnectionManager#connectionStateChanged\n * @emits PeerConnectionManager#description\n * @emits PeerConnectionManager#iceConnectionStateChanged\n * @emits PeerConnectionManager#trackAdded\n */\nclass PeerConnectionManager extends QueueingEventEmitter {\n /**\n * Construct {@link PeerConnectionManager}.\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} options\n */\n constructor(encodingParameters, preferredCodecs, options) {\n super();\n\n options = Object.assign({\n audioContextFactory: isFirefox\n ? require('../../webaudio/audiocontext')\n : null,\n PeerConnectionV2\n }, options);\n\n const audioContext = options.audioContextFactory\n ? options.audioContextFactory.getOrCreate(this)\n : null;\n\n // NOTE(mroberts): If we're using an AudioContext, we don't need to specify\n // `offerToReceiveAudio` in RTCOfferOptions.\n const offerOptions = audioContext\n ? { offerToReceiveVideo: true }\n : { offerToReceiveAudio: true, offerToReceiveVideo: true };\n\n Object.defineProperties(this, {\n _audioContextFactory: {\n value: options.audioContextFactory\n },\n _closedPeerConnectionIds: {\n value: new Set()\n },\n _configuration: {\n writable: true,\n value: null\n },\n _configurationDeferred: {\n writable: true,\n value: util.defer()\n },\n _connectionState: {\n value: 'new',\n writable: true\n },\n _dummyAudioTrackSender: {\n value: audioContext\n ? new MediaTrackSender(createDummyAudioMediaStreamTrack(audioContext))\n : null\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _iceConnectionState: {\n writable: true,\n value: 'new'\n },\n _dataTrackSenders: {\n writable: true,\n value: new Set()\n },\n _lastConnectionState: {\n value: 'new',\n writable: true\n },\n _lastIceConnectionState: {\n writable: true,\n value: 'new'\n },\n _mediaTrackSenders: {\n writable: true,\n value: new Set()\n },\n _offerOptions: {\n value: offerOptions\n },\n _peerConnections: {\n value: new Map()\n },\n _preferredCodecs: {\n value: preferredCodecs\n },\n _sessionTimeout: {\n value: null,\n writable: true\n },\n _PeerConnectionV2: {\n value: options.PeerConnectionV2\n }\n });\n }\n\n setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast) {\n this._peerConnections.forEach(pc => pc.setEffectiveAdaptiveSimulcast(effectiveAdaptiveSimulcast));\n this._preferredCodecs.video.forEach(cs => {\n if ('adaptiveSimulcast' in cs) {\n cs.adaptiveSimulcast = effectiveAdaptiveSimulcast;\n }\n });\n }\n\n /**\n * A summarized RTCPeerConnectionState across all the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this._connectionState;\n }\n\n /**\n * A summarized RTCIceConnectionState across all the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return this._iceConnectionState;\n }\n\n /**\n * Close the {@link PeerConnectionV2}s which are no longer relevant.\n * @param {Array} peerConnectionStates\n * @returns {this}\n */\n _closeAbsentPeerConnections(peerConnectionStates) {\n const peerConnectionIds = new Set(peerConnectionStates.map(peerConnectionState => peerConnectionState.id));\n this._peerConnections.forEach(peerConnection => {\n if (!peerConnectionIds.has(peerConnection.id)) {\n peerConnection._close();\n }\n });\n return this;\n }\n\n /**\n * Get the {@link PeerConnectionManager}'s configuration.\n * @private\n * @returns {Promise}\n */\n _getConfiguration() {\n return this._configurationDeferred.promise;\n }\n\n /**\n * Get or create a {@link PeerConnectionV2}.\n * @private\n * @param {string} id\n * @param {object} [configuration]\n * @returns {PeerConnectionV2}\n */\n _getOrCreate(id, configuration) {\n const self = this;\n let peerConnection = this._peerConnections.get(id);\n if (!peerConnection) {\n const PeerConnectionV2 = this._PeerConnectionV2;\n\n const options = Object.assign({\n dummyAudioMediaStreamTrack: this._dummyAudioTrackSender\n ? this._dummyAudioTrackSender.track\n : null,\n offerOptions: this._offerOptions\n }, this._sessionTimeout ? {\n sessionTimeout: this._sessionTimeout\n } : {}, configuration);\n\n try {\n peerConnection = new PeerConnectionV2(id, this._encodingParameters, this._preferredCodecs, options);\n } catch (e) {\n throw new MediaConnectionError();\n }\n\n this._peerConnections.set(peerConnection.id, peerConnection);\n peerConnection.on('candidates', this.queue.bind(this, 'candidates'));\n peerConnection.on('description', this.queue.bind(this, 'description'));\n peerConnection.on('trackAdded', this.queue.bind(this, 'trackAdded'));\n peerConnection.on('stateChanged', function stateChanged(state) {\n if (state === 'closed') {\n peerConnection.removeListener('stateChanged', stateChanged);\n self._dataTrackSenders.forEach(sender => peerConnection.removeDataTrackSender(sender));\n self._mediaTrackSenders.forEach(sender => peerConnection.removeMediaTrackSender(sender));\n self._peerConnections.delete(peerConnection.id);\n self._closedPeerConnectionIds.add(peerConnection.id);\n updateConnectionState(self);\n updateIceConnectionState(self);\n }\n });\n peerConnection.on('connectionStateChanged', () => updateConnectionState(this));\n peerConnection.on('iceConnectionStateChanged', () => updateIceConnectionState(this));\n\n this._dataTrackSenders.forEach(peerConnection.addDataTrackSender, peerConnection);\n this._mediaTrackSenders.forEach(peerConnection.addMediaTrackSender, peerConnection);\n\n updateIceConnectionState(this);\n }\n return peerConnection;\n }\n\n /**\n * Close all the {@link PeerConnectionV2}s in this {@link PeerConnectionManager}.\n * @returns {this}\n */\n close() {\n this._peerConnections.forEach(peerConnection => {\n peerConnection.close();\n });\n if (this._dummyAudioTrackSender) {\n this._dummyAudioTrackSender.stop();\n }\n if (this._audioContextFactory) {\n this._audioContextFactory.release(this);\n }\n updateIceConnectionState(this);\n return this;\n }\n\n /**\n * Create a new {@link PeerConnectionV2} on this {@link PeerConnectionManager}.\n * Then, create a new offer with the newly-created {@link PeerConnectionV2}.\n * @return {Promise}\n */\n createAndOffer() {\n return this._getConfiguration().then(configuration => {\n let id;\n do {\n id = util.makeUUID();\n } while (this._peerConnections.has(id));\n\n return this._getOrCreate(id, configuration);\n }).then(peerConnection => {\n return peerConnection.offer();\n }).then(() => {\n return this;\n });\n }\n\n /**\n * Get the {@link DataTrackReceiver}s and {@link MediaTrackReceiver}s of all\n * the {@link PeerConnectionV2}s.\n * @returns {Array} trackReceivers\n */\n getTrackReceivers() {\n return util.flatMap(this._peerConnections, peerConnection => peerConnection.getTrackReceivers());\n }\n\n /**\n * Get the states of all {@link PeerConnectionV2}s.\n * @returns {Array}\n */\n getStates() {\n const peerConnectionStates = [];\n this._peerConnections.forEach(peerConnection => {\n const peerConnectionState = peerConnection.getState();\n if (peerConnectionState) {\n peerConnectionStates.push(peerConnectionState);\n }\n });\n return peerConnectionStates;\n }\n\n /**\n * Set the {@link PeerConnectionManager}'s configuration.\n * @param {object} configuration\n * @returns {this}\n */\n setConfiguration(configuration) {\n if (this._configuration) {\n this._configurationDeferred = util.defer();\n this._peerConnections.forEach(peerConnection => {\n peerConnection.setConfiguration(configuration);\n });\n }\n this._configuration = configuration;\n this._configurationDeferred.resolve(configuration);\n return this;\n }\n\n /**\n * Set the ICE reconnect timeout period for all {@link PeerConnectionV2}s.\n * @param {number} period - Period in milliseconds.\n * @returns {this}\n */\n setIceReconnectTimeout(period) {\n if (this._sessionTimeout === null) {\n this._peerConnections.forEach(peerConnection => {\n peerConnection.setIceReconnectTimeout(period);\n });\n this._sessionTimeout = period;\n }\n return this;\n }\n\n /**\n * Set the {@link DataTrackSender}s and {@link MediaTrackSender}s on the\n * {@link PeerConnectionManager}'s underlying {@link PeerConnectionV2}s.\n * @param {Array} trackSenders\n * @returns {this}\n */\n setTrackSenders(trackSenders) {\n const dataTrackSenders = new Set(trackSenders.filter(trackSender => trackSender.kind === 'data'));\n\n const mediaTrackSenders = new Set(trackSenders\n .filter(trackSender => trackSender && (trackSender.kind === 'audio' || trackSender.kind === 'video')));\n\n const changes = getTrackSenderChanges(this, dataTrackSenders, mediaTrackSenders);\n this._dataTrackSenders = dataTrackSenders;\n this._mediaTrackSenders = mediaTrackSenders;\n applyTrackSenderChanges(this, changes);\n\n return this;\n }\n\n /**\n * Update the {@link PeerConnectionManager}.\n * @param {Array} peerConnectionStates\n * @param {boolean} [synced=false]\n * @returns {Promise}\n */\n update(peerConnectionStates, synced = false) {\n if (synced) {\n this._closeAbsentPeerConnections(peerConnectionStates);\n }\n return this._getConfiguration().then(configuration => {\n return Promise.all(peerConnectionStates.map(peerConnectionState => {\n if (this._closedPeerConnectionIds.has(peerConnectionState.id)) {\n return null;\n }\n const peerConnection = this._getOrCreate(peerConnectionState.id, configuration);\n return peerConnection.update(peerConnectionState);\n }));\n }).then(() => {\n return this;\n });\n }\n\n /**\n * Get the {@link PeerConnectionManager}'s media statistics.\n * @returns {Promise.>}\n */\n getStats() {\n const peerConnections = Array.from(this._peerConnections.values());\n return Promise.all(peerConnections.map(peerConnection => peerConnection.getStats().then(response => [\n peerConnection.id,\n response\n ]))).then(responses => new Map(responses));\n }\n}\n\n/**\n * Create a dummy audio MediaStreamTrack with the given AudioContext.\n * @private\n * @param {AudioContext} audioContext\n * @return {MediaStreamTrack}\n */\nfunction createDummyAudioMediaStreamTrack(audioContext) {\n const mediaStreamDestination = audioContext.createMediaStreamDestination();\n return mediaStreamDestination.stream.getAudioTracks()[0];\n}\n\n/**\n * @event {PeerConnectionManager#candidates}\n * @param {object} candidates\n */\n\n/**\n * @event {PeerConnectionManager#connectionStateChanged}\n */\n\n/**\n * @event {PeerConnectionManager#description}\n * @param {object} description\n */\n\n/**\n * @event {PeerConnectionManager#iceConnectionStateChanged}\n */\n\n/**\n * @event {PeerConnectionManager#trackAdded}\n * @param {MediaStreamTrack|DataTrackReceiver} mediaStreamTrackOrDataTrackReceiver\n */\n\n/**\n * Apply {@link TrackSenderChanges}.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {TrackSenderChanges} changes\n * @returns {void}\n */\nfunction applyTrackSenderChanges(peerConnectionManager, changes) {\n if (changes.data.add.size\n || changes.data.remove.size\n || changes.media.add.size\n || changes.media.remove.size) {\n peerConnectionManager._peerConnections.forEach(peerConnection => {\n changes.data.remove.forEach(peerConnection.removeDataTrackSender, peerConnection);\n changes.media.remove.forEach(peerConnection.removeMediaTrackSender, peerConnection);\n changes.data.add.forEach(peerConnection.addDataTrackSender, peerConnection);\n changes.media.add.forEach(peerConnection.addMediaTrackSender, peerConnection);\n if (changes.media.add.size\n || changes.media.remove.size\n || (changes.data.add.size && !peerConnection.isApplicationSectionNegotiated)) {\n peerConnection.offer();\n }\n });\n }\n}\n\n/**\n * @interface DataTrackSenderChanges\n * @property {Set} add\n * @property {Set} remove\n */\n\n/**\n * Get the {@Link DataTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} dataTrackSenders\n * @returns {DataTrackSenderChanges} changes\n */\nfunction getDataTrackSenderChanges(peerConnectionManager, dataTrackSenders) {\n const dataTrackSendersToAdd = util.difference(dataTrackSenders, peerConnectionManager._dataTrackSenders);\n const dataTrackSendersToRemove = util.difference(peerConnectionManager._dataTrackSenders, dataTrackSenders);\n return {\n add: dataTrackSendersToAdd,\n remove: dataTrackSendersToRemove\n };\n}\n\n/**\n * @interface TrackSenderChanges\n * @property {DataTrackSenderChanges} data\n * @property {MediaTrackSenderChanges} media\n */\n\n/**\n * Get {@link DataTrackSender} and {@link MediaTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} dataTrackSenders\n * @param {Array} mediaTrackSenders\n * @returns {TrackSenderChanges} changes\n */\nfunction getTrackSenderChanges(peerConnectionManager, dataTrackSenders, mediaTrackSenders) {\n return {\n data: getDataTrackSenderChanges(peerConnectionManager, dataTrackSenders),\n media: getMediaTrackSenderChanges(peerConnectionManager, mediaTrackSenders)\n };\n}\n\n/**\n * @interface MediaTrackSenderChanges\n * @property {Set} add\n * @property {Set} remove\n */\n\n/**\n * Get the {@link MediaTrackSender} changes.\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {Array} mediaTrackSenders\n * @returns {MediaTrackSenderChanges} changes\n */\nfunction getMediaTrackSenderChanges(peerConnectionManager, mediaTrackSenders) {\n const mediaTrackSendersToAdd = util.difference(mediaTrackSenders, peerConnectionManager._mediaTrackSenders);\n const mediaTrackSendersToRemove = util.difference(peerConnectionManager._mediaTrackSenders, mediaTrackSenders);\n return {\n add: mediaTrackSendersToAdd,\n remove: mediaTrackSendersToRemove\n };\n}\n\n/**\n * This object maps RTCIceConnectionState and RTCPeerConnectionState values to a \"rank\".\n */\nconst toRank = {\n new: 0,\n checking: 1,\n connecting: 2,\n connected: 3,\n completed: 4,\n disconnected: -1,\n failed: -2,\n closed: -3\n};\n\n/**\n * This object maps \"rank\" back to RTCIceConnectionState or RTCPeerConnectionState values.\n */\nlet fromRank;\n\n/**\n * `Object.keys` is not supported in older browsers, so we can't just\n * synchronously call it in this module; we need to defer invoking it until we\n * know we're in a modern environment (i.e., anything that supports WebRTC).\n * @returns {object} fromRank\n */\nfunction createFromRank() {\n return Object.keys(toRank).reduce((fromRank, state) => {\n return Object.assign(fromRank, { [toRank[state]]: state });\n }, {});\n}\n\n/**\n * Summarize RTCIceConnectionStates or RTCPeerConnectionStates.\n * @param {Array|Array} states\n * @returns {RTCIceConnectionState|RTCPeerConnectionState} summary\n */\nfunction summarizeIceOrPeerConnectionStates(states) {\n if (!states.length) {\n return 'new';\n }\n fromRank = fromRank || createFromRank();\n return states.reduce((state1, state2) => {\n return fromRank[Math.max(toRank[state1], toRank[state2])];\n });\n}\n\n/**\n * Update the {@link PeerConnectionManager}'s `iceConnectionState`, and emit an\n * \"iceConnectionStateChanged\" event, if necessary.\n * @param {PeerConnectionManager} pcm\n * @returns {void}\n */\nfunction updateIceConnectionState(pcm) {\n pcm._lastIceConnectionState = pcm.iceConnectionState;\n pcm._iceConnectionState = summarizeIceOrPeerConnectionStates(\n [...pcm._peerConnections.values()].map(pcv2 => pcv2.iceConnectionState));\n if (pcm.iceConnectionState !== pcm._lastIceConnectionState) {\n pcm.emit('iceConnectionStateChanged');\n }\n}\n\n/**\n * Update the {@link PeerConnectionManager}'s `connectionState`, and emit a\n * \"connectionStateChanged\" event, if necessary.\n * @param {PeerConnectionManager} pcm\n * @returns {void}\n */\nfunction updateConnectionState(pcm) {\n pcm._lastConnectionState = pcm.connectionState;\n pcm._connectionState = summarizeIceOrPeerConnectionStates(\n [...pcm._peerConnections.values()].map(pcv2 => pcv2.connectionState));\n if (pcm.connectionState !== pcm._lastConnectionState) {\n pcm.emit('connectionStateChanged');\n }\n}\n\nmodule.exports = PeerConnectionManager;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst EventEmitter = require('events');\n\nlet nInstances = 0;\nclass MediaSignaling extends EventEmitter {\n /**\n * Construct a {@link MediaSignaling}.\n * @param {Promise} getReceive\n * @param {string} channel\n */\n constructor(getReceiver, channel, options) {\n super();\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n channel: {\n value: channel,\n },\n _log: {\n value: options.log.createLog('default', this)\n },\n _getReceiver: {\n value: getReceiver\n },\n _receiverPromise: {\n value: null,\n writable: true,\n },\n _transport: {\n value: null,\n writable: true\n }\n });\n }\n\n get isSetup() {\n return !!this._receiverPromise;\n }\n\n toString() {\n return `[MediaSignaling #${this._instanceId}:${this.channel}]`;\n }\n\n setup(id) {\n this._teardown();\n this._log.info('setting up msp transport for id:', id);\n const receiverPromise = this._getReceiver(id).then(receiver => {\n if (receiver.kind !== 'data') {\n this._log.error('Expected a DataTrackReceiver');\n } if (this._receiverPromise !== receiverPromise) {\n return;\n }\n\n try {\n this._transport = receiver.toDataTransport();\n this.emit('ready', this._transport);\n } catch (ex) {\n this._log.error(`Failed to toDataTransport: ${ex.message}`);\n }\n receiver.once('close', () => this._teardown());\n });\n this._receiverPromise = receiverPromise;\n }\n\n _teardown() {\n if (this._transport) {\n this._log.info('Tearing down');\n this._transport = null;\n this._receiverPromise = null;\n this.emit('teardown');\n }\n }\n}\n\nmodule.exports = MediaSignaling;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\n/**\n * @property {?Track.SID} loudestParticipantSid\n * @emits DominantSpeakerSignaling#updated\n */\nclass DominantSpeakerSignaling extends MediaSignaling {\n /**\n * Construct an {@link DominantSpeakerSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'active_speaker', options);\n\n Object.defineProperties(this, {\n _loudestParticipantSid: {\n value: null,\n writable: true\n },\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n switch (message.type) {\n case 'active_speaker':\n this._setLoudestParticipantSid(message.participant);\n break;\n default:\n break;\n }\n });\n });\n }\n\n /**\n * Get the loudest {@link Track.SID}, if known.\n * @returns {?Track.SID}\n */\n get loudestParticipantSid() {\n return this._loudestParticipantSid;\n }\n\n /**\n * @private\n * @param {Track.SID} loudestParticipantSid\n * @returns {void}\n */\n _setLoudestParticipantSid(loudestParticipantSid) {\n if (this.loudestParticipantSid === loudestParticipantSid) {\n return;\n }\n this._loudestParticipantSid = loudestParticipantSid;\n this.emit('updated');\n }\n}\n\n/**\n * @event DominantSpeakerSignaling#updated\n */\n\nmodule.exports = DominantSpeakerSignaling;\n", "'use strict';\n\n/**\n * @property {number} [availableSend] - bps (undefined in Firefox)\n * @property {number} recv - bps\n * @property {number} [rtt] - s (undefined in Firefox)\n * @property {number} send - bps\n */\nclass IceReport {\n /**\n * Construct an {@link IceReport}.\n * @param {number} send - bps\n * @param {number} recv - bps\n * @param {number} [rtt] - s\n * @param {number} [availableSend] - bps\n */\n constructor(send, recv, availableSend, rtt) {\n Object.defineProperties(this, {\n availableSend: {\n enumerable: true,\n value: availableSend\n },\n recv: {\n enumerable: true,\n value: recv\n },\n rtt: {\n enumerable: true,\n value: rtt\n },\n send: {\n enumerable: true,\n value: send\n }\n });\n }\n\n /**\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @returns {IceReport}\n */\n static of(olderStats, newerStats) {\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesSent = newerStats.bytesSent - olderStats.bytesSent;\n const deltaBytesReceived = newerStats.bytesReceived - olderStats.bytesReceived;\n const send = secondsElapsed > 0\n ? (deltaBytesSent / secondsElapsed) * 8\n : 0;\n const recv = secondsElapsed > 0\n ? (deltaBytesReceived / secondsElapsed) * 8\n : 0;\n const { availableOutgoingBitrate: availableSend, currentRoundTripTime: rtt } = newerStats;\n return new IceReport(send, recv, availableSend, rtt);\n }\n}\n\nmodule.exports = IceReport;\n", "'use strict';\n\nconst IceReport = require('./icereport');\n\n/**\n * @property {IceReport} lastReport\n * @property {?RTCStats} lastStats\n */\nclass IceReportFactory {\n /**\n * Construct an {@link IceReportFactory}.\n */\n constructor() {\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: new IceReport(0, 0),\n writable: true\n },\n lastStats: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create an {@link IceReport}.\n * @param {RTCStats} newerStats;\n * @returns {IceReport}\n */\n next(newerStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n if (olderStats) {\n const report = olderStats.id === newerStats.id\n ? IceReport.of(olderStats, newerStats)\n : new IceReport(0, 0);\n this.lastReport = report;\n }\n return this.lastReport;\n }\n}\n\nmodule.exports = IceReportFactory;\n", "/* eslint no-undefined:0 */\n'use strict';\n\n/**\n * @param {Array} xs\n * @returns {number|undefined}\n */\nfunction average(xs) {\n xs = xs.filter(x => typeof x === 'number');\n return xs.length < 1 ? undefined : xs.reduce((y, x) => x + y) / xs.length;\n}\n\nmodule.exports = average;\n", "'use strict';\n\n/**\n * @property {StatsId} id\n * @property {TrackId} trackId\n * @property {number} bitrate - bps\n */\nclass SenderOrReceiverReport {\n /**\n * Construct a {@link SenderOrReceiverReport}.\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n */\n constructor(id, trackId, bitrate) {\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id\n },\n trackId: {\n enumerable: true,\n value: trackId\n },\n bitrate: {\n enumerable: true,\n value: bitrate\n }\n });\n }\n}\n\nmodule.exports = SenderOrReceiverReport;\n", "'use strict';\n\n/**\n * @param {Array} xs\n * @returns {number}\n */\nfunction sum(xs) {\n return xs.reduce((y, x) => typeof x === 'number' ? x + y : y, 0);\n}\n\nmodule.exports = sum;\n", "'use strict';\n\nconst average = require('./average');\nconst SenderOrReceiverReport = require('./senderorreceiverreport');\nconst sum = require('./sum');\n\n/**\n * @interface ReceiverSummary\n * @property {number} bitrate\n * @property {number} fractionLost - 0\u20131\n * @property {number} [jitter] - s (undefined for video tracks in Chrome)\n */\n\n/**\n * @extends SenderOrReceiverReport\n * @property {number} deltaPacketsLost\n * @property {number} deltaPacketsReceived\n * @property {number} [fractionLost] - 0\u20131 (undefined in Firefox)\n * @property {number} [jitter] - s (undefined for video tracks in Chrome)\n * @property {number} phonyPacketsLost - 0\u20131\n */\nclass ReceiverReport extends SenderOrReceiverReport {\n /**\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n * @param {number} deltaPacketsLost\n * @param {number} deltaPacketsReceived\n * @param {number} [fractionLost] - 0\u20131 (undefined in Firefox)\n * @param {number} [jitter] - s (undefined for video tracks in Chrome)\n */\n constructor(id, trackId, bitrate, deltaPacketsLost, deltaPacketsReceived, fractionLost, jitter) {\n super(id, trackId, bitrate);\n const phonyFractionLost = deltaPacketsReceived > 0\n ? deltaPacketsLost / deltaPacketsReceived\n : 0;\n Object.defineProperties(this, {\n deltaPacketsLost: {\n enumerable: true,\n value: deltaPacketsLost\n },\n deltaPacketsReceived: {\n enumerable: true,\n value: deltaPacketsReceived\n },\n fractionLost: {\n enumerable: true,\n value: fractionLost\n },\n jitter: {\n enumerable: true,\n value: jitter\n },\n phonyFractionLost: {\n enumerable: true,\n value: phonyFractionLost\n }\n });\n }\n\n /**\n * Create a {@link ReceiverReport}.\n * @param {string} trackId\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @returns {ReceiverReport}\n */\n static of(trackId, olderStats, newerStats) {\n if (olderStats.id !== newerStats.id) {\n throw new Error('RTCStats IDs must match');\n }\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesReceived = newerStats.bytesReceived - olderStats.bytesReceived;\n const bitrate = secondsElapsed > 0\n ? (deltaBytesReceived / secondsElapsed) * 8\n : 0;\n const deltaPacketsLost = Math.max(newerStats.packetsLost - olderStats.packetsLost, 0);\n const deltaPacketsReceived = newerStats.packetsReceived - olderStats.packetsReceived;\n const { fractionLost, jitter } = newerStats;\n return new ReceiverReport(olderStats.id, trackId, bitrate, deltaPacketsLost, deltaPacketsReceived, fractionLost, jitter);\n }\n\n /**\n * Summarize {@link ReceiverReport}s by summing and averaging their values.\n * @param {Array} reports\n * @returns {ReceiverSummary}\n */\n static summarize(reports) {\n const summaries = reports.map(report => report.summarize());\n const bitrate = sum(summaries.map(summary => summary.bitrate));\n const fractionLost = average(summaries.map(summary => summary.fractionLost));\n const jitter = average(summaries.map(summary => summary.jitter));\n return {\n bitrate,\n fractionLost,\n jitter\n };\n }\n\n /**\n * Summarize the {@link ReceiveReport}.\n * @returns {ReceiverSummary}\n */\n summarize() {\n return {\n bitrate: this.bitrate,\n fractionLost: typeof this.fractionLost === 'number' ? this.fractionLost : this.phonyFractionLost,\n jitter: this.jitter\n };\n }\n}\n\nmodule.exports = ReceiverReport;\n", "/* eslint no-undefined:0 */\n'use strict';\n\nconst average = require('./average');\nconst SenderOrReceiverReport = require('./senderorreceiverreport');\nconst sum = require('./sum');\n\n/**\n * @interface SenderSummary\n * @property {number} bitrate\n * @property {number} [rtt] - s (undefined in Chrome)\n */\n\n/**\n * @extends SenderOrReceiverReport\n * @property {number} [rtt] - s (undefined in Chrome)\n */\nclass SenderReport extends SenderOrReceiverReport {\n /**\n * Construct a {@link SenderReport}.\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {number} bitrate - bps\n * @param {number} [rtt] - s\n */\n constructor(id, trackId, bitrate, rtt) {\n super(id, trackId, bitrate);\n Object.defineProperties(this, {\n rtt: {\n enumerable: true,\n value: rtt\n }\n });\n }\n\n /**\n * Create a {@link SenderReport}.\n * @param {string} trackId\n * @param {RTCStats} olderStats\n * @param {RTCStats} newerStats\n * @param {RTCRemoteInboundRtpStreamStats} [newerRemoteStats]\n * @returns {SenderReport}\n */\n static of(trackId, olderStats, newerStats, newerRemoteStats) {\n if (olderStats.id !== newerStats.id) {\n throw new Error('RTCStats IDs must match');\n }\n const secondsElapsed = (newerStats.timestamp - olderStats.timestamp) / 1000;\n const deltaBytesSent = newerStats.bytesSent - olderStats.bytesSent;\n const bitrate = secondsElapsed > 0\n ? (deltaBytesSent / secondsElapsed) * 8\n : 0;\n const rtt = newerRemoteStats && typeof newerRemoteStats.roundTripTime === 'number'\n ? newerRemoteStats.roundTripTime / 1000\n : undefined;\n return new SenderReport(olderStats.id, trackId, bitrate, rtt);\n }\n\n /**\n * Summarize {@link SenderReport}s by summing and averaging their values.\n * @param {Array} reports\n * @returns {SenderSummary}\n */\n static summarize(reports) {\n const bitrate = sum(reports.map(report => report.bitrate));\n const rtt = average(reports.map(report => report.rtt));\n return {\n bitrate,\n rtt\n };\n }\n}\n\nmodule.exports = SenderReport;\n", "'use strict';\n\nconst ReceiverReport = require('./receiverreport');\nconst SenderReport = require('./senderreport');\n\n/**\n * @interface SenderAndReceiverReports\n * @property {Array} send\n * @property {Array} recv\n */\n\n/**\n * @interface SenderAndReceiverSummary\n * @property {SenderSummary} send\n * @property {ReceiverSummary} recv\n */\n\n/**\n * @interface PeerConnectionSummary\n * @property {IceReport} ice\n * @property {SenderSummary} send\n * @property {ReceiverSummary} recv\n * @property {SenderAndReceiverSummary} audio\n * @property {SenderAndReceiverSummary} video\n */\n\n/**\n * @property {IceReport} ice\n * @roperty {SenderAndReceiverReports} audio\n * @roperty {SenderAndReceiverReports} video\n */\nclass PeerConnectionReport {\n /**\n * Construct a {@link PeerConnectionReport}.\n * @param {IceReport} ice\n * @param {SenderAndReceiverReports} audio\n * @param {SenderAndReceiverReports} video\n */\n constructor(ice, audio, video) {\n Object.defineProperties(this, {\n ice: {\n enumerable: true,\n value: ice\n },\n audio: {\n enumerable: true,\n value: audio\n },\n video: {\n enumerable: true,\n value: video\n }\n });\n }\n\n /**\n * Summarize the {@link PeerConnectionReport} by summarizing its\n * {@link SenderReport}s and {@link ReceiverReport}s.\n * @returns {PeerConnectionSummary}\n */\n summarize() {\n const senderReports = this.audio.send.concat(this.video.send);\n const send = SenderReport.summarize(senderReports);\n\n const receiverReports = this.audio.recv.concat(this.video.recv);\n const recv = ReceiverReport.summarize(receiverReports);\n\n return {\n ice: this.ice,\n send,\n recv,\n audio: {\n send: SenderReport.summarize(this.audio.send),\n recv: ReceiverReport.summarize(this.audio.recv)\n },\n video: {\n send: SenderReport.summarize(this.video.send),\n recv: ReceiverReport.summarize(this.video.recv)\n }\n };\n }\n}\n\nmodule.exports = PeerConnectionReport;\n", "'use strict';\n\n/**\n * @property {StatsId} id\n * @property {TrackId} trackId\n * @property {RTCStats} lastStats\n */\nclass SenderOrReceiverReportFactory {\n /**\n * @param {StatsId} id\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(id, trackId, initialStats) {\n Object.defineProperties(this, {\n id: {\n enumerable: true,\n value: id,\n writable: true\n },\n trackId: {\n enumerable: true,\n value: trackId,\n writable: true\n },\n lastStats: {\n enumerable: true,\n value: initialStats,\n writable: true\n }\n });\n }\n}\n\nmodule.exports = SenderOrReceiverReportFactory;\n", "'use strict';\n\nconst ReceiverReport = require('./receiverreport');\nconst SenderOrReceiverReportFactory = require('./senderorreceiverreportfactory');\n\n/**\n * @extends SenderOrReceiverReportFactory\n * @param {?ReceiverReport} lastReport\n */\nclass ReceiverReportFactory extends SenderOrReceiverReportFactory {\n /**\n * Construct a {@link ReceiverReportFactory}.\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(trackId, initialStats) {\n super(initialStats.id, trackId, initialStats);\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create a {@link ReceiverReport}.\n * @param {TrackId} trackId\n * @param {RTCStats} newerStats\n * @returns {ReceiverReport}\n */\n next(trackId, newerStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n this.trackId = trackId;\n const report = ReceiverReport.of(trackId, olderStats, newerStats);\n this.lastReport = report;\n return report;\n }\n}\n\nmodule.exports = ReceiverReportFactory;\n", "'use strict';\n\nconst SenderOrReceiverReportFactory = require('./senderorreceiverreportfactory');\nconst SenderReport = require('./senderreport');\n\n/**\n * @extends {SenderOrReceiverReportFactory}\n * @property {?SenderReport} lastReport\n */\nclass SenderReportFactory extends SenderOrReceiverReportFactory {\n /**\n * Construct a {@link SenderReportFactory}.\n * @param {TrackId} trackId\n * @param {RTCStats} initialStats\n */\n constructor(trackId, initialStats) {\n super(initialStats.id, trackId, initialStats);\n Object.defineProperties(this, {\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * @param {TrackId} trackId\n * @param {RTCStats} newerStats\n * @param {RTCRemoteInboundRtpStreamStats} [newerRemoteStats]\n * @returns {SenderReport}\n */\n next(trackId, newerStats, newerRemoteStats) {\n const olderStats = this.lastStats;\n this.lastStats = newerStats;\n this.trackId = trackId;\n const report = SenderReport.of(trackId, olderStats, newerStats, newerRemoteStats);\n this.lastReport = report;\n return report;\n }\n}\n\nmodule.exports = SenderReportFactory;\n", "'use strict';\n\nconst { guessBrowser } = require('../webrtc/util');\n\nconst IceReportFactory = require('./icereportfactory');\nconst PeerConnectionReport = require('./peerconnectionreport');\nconst ReceiverReportFactory = require('./receiverreportfactory');\nconst SenderReportFactory = require('./senderreportfactory');\n\n/**\n * @typedef {string} TrackId\n */\n\n/**\n * @typedef {string} StatsId\n */\n\n/**\n * @interface SenderReportFactoriesByMediaType\n * @property {Map} audio\n * @property {Map} video\n */\n\n/**\n * @interface ReceiverReportFactoriesByMediaType\n * @property {Map} audio\n * @property {Map} video\n */\n\n/**\n * @interface SenderAndReceiverReportFactories\n * @property {Map} send\n * @property {Map} recv\n */\n\n/**\n * @interface {StatsIdsByMediaType}\n * @property {Set} audio\n * @property {Set} video\n */\n\n/**\n * @property {RTCPeerConnection} pc\n * @property {IceReportFactory} iceReportFactory\n * @property {SenderAndReceiverReportFactories} audio\n * @property {SenderAndReceiverReportFactories} video\n * @property {?PeerConnectionReport} lastReport\n */\nclass PeerConnectionReportFactory {\n /**\n * Construct a {@link PeerConnectionReportFactory}.\n * @param {RTCPeerConnection} pc\n */\n constructor(pc) {\n Object.defineProperties(this, {\n pc: {\n enumerable: true,\n value: pc\n },\n ice: {\n enumerable: true,\n value: new IceReportFactory()\n },\n audio: {\n enumerable: true,\n value: {\n send: new Map(),\n recv: new Map()\n }\n },\n video: {\n enumerable: true,\n value: {\n send: new Map(),\n recv: new Map()\n }\n },\n lastReport: {\n enumerable: true,\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Create a {@link PeerConnectionReport}.\n * @returns {Promise}\n */\n next() {\n const updatePromise = guessBrowser() === 'firefox'\n ? updateFirefox(this)\n : updateChrome(this);\n\n return updatePromise.then(() => {\n const audioSenderReportFactories = [...this.audio.send.values()];\n const videoSenderReportFactories = [...this.video.send.values()];\n const audioReceiverReportFactories = [...this.audio.recv.values()];\n const videoReceiverReportFactories = [...this.video.recv.values()];\n\n const report = new PeerConnectionReport(\n this.ice.lastReport,\n {\n send: audioSenderReportFactories.map(factory => factory.lastReport).filter(report => report),\n recv: audioReceiverReportFactories.map(factory => factory.lastReport).filter(report => report)\n },\n {\n send: videoSenderReportFactories.map(factory => factory.lastReport).filter(report => report),\n recv: videoReceiverReportFactories.map(factory => factory.lastReport).filter(report => report)\n }\n );\n\n this.lastReport = report;\n\n return report;\n });\n }\n}\n\n/**\n * Construct a Map from MediaStreamTrack Ids to RTCStatsReports.\n * @param {Array|Array} sendersOrReceivers - each\n * RTCRtpSender should have a non-null track\n * @returns {Promise>}\n */\nfunction getSenderOrReceiverReports(sendersOrReceivers) {\n return Promise.all(sendersOrReceivers.map(senderOrReceiver => {\n const trackId = senderOrReceiver.track.id;\n return senderOrReceiver.getStats().then(report => {\n // NOTE(mroberts): We have to rewrite Ids due to this bug:\n //\n // https://bugzilla.mozilla.org/show_bug.cgi?id=1463430\n //\n for (const stats of report.values()) {\n if (stats.type === 'inbound-rtp') {\n stats.id = `${trackId}-${stats.id}`;\n }\n }\n return [trackId, report];\n });\n })).then(pairs => new Map(pairs));\n}\n\n/**\n * @param {SenderReportFactory.constructor} SenderReportFactory\n * @param {SenderReportFactoriesByMediaType} sendersByMediaType\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?SenderReportFactory}\n *//**\n * @param {ReceiverReportFactory.constructor} ReceiverReportFactory\n * @param {ReceiverReportFactoriesByMediaType} receiversByMediaType\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?ReceiverReportFactory}\n */\nfunction getOrCreateSenderOrReceiverReportFactory(SenderOrReceiverReportFactory, sendersOrReceiversByMediaType, report, stats, trackId) {\n const sendersOrReceivers = sendersOrReceiversByMediaType[stats.mediaType];\n if (!trackId) {\n const trackStats = report.get(stats.trackId);\n if (trackStats) {\n trackId = trackStats.trackIdentifier;\n }\n }\n if (sendersOrReceivers && trackId) {\n if (sendersOrReceivers.has(stats.id)) {\n return sendersOrReceivers.get(stats.id);\n }\n const senderOrReceiverFactory = new SenderOrReceiverReportFactory(trackId, stats);\n sendersOrReceivers.set(stats.id, senderOrReceiverFactory);\n }\n return null;\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {SenderReportFactoriesByMediaType}\n */\nfunction getSenderReportFactoriesByMediaType(factory) {\n return { audio: factory.audio.send, video: factory.video.send };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {ReceiverReportFactoriesByMediaType}\n */\nfunction getReceiverReportFactoriesByMediaType(factory) {\n return { audio: factory.audio.recv, video: factory.video.recv };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?SenderReportFactory}\n */\nfunction getOrCreateSenderReportFactory(factory, report, stats, trackId) {\n return getOrCreateSenderOrReceiverReportFactory(SenderReportFactory, getSenderReportFactoriesByMediaType(factory), report, stats, trackId);\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {RTCStats} stats\n * @param {TrackId} [trackId]\n * @returns {?ReceiverReportFactory}\n */\nfunction getOrCreateReceiverReportFactory(factory, report, stats, trackId) {\n return getOrCreateSenderOrReceiverReportFactory(ReceiverReportFactory, getReceiverReportFactoriesByMediaType(factory), report, stats, trackId);\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @retuns {StatsIdsByMediaType}\n */\nfunction getSenderReportFactoryIdsByMediaType(factory) {\n return {\n audio: new Set(factory.audio.send.keys()),\n video: new Set(factory.video.send.keys())\n };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @retuns {StatsIdsByMediaType}\n */\nfunction getReceiverReportFactoryIdsByMediaType(factory) {\n return {\n audio: new Set(factory.audio.recv.keys()),\n video: new Set(factory.video.recv.keys())\n };\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {StatsIdsByMediaType} senderReportFactoryIdsToDeleteByMediaType\n * @param {TrackId} [trackId]\n * @returns {void}\n */\nfunction updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType, trackId) {\n for (const stats of report.values()) {\n if (stats.type === 'outbound-rtp' && !stats.isRemote) {\n if (guessBrowser() !== 'firefox' && !stats.trackId) {\n continue;\n }\n const senderReportFactoryIdsToDelete = senderReportFactoryIdsToDeleteByMediaType[stats.mediaType];\n if (senderReportFactoryIdsToDelete) {\n senderReportFactoryIdsToDelete.delete(stats.id);\n }\n const senderReportFactory = getOrCreateSenderReportFactory(factory, report, stats, trackId);\n if (senderReportFactory) {\n const remoteInboundStats = report.get(stats.remoteId);\n senderReportFactory.next(trackId || senderReportFactory.trackId, stats, remoteInboundStats);\n }\n }\n }\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @param {RTCStatsReport} report\n * @param {StatsIdsByMediaType} receiverReportFactoryIdsToDeleteByMediaType\n * @param {TrackId} [trackId]\n * @returns {void}\n */\nfunction updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType, trackId) {\n for (const stats of report.values()) {\n if (stats.type === 'inbound-rtp' && !stats.isRemote) {\n const receiverReportFactoryIdsToDelete = receiverReportFactoryIdsToDeleteByMediaType[stats.mediaType];\n if (receiverReportFactoryIdsToDelete) {\n receiverReportFactoryIdsToDelete.delete(stats.id);\n }\n const receiverReportFactory = getOrCreateReceiverReportFactory(factory, report, stats, trackId);\n if (receiverReportFactory) {\n receiverReportFactory.next(trackId || receiverReportFactory.trackId, stats);\n }\n }\n }\n}\n\n/**\n * @param {SenderReportFactoriesByMediaType|ReceiverReportFactoriesByMediaType} senderOrReceiverReportFactoriesByMediaType\n * @param {StatsIdsByMediaType} senderOrReceiverReportFactoryIdsByMediaType\n * @returns {void}\n */\nfunction deleteSenderOrReceiverReportFactories(senderOrReceiverReportFactoriesByMediaType, senderOrReceiverReportFactoryIdsByMediaType) {\n for (const mediaType in senderOrReceiverReportFactoryIdsByMediaType) {\n const senderOrReceiverReportFactories = senderOrReceiverReportFactoriesByMediaType[mediaType];\n const senderOrReceiverReportFactoryIds = senderOrReceiverReportFactoryIdsByMediaType[mediaType];\n senderOrReceiverReportFactoryIds.forEach(senderOrReceiverReportFactoryId => senderOrReceiverReportFactories.delete(senderOrReceiverReportFactoryId));\n }\n}\n\n/**\n * @param {IceReportFactory} ice\n * @param {RTCStatsReport} report\n * @returns {void}\n */\nfunction updateIceReport(ice, report) {\n let selectedCandidatePair;\n for (const stats of report.values()) {\n if (stats.type === 'transport') {\n selectedCandidatePair = report.get(stats.selectedCandidatePairId);\n }\n }\n if (selectedCandidatePair) {\n ice.next(selectedCandidatePair);\n return;\n }\n for (const stats of report.values()) {\n if (stats.type === 'candidate-pair'\n && stats.nominated\n && ('selected' in stats ? stats.selected : true)) {\n ice.next(stats);\n }\n }\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {Promise}\n */\nfunction updateFirefox(factory) {\n const senders = factory.pc.getTransceivers()\n .filter(transceiver => transceiver.currentDirection && transceiver.currentDirection.match(/send/) && transceiver.sender.track)\n .map(transceiver => transceiver.sender);\n\n const receivers = factory.pc.getTransceivers()\n .filter(transceiver => transceiver.currentDirection && transceiver.currentDirection.match(/recv/))\n .map(transceiver => transceiver.receiver);\n\n return Promise.all([\n getSenderOrReceiverReports(senders),\n getSenderOrReceiverReports(receivers),\n factory.pc.getStats()\n ]).then(([senderReports, receiverReports, pcReport]) => {\n const senderReportFactoriesByMediaType = getSenderReportFactoriesByMediaType(factory);\n const senderReportFactoryIdsToDeleteByMediaType = getSenderReportFactoryIdsByMediaType(factory);\n senderReports.forEach((report, trackId) => updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType, trackId));\n deleteSenderOrReceiverReportFactories(senderReportFactoriesByMediaType, senderReportFactoryIdsToDeleteByMediaType);\n\n const receiverReportFactoriesByMediaType = getReceiverReportFactoriesByMediaType(factory);\n const receiverReportFactoryIdsToDeleteByMediaType = getReceiverReportFactoryIdsByMediaType(factory);\n receiverReports.forEach((report, trackId) => updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType, trackId));\n deleteSenderOrReceiverReportFactories(receiverReportFactoriesByMediaType, receiverReportFactoryIdsToDeleteByMediaType);\n\n updateIceReport(factory.ice, pcReport);\n });\n}\n\n/**\n * @param {PeerConnectionReportFactory} factory\n * @returns {Promise}\n */\nfunction updateChrome(factory) {\n return factory.pc.getStats().then(report => {\n const senderReportFactoriesByMediaType = getSenderReportFactoriesByMediaType(factory);\n const senderReportFactoryIdsToDeleteByMediaType = getSenderReportFactoryIdsByMediaType(factory);\n updateSenderReports(factory, report, senderReportFactoryIdsToDeleteByMediaType);\n deleteSenderOrReceiverReportFactories(senderReportFactoriesByMediaType, senderReportFactoryIdsToDeleteByMediaType);\n\n const receiverReportFactoriesByMediaType = getReceiverReportFactoriesByMediaType(factory);\n const receiverReportFactoryIdsToDeleteByMediaType = getReceiverReportFactoryIdsByMediaType(factory);\n updateReceiverReports(factory, report, receiverReportFactoryIdsToDeleteByMediaType);\n deleteSenderOrReceiverReportFactories(receiverReportFactoriesByMediaType, receiverReportFactoryIdsToDeleteByMediaType);\n\n updateIceReport(factory.ice, report);\n });\n}\n\nmodule.exports = PeerConnectionReportFactory;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst EventEmitter = require('events');\n\nconst PeerConnectionReportFactory = require('../../stats/peerconnectionreportfactory');\n\n/**\n * @emits NetworkQualityMonitor#updated\n */\nclass NetworkQualityMonitor extends EventEmitter {\n /**\n * Construct a {@link NetworkQualityMonitor}.\n * @param {PeerConnectionManager} manager\n * @param {NetworkQualitySignaling} signaling\n */\n constructor(manager, signaling) {\n super();\n Object.defineProperties(this, {\n _factories: {\n value: new WeakMap()\n },\n _manager: {\n value: manager\n },\n _signaling: {\n value: signaling\n }\n });\n signaling.on('updated', () => this.emit('updated'));\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} level - initially null\n */\n get level() {\n return this._signaling.level;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels}, if any.\n * @returns {?NetworkQualityLevels} levels - initially null\n */\n get levels() {\n return this._signaling.levels;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels} of remote participants, if any.\n * @returns {Map} remoteLevels\n */\n get remoteLevels() {\n return this._signaling.remoteLevels;\n }\n\n /**\n * Start monitoring.\n * @returns {void}\n */\n start() {\n this.stop();\n const timeout = setTimeout(() => {\n if (this._timeout !== timeout) {\n return;\n }\n next(this).then(reports => {\n if (this._timeout !== timeout) {\n return;\n }\n if (reports.length) {\n const [report] = reports;\n this._signaling.put(report);\n }\n this.start();\n });\n }, 200);\n this._timeout = timeout;\n }\n\n /**\n * Stop monitoring.\n * @returns {void}\n */\n stop() {\n clearTimeout(this._timeout);\n this._timeout = null;\n }\n}\n\n/**\n * @param {NetworkQualityMonitor}\n * @returns {Promise}\n */\nfunction next(monitor) {\n const pcv2s = monitor._manager._peerConnections\n ? Array.from(monitor._manager._peerConnections.values())\n : [];\n\n const pcs = pcv2s\n .map(pcv2 => pcv2._peerConnection)\n .filter(pc => pc.signalingState !== 'closed');\n\n const factories = pcs.map(pc => {\n if (monitor._factories.has(pc)) {\n return monitor._factories.get(pc);\n }\n const factory = new PeerConnectionReportFactory(pc);\n monitor._factories.set(pc, factory);\n return factory;\n });\n\n const reportsOrNullPromises = factories.map(factory => factory.next().catch(() => null));\n\n return Promise.all(reportsOrNullPromises).then(reportsOrNull => reportsOrNull\n .filter(reportOrNull => reportOrNull)\n .map(report => report.summarize()));\n}\n\n/**\n * The {@link NetworkQualityLevel} changed.\n * @event NetworkQualityMonitor#updated\n */\n\nmodule.exports = NetworkQualityMonitor;\n", "'use strict';\n\nconst { defer } = require('./');\n\n/**\n * An {@link AsyncVar} is an \"asynchronous variable\" which may or may not\n * contain a value of some type T. You can put a value into the {@link AsyncVar}\n * with {@link AsyncVar#put}. Callers can take a value out of the\n * {@link AsyncVar} by queueing up with {@link AsyncVar#take}. N calls to\n * {@link AsyncVar#take} require N calls to {@link AsyncVar#put} to resolve, and\n * they resolve in order.\n */\nclass AsyncVar {\n /**\n * Construct an {@link AsyncVar}.\n */\n constructor() {\n Object.defineProperties(this, {\n _deferreds: {\n value: []\n },\n _hasValue: {\n value: false,\n writable: true\n },\n _value: {\n value: null,\n writable: true\n }\n });\n }\n\n /**\n * Put a value into the {@link AsyncVar}.\n * @param {T} value\n * @returns {this}\n */\n put(value) {\n this._hasValue = true;\n this._value = value;\n const deferred = this._deferreds.shift();\n if (deferred) {\n deferred.resolve(value);\n }\n return this;\n }\n\n /**\n * Take the value out of the {@link AsyncVar}.\n * @returns {Promise}\n */\n take() {\n if (this._hasValue && !this._deferreds.length) {\n this._hasValue = false;\n return Promise.resolve(this._value);\n }\n const deferred = defer();\n this._deferreds.push(deferred);\n return deferred.promise.then(value => {\n this._hasValue = false;\n return value;\n });\n }\n}\n\nmodule.exports = AsyncVar;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nconst AsyncVar = require('../../util/asyncvar');\nconst Timeout = require('../../util/timeout');\n\nconst NETWORK_QUALITY_RESPONSE_TIME_MS = 5000;\n\n/**\n * @interface MediaSignalingTransport\n * @property {function(object): boolean} send\n * @emits MediaSignalingTransport#message\n */\n\n/**\n * The {@link MediaSignalingTransport} received a message.\n * @event MediaSignalingTransport#message\n * @param {object} message\n */\n\n/**\n * @interface LatencyStats\n * @property {number} jitter\n * @property {number} rtt\n * @property {number} level\n */\n\n/**\n * @interface FractionLostStats\n * @property {number} fractionLost\n * @property {number} level\n */\n\n/**\n * @interface BandwidthStats\n * @property {number} actual\n * @property {number} available\n * @property {number} level\n */\n\n/**\n * @interface SendOrRecvStats\n * @property {BandwidthStats} bandwidth\n * @property {FractionLostStats} fractionLost\n * @property {LatencyStats} latency\n */\n\n/**\n * @interface MediaLevels\n * @property {number} send\n * @property {SendOrRecvStats} sendStats\n * @property {number} recv\n * @property {SendOrRecvStats} recvStats\n */\n\n/**\n * @interface NetworkQualityLevels\n * @property {number} level\n * @property {MediaLevels} audio\n * @property {MediaLevels} video\n */\n\n/**\n * @typedef {PeerConnectionSummary} NetworkQualityInputs\n */\n\n/**\n * @classdesc The {@link NetworkQualitySignaling} class allows submitting\n * {@link NetworkQualityInputs} for computing {@link NetworkQualityLevel}. It\n * does so by sending and receiving messages over a\n * {@link MediaSignalingTransport}. The exact transport used depends on the\n * topology of the {@link Room} that {@link NetworkQualitySignaling} is being\n * used within: for P2P Rooms, we re-use the {@link TransportV2}; and for\n * Group Rooms, we use a {@link DataTransport}.\n * @emits NetworkQualitySignaling#updated\n */\nclass NetworkQualitySignaling extends MediaSignaling {\n /**\n * Construct a {@link NetworkQualitySignaling}.\n * @param {Promise} getReceiver\n * @param {NetworkQualityConfigurationImpl} networkQualityConfiguration\n */\n constructor(getReceiver, networkQualityConfiguration, options) {\n super(getReceiver, 'network_quality', options);\n\n Object.defineProperties(this, {\n _level: {\n value: null,\n writable: true\n },\n _levels: {\n value: null,\n writable: true\n },\n _remoteLevels: {\n value: new Map(),\n writable: true\n },\n _networkQualityInputs: {\n value: new AsyncVar()\n },\n _resendTimer: {\n value: new Timeout(() => {\n // and schedule next timer at x1.5 the delay..\n this._resendTimer.setDelay(this._resendTimer.delay * 1.5);\n this._sendNetworkQualityInputs();\n }, NETWORK_QUALITY_RESPONSE_TIME_MS, false),\n },\n _networkQualityReportLevels: {\n get() {\n return {\n reportLevel: networkQualityConfiguration.local,\n remoteReportLevel: networkQualityConfiguration.remote\n };\n }\n }\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'network_quality':\n this._handleNetworkQualityMessage(message);\n break;\n default:\n break;\n }\n });\n });\n\n this._sendNetworkQualityInputs();\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} level - initially null\n */\n get level() {\n return this._level;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels}, if any.\n * @returns {?NetworkQualityLevels} levels - initially null\n */\n get levels() {\n return this._levels;\n }\n\n /**\n * Get the current {@link NetworkQualityLevels} of remote participants, if any.\n * @returns {Map} remoteLevels\n */\n get remoteLevels() {\n return this._remoteLevels;\n }\n\n /**\n * Check to see if the {@link NetworkQualityLevel} is new, and raise an\n * event if necessary.\n * @private\n * @param {object} message\n * @returns {void}\n */\n _handleNetworkQualityMessage(message) {\n let updated = false;\n let level = null;\n const local = message ? message.local : null;\n if (typeof local === 'number') {\n // NOTE(mroberts): In prod, we plan to only send the level.\n level = local;\n this._levels = null;\n } else if (typeof local === 'object' && local) {\n // NOTE(mroberts): In dev, we plan to send the decomposed levels. An early\n // VMS version does not compute `level` for us, so we fallback to taking\n // the minimum ourselves.\n this._levels = local;\n level = typeof local.level === 'number'\n ? local.level\n : Math.min(\n local.audio.send,\n local.audio.recv,\n local.video.send,\n local.video.recv);\n }\n if (level !== null && this.level !== level) {\n this._level = level;\n updated = true;\n }\n this._remoteLevels = message && message.remotes\n ? message.remotes.reduce((levels, obj) => {\n const oldObj = this._remoteLevels.get(obj.sid) || {};\n if (oldObj.level !== obj.level) {\n updated = true;\n }\n return levels.set(obj.sid, obj);\n }, new Map())\n : this._remoteLevels;\n\n if (updated) {\n this.emit('updated');\n }\n\n\n // score is received. so reset the timer to default timeout.\n this._resendTimer.setDelay(NETWORK_QUALITY_RESPONSE_TIME_MS);\n\n // timer is cleared only while we are sending inputs.\n // if we are already sending inputs do not send them again.\n if (this._resendTimer.isSet) {\n setTimeout(() => this._sendNetworkQualityInputs(), 1000);\n }\n }\n\n /**\n * Start sending {@link NetworkQualityInputs}.\n * @private\n * @returns {Promise}\n */\n _sendNetworkQualityInputs() {\n this._resendTimer.clear();\n return this._networkQualityInputs.take().then(networkQualityInputs => {\n if (this._transport) {\n this._transport.publish(\n createNetworkQualityInputsMessage(networkQualityInputs, this._networkQualityReportLevels));\n }\n }).finally(() => {\n this._resendTimer.start();\n });\n }\n\n /**\n * Put {@link NetworkQualityInputs} to be used for computing\n * {@link NetworkQualityLevel}.\n * @param {NetworkQualityInputs} networkQualityInputs\n * @returns {void}\n */\n put(networkQualityInputs) {\n this._networkQualityInputs.put(networkQualityInputs);\n }\n}\n\n/**\n * The {@link NetworkQualityLevel} changed.\n * @event NetworkQualitySignaling#updated\n */\n\n/**\n * @typedef {object} NetworkQualityReportLevels\n * @param {number} reportLevel\n * @param {number} remoteReportLevel\n */\n\n/**\n * @param {NetworkQualityInputs} networkQualityInputs\n * @param {NetworkQualityReportLevels} networkQualityReportLevels\n * @returns {object} message\n */\nfunction createNetworkQualityInputsMessage(networkQualityInputs, networkQualityReportLevels) {\n return Object.assign(\n { type: 'network_quality' },\n networkQualityInputs,\n networkQualityReportLevels);\n}\n\nmodule.exports = NetworkQualitySignaling;\n", "'use strict';\n\nconst EventEmitter = require('events').EventEmitter;\n\n/**\n * Represents recording state\n * @extends EventEmitter\n * @property {?boolean} isEnabled\n */\nclass RecordingSignaling extends EventEmitter {\n /**\n * Construct a {@link RecordingSignaling}.\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _isEnabled: {\n value: null,\n writable: true\n },\n isEnabled: {\n enumerable: true,\n get() {\n return this._isEnabled;\n }\n }\n });\n }\n\n /**\n * Disable the {@link RecordingSignaling} if it is not already disabled.\n * @return {this}\n */\n disable() {\n return this.enable(false);\n }\n\n /**\n * Enable (or disable) the {@link RecordingSignaling} if it is not already enabled\n * (or disabled).\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (this.isEnabled !== enabled) {\n this._isEnabled = enabled;\n this.emit('updated');\n }\n return this;\n }\n}\n\n/**\n * Emitted whenever the {@link RecordingSignaling} is updated\n * @event RecordingSignaling#updated\n */\n\nmodule.exports = RecordingSignaling;\n", "'use strict';\n\nconst RecordingSignaling = require('../recording');\n\n/**\n * @extends RecordingSignaling\n */\nclass RecordingV2 extends RecordingSignaling {\n /**\n * Construct a {@link RecordingV2}.\n */\n constructor() {\n super();\n Object.defineProperties(this, {\n _revision: {\n value: 1,\n writable: true\n }\n });\n }\n\n /**\n * Compare the {@link RecordingV2} to a {@link RecordingV2#Representation}\n * of itself and perform any updates necessary.\n * @param {RecordingV2#Representation} recording\n * @returns {this}\n * @fires RecordingSignaling#updated\n */\n update(recording) {\n if (recording.revision < this._revision) {\n return this;\n }\n this._revision = recording.revision;\n return this.enable(recording.is_recording);\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link RecordingV2}\n * @typedef {object} RecordingV2#Representation\n * @property {boolean} enabled\n * @property {number} revision\n */\n\nmodule.exports = RecordingV2;\n", "'use strict';\n\nconst DefaultRecordingSignaling = require('./recording');\nconst StateMachine = require('../statemachine');\nconst DefaultTimeout = require('../util/timeout');\nconst { buildLogLevels } = require('../util');\nconst { DEFAULT_LOG_LEVEL } = require('../util/constants');\nconst Log = require('../util/log');\n\nconst {\n MediaConnectionError,\n MediaDTLSTransportFailedError,\n SignalingConnectionDisconnectedError\n} = require('../util/twilio-video-errors');\n\nlet nInstances = 0;\n\n/*\nRoomSignaling States\n-----------------------\n\n +-----------+ +--------------+\n | | | |\n | connected |---->| disconnected |\n | | | |\n +-----------+ +--------------+\n | ^ ^\n | | |\n | | +--------------+\n | +---| |\n | | reconnecting |\n +----->| |\n +--------------+\n\n*/\n\nconst states = {\n connected: [\n 'reconnecting',\n 'disconnected'\n ],\n reconnecting: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link Room} implementation\n * @extends StateMachine\n * @property {RTCPeerConnectionState} connectionState\n * @property {?Participant.SID} dominantSpeakerSid\n * @property {ParticipantSignaling} localParticipant\n * @property {RTCIceConnectionState} iceConnectionState\n * @property {string} name\n * @property {Map} participants\n * @property {RecordingSignaling} recording\n * @property {Room.SID} sid\n * @property {string} state - \"connected\", \"reconnecting\", or \"disconnected\"\n * @property {string} signalingConnectionState - \"connected\",\n * \"reconnecting\", or \"disconnected\"\n * @emits RoomSignaling#connectionStateChanged\n * @emits RoomSignaling#dominantSpeakerChanged\n * @emits RoomSignaling#iceConnectionStateChanged\n * @emits RoomSignaling#signalingConnectionStateChanged\n */\nclass RoomSignaling extends StateMachine {\n /**\n * Construct a {@link RoomSignaling}.\n * @param {ParticipantSignaling} localParticipant\n * @param {Room.SID} sid\n * @param {string} name\n * @param {object} options\n */\n constructor(localParticipant, sid, name, options) {\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL,\n RecordingSignaling: DefaultRecordingSignaling,\n Timeout: DefaultTimeout\n }, options);\n\n const logLevels = buildLogLevels(options.logLevel);\n\n super('connected', states);\n\n const RecordingSignaling = options.RecordingSignaling;\n\n const sessionTimeout = new options.Timeout(() => {\n this._disconnect(this._reconnectingError);\n }, options.sessionTimeout, false);\n\n Object.defineProperties(this, {\n _instanceId: {\n value: nInstances++\n },\n _log: {\n value: options.log\n ? options.log.createLog('default', this)\n : new Log('default', this, logLevels, options.loggerName)\n },\n _mediaConnectionIsReconnecting: {\n writable: true,\n value: false\n },\n _options: {\n value: options\n },\n _reconnectingError: {\n value: null,\n writable: true\n },\n _sessionTimeout: {\n value: sessionTimeout\n },\n dominantSpeakerSid: {\n enumerable: true,\n value: null,\n writable: true\n },\n localParticipant: {\n enumerable: true,\n value: localParticipant\n },\n name: {\n enumerable: true,\n value: name\n },\n participants: {\n enumerable: true,\n value: new Map()\n },\n recording: {\n enumerable: true,\n value: new RecordingSignaling()\n },\n sid: {\n enumerable: true,\n value: sid\n }\n });\n\n this.on('connectionStateChanged', () => {\n if (this.connectionState === 'failed'\n && !['disconnected', 'failed'].includes(this.iceConnectionState)) {\n this._disconnect(new MediaDTLSTransportFailedError());\n }\n });\n\n this.on('iceConnectionStateChanged', () => maybeUpdateState(this));\n this.on('signalingConnectionStateChanged', () => maybeUpdateState(this));\n\n // NOTE(mmalavalli): In case \"iceConnectionState\" is already failed, update\n // the RoomSignaling state. setTimeout() ensures that the state is updated\n // after RoomV2's constructor is fully executed, thereby making \"signalingConnectionState\"\n // available here.\n setTimeout(() => maybeUpdateState(this));\n }\n\n /**\n * Disconnect, possibly with an Error.\n * @private\n * @param {Error} [error]\n * @returns {boolean}\n */\n _disconnect(error) {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected', null, [error]);\n return true;\n }\n return false;\n }\n\n toString() {\n return `[RoomSignaling #${this._instanceId}: ${this.localParticipant ? this.localParticipant.sid : 'null'}]`;\n }\n\n /**\n * Connect {@link RemoteParticipantSignaling} to the {@link RoomSignaling}.\n * @param {RemoteParticipantSignaling} participant\n * @returns {boolean}\n */\n connectParticipant(participant) {\n const self = this;\n\n if (participant.state === 'disconnected') {\n return false;\n }\n\n if (this.participants.has(participant.sid)) {\n return false;\n }\n\n this.participants.set(participant.sid, participant);\n\n participant.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n participant.removeListener('stateChanged', stateChanged);\n self.participants.delete(participant.sid);\n self.emit('participantDisconnected', participant);\n }\n });\n\n this.emit('participantConnected', participant);\n\n return true;\n }\n\n /**\n * Disconnect.\n * @returns {boolean}\n */\n disconnect() {\n return this._disconnect();\n }\n\n /**\n * Set (or unset) the Dominant Speaker.\n * @param {?Participant.SID} dominantSpeakerSid\n * @returns {void}\n */\n setDominantSpeaker(dominantSpeakerSid) {\n this.dominantSpeakerSid = dominantSpeakerSid;\n this.emit('dominantSpeakerChanged');\n }\n}\n\n/**\n * @event RoomSignaling#event:connectionStateChanged\n */\n\n/**\n * @event RoomSignaling#event:dominantSpeakerChanged\n */\n\n/**\n * {@link RemoteParticipantSignaling} connected to the {@link RoomSignaling}.\n * @event RoomSignaling#event:participantConnected\n * @param {RemoteParticipantSignaling} participantSignaling\n */\n\n/**\n * {@link RemoteParticipantSignaling} disconnected from the {@link RoomSignaling}.\n * @event RoomSignaling#event:participantDisconnected\n * @param {RemoteParticipantSignaling} participantSignaling\n */\n\n/**\n * @event RoomSignaling#event:iceConnectionStateChanged\n */\n\n/**\n * @event RoomSignaling#event:signalingConnectionStateChanged\n */\n\n/**\n * Maybe update the {@link RoomSignaling} state.\n * @param {RoomSignaling} roomSignaling\n */\nfunction maybeUpdateState(roomSignaling) {\n if (roomSignaling.state === 'disconnected' || roomSignaling.signalingConnectionState === 'disconnected') {\n roomSignaling._sessionTimeout.clear();\n return;\n }\n\n let newState;\n\n if (roomSignaling.signalingConnectionState === 'reconnecting') {\n newState = roomSignaling.signalingConnectionState;\n } else if (roomSignaling.iceConnectionState === 'failed') {\n roomSignaling._mediaConnectionIsReconnecting = true;\n newState = 'reconnecting';\n } else if (roomSignaling.iceConnectionState === 'new' || roomSignaling.iceConnectionState === 'checking') {\n newState = roomSignaling._mediaConnectionIsReconnecting ? 'reconnecting' : 'connected';\n } else {\n roomSignaling._mediaConnectionIsReconnecting = false;\n roomSignaling._reconnectingError = null;\n roomSignaling._sessionTimeout.clear();\n newState = 'connected';\n }\n\n if (newState === roomSignaling.state) {\n return;\n }\n\n if (newState === 'reconnecting') {\n roomSignaling._reconnectingError = roomSignaling.signalingConnectionState === 'reconnecting'\n ? new SignalingConnectionDisconnectedError()\n : new MediaConnectionError();\n roomSignaling._sessionTimeout.start();\n roomSignaling.preempt(newState, null, [roomSignaling._reconnectingError]);\n } else {\n roomSignaling.preempt(newState);\n }\n}\n\nmodule.exports = RoomSignaling;\n", "'use strict';\n\n/**\n * Bandwidth network quality statistics.\n * @property {?number} actual - the actual bandwidth used, in bits per second\n * @property {?number} available - an estimate of available useable bandwidth, in bits per second\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for bandwidth\n */\nclass NetworkQualityBandwidthStats {\n /**\n * Construct a {@link NetworkQualityBandwidthStats}.\n * @param {BandwidthStats} bandwidthStats\n */\n constructor({ actual = null, available = null, level = null }) {\n Object.defineProperties(this, {\n actual: {\n value: actual,\n enumerable: true\n },\n available: {\n value: available,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityBandwidthStats;\n", "'use strict';\n\n/**\n * Fraction lost network quality statistics.\n * @property {?number} fractionLost - packets lost\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for fraction lost\n */\nclass NetworkQualityFractionLostStats {\n /**\n * Construct a {@link NetworkQualityFractionLostStats}.\n * @param {FractionLostStats} fractionLostStats\n */\n constructor({ fractionLost = null, level = null }) {\n Object.defineProperties(this, {\n fractionLost: {\n value: fractionLost,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityFractionLostStats;\n", "'use strict';\n\n/**\n * Latency network quality statistics.\n * @property {?number} jitter - media jitter in seconds\n * @property {?number} rtt - round trip time in seconds\n * @property {?NetworkQualityLevel} level - {@link NetworkQualityLevel} for latency\n */\nclass NetworkQualityLatencyStats {\n /**\n * Construct a {@link NetworkQualityLatencyStats}.\n * @param {LatencyStats} latencyStats\n */\n constructor({ jitter = null, rtt = null, level = null }) {\n Object.defineProperties(this, {\n jitter: {\n value: jitter,\n enumerable: true\n },\n rtt: {\n value: rtt,\n enumerable: true\n },\n level: {\n value: level,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityLatencyStats;\n", "'use strict';\n\nconst NetworkQualityBandwidthStats = require('./networkqualitybandwidthstats');\nconst NetworkQualityFractionLostStats = require('./networkqualityfractionloststats');\nconst NetworkQualityLatencyStats = require('./networkqualitylatencystats');\n\n/**\n * Network quality statistics shared between {@link NetworkQualitySendStats} and\n * {@link NetworkQualityRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#send or\n * {@link NetworkQualityMediaStats}#recv is calculated.\n * @property {?NetworkQualityBandwidthStats} bandwidth - bandwidth statistics\n * @property {?NetworkQualityLatencyStats} latency - latency statistics\n * @property {?NetworkQualityFractionLostStats} fractionLost - fraction lost statistics\n */\nclass NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualitySendOrRecvStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor({ bandwidth = null, fractionLost = null, latency = null }) {\n Object.defineProperties(this, {\n bandwidth: {\n value: bandwidth ? new NetworkQualityBandwidthStats(bandwidth) : null,\n enumerable: true\n },\n fractionLost: {\n value: fractionLost ? new NetworkQualityFractionLostStats(fractionLost) : null,\n enumerable: true\n },\n latency: {\n value: latency ? new NetworkQualityLatencyStats(latency) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualitySendOrRecvStats;\n", "'use strict';\n\nconst NetworkQualitySendOrRecvStats = require('./networkqualitysendorrecvstats');\n\n/**\n * {@link NetworkQualitySendOrRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#send is calculated.\n */\nclass NetworkQualitySendStats extends NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualitySendStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor(sendOrRecvStats) {\n super(sendOrRecvStats);\n }\n}\n\nmodule.exports = NetworkQualitySendStats;\n", "'use strict';\n\nconst NetworkQualitySendOrRecvStats = require('./networkqualitysendorrecvstats');\n\n/**\n * {@link NetworkQualitySendOrRecvStats} based on which a {@link Participant}'s\n * {@link NetworkQualityMediaStats}#recv is calculated.\n */\nclass NetworkQualityRecvStats extends NetworkQualitySendOrRecvStats {\n /**\n * Construct a {@link NetworkQualityRecvStats}.\n * @param {SendOrRecvStats} sendOrRecvStats\n */\n constructor(sendOrRecvStats) {\n super(sendOrRecvStats);\n }\n}\n\nmodule.exports = NetworkQualityRecvStats;\n", "'use strict';\n\nconst NetworkQualitySendStats = require('./networkqualitysendstats');\nconst NetworkQualityRecvStats = require('./networkqualityrecvstats');\n\n/**\n * Network quality statistics shared between a {@link Participant}'s audio or video.\n * @property {NetworkQualityLevel} send - {@link NetworkQualityLevel} of the\n * {@link Participant}'s published audio or video\n * @property {number} recv - {@link NetworkQualityLevel} of the\n * {@link Participant}'s subscribed audio or video\n * @property {?NetworkQualitySendOrRecvStats} sendStats - {@link NetworkQualitySendOrRecvStats}\n * based on which {@link NetworkQualityMediaStats}#send\n * is calculated\n * @property {?NetworkQualitySendOrRecvStats} recvStats - {@link NetworkQualitySendOrRecvStats}\n * based on which {@link NetworkQualityMediaStats}#recv\n * is calculated\n */\nclass NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityMediaStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor({ send, recv, sendStats = null, recvStats = null }) {\n Object.defineProperties(this, {\n send: {\n value: send,\n enumerable: true\n },\n recv: {\n value: recv,\n enumerable: true\n },\n sendStats: {\n value: sendStats ? new NetworkQualitySendStats(sendStats) : null,\n enumerable: true\n },\n recvStats: {\n value: recvStats ? new NetworkQualityRecvStats(recvStats) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityMediaStats;\n", "'use strict';\n\nconst NetworkQualityMediaStats = require('./networkqualitymediastats');\n\n/**\n * {@link NetworkQualityMediaStats} for a {@link Participant}'s audio.\n */\nclass NetworkQualityAudioStats extends NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityAudioStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor(mediaLevels) {\n super(mediaLevels);\n }\n}\n\nmodule.exports = NetworkQualityAudioStats;\n", "'use strict';\n\nconst NetworkQualityMediaStats = require('./networkqualitymediastats');\n\n/**\n * {@link NetworkQualityMediaStats} for a {@link Participant}'s video.\n */\nclass NetworkQualityVideoStats extends NetworkQualityMediaStats {\n /**\n * Construct a {@link NetworkQualityVideoStats}.\n * @param {MediaLevels} mediaLevels\n */\n constructor(mediaLevels) {\n super(mediaLevels);\n }\n}\n\nmodule.exports = NetworkQualityVideoStats;\n", "'use strict';\n\nconst NetworkQualityAudioStats = require('./networkqualityaudiostats');\nconst NetworkQualityVideoStats = require('./networkqualityvideostats');\n\n/**\n * Network quality statistics for a {@link Participant}.\n * @property {NetworkQualityLevel} level - {@link NetworkQualityLevel} of the {@link Participant}\n * @property {?NetworkQualityAudioStats} audio - {@link NetworkQualityMediaStats}\n * for audio; null if {@link NetworkQualityVerbosity} is {@link NetworkQualityVerbosity}#minimal\n * or below\n * @property {?NetworkQualityVideoStats} video - {@link NetworkQualityMediaStats}\n * for video; null if {@link NetworkQualityVerbosity} is {@link NetworkQualityVerbosity}#minimal\n * or below\n */\nclass NetworkQualityStats {\n /**\n * Construct a {@link NetworkQualityStats}.\n * @param {NetworkQualityLevels} networkQualityLevels\n */\n constructor({ level, audio, video }) {\n Object.defineProperties(this, {\n level: {\n value: level,\n enumerable: true\n },\n audio: {\n value: audio ? new NetworkQualityAudioStats(audio) : null,\n enumerable: true\n },\n video: {\n value: video ? new NetworkQualityVideoStats(video) : null,\n enumerable: true\n }\n });\n }\n}\n\nmodule.exports = NetworkQualityStats;\n", "'use strict';\n\nconst StateMachine = require('../statemachine');\nconst NetworkQualityStats = require('../stats/networkqualitystats');\n\n/*\nParticipantSignaling States\n----------------------\n\n +------------+ +-----------+ +--------------+\n | | | | | |\n | connecting |---->| connected |---->| disconnected |\n | | | | | |\n +------------+ +-----------+ +--------------+\n | ^ ^\n | | +--------------+ |\n | |--| | |\n |--->| reconnecting |--|\n | |\n +--------------+\n*/\n\nconst states = {\n connecting: [\n 'connected'\n ],\n connected: [\n 'disconnected',\n 'reconnecting'\n ],\n reconnecting: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link Participant} implementation\n * @extends StateMachine\n * @property {?string} identity\n * @property {?Participant.SID} sid\n * @property {string} state - \"connecting\", \"connected\", or \"disconnected\"\n * @property {Map} tracks\n * @emits ParticipantSignaling#networkQualityLevelChanged\n * @emits ParticipantSignaling#trackAdded\n * @emits ParticipantSignaling#trackRemoved\n */\nclass ParticipantSignaling extends StateMachine {\n /**\n * Construct a {@link ParticipantSignaling}.\n */\n constructor() {\n super('connecting', states);\n\n Object.defineProperties(this, {\n _identity: {\n writable: true,\n value: null\n },\n _networkQualityLevel: {\n value: null,\n writable: true\n },\n _networkQualityStats: {\n value: null,\n writable: true\n },\n _sid: {\n writable: true,\n value: null\n },\n identity: {\n enumerable: true,\n get() {\n return this._identity;\n }\n },\n sid: {\n enumerable: true,\n get() {\n return this._sid;\n }\n },\n tracks: {\n enumerable: true,\n value: new Map()\n }\n });\n }\n\n /**\n * Get the current {@link NetworkQualityLevel}, if any.\n * @returns {?NetworkQualityLevel} networkQualityLevel - initially null\n */\n get networkQualityLevel() {\n return this._networkQualityLevel;\n }\n\n /**\n * Get the current {@link NetworkQualityStats}\n * @returns {?NetworkQualityStats} networkQualityStats - initially null\n */\n get networkQualityStats() {\n return this._networkQualityStats;\n }\n\n /**\n * Add the {@link TrackSignaling}, MediaStreamTrack, or\n * {@link DataTrackSender} to the {@link ParticipantSignaling}.\n * @param {TrackSignaling|DataTrackSender|MediaTrackSender} track\n * @returns {this}\n * @fires ParticipantSignaling#trackAdded\n */\n addTrack(track) {\n this.tracks.set(track.id || track.sid, track);\n this.emit('trackAdded', track);\n return this;\n }\n\n /**\n * Disconnect the {@link ParticipantSignaling}.\n * @returns {boolean}\n */\n disconnect() {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected');\n return true;\n }\n return false;\n }\n\n /**\n * Remove the {@link TrackSignaling}, MediaStreamTrack, or\n * {@link DataTrackSender} from the {@link ParticipantSignaling}.\n * @param {TrackSignaling|DataTrackSender|MediaTrackSender} track\n * @returns {?TrackSignaling}\n * @fires ParticipantSignaling#trackRemoved\n */\n removeTrack(track) {\n const signaling = this.tracks.get(track.id || track.sid);\n this.tracks.delete(track.id || track.sid);\n if (signaling) {\n this.emit('trackRemoved', track);\n }\n return signaling || null;\n }\n\n /**\n * @param {NetworkQualityLevel} networkQualityLevel\n * @param {?NetworkQualityLevels} [networkQualityLevels=null]\n * @returns {void}\n */\n setNetworkQualityLevel(networkQualityLevel, networkQualityLevels) {\n if (this._networkQualityLevel !== networkQualityLevel) {\n this._networkQualityLevel = networkQualityLevel;\n this._networkQualityStats = networkQualityLevels\n && (networkQualityLevels.audio || networkQualityLevels.video)\n ? new NetworkQualityStats(networkQualityLevels)\n : null;\n this.emit('networkQualityLevelChanged');\n }\n }\n\n /**\n * Connect the {@link ParticipantSignaling}.\n * @param {Participant.SID} sid\n * @param {string} identity\n * @returns {boolean}\n */\n connect(sid, identity) {\n if (this.state === 'connecting' || this.state === 'reconnecting') {\n if (!this._sid) {\n this._sid = sid;\n }\n if (!this._identity) {\n this._identity = identity;\n }\n this.preempt('connected');\n return true;\n }\n return false;\n }\n\n /**\n * Transition to \"reconnecting\" state.\n * @returns {boolean}\n */\n reconnecting() {\n if (this.state === 'connecting' || this.state === 'connected') {\n this.preempt('reconnecting');\n return true;\n }\n return false;\n }\n}\n\n/**\n * @event ParticipantSignaling#event:networkQualityLevelChanged\n */\n\n/**\n * {@link TrackSignaling} was added to the {@link ParticipantSignaling}.\n * @event ParticipantSignaling#trackAdded\n * @param {TrackSignaling} track\n */\n\n/**\n * {@link TrackSignaling} was removed from the {@link ParticipantSignaling}.\n * @event ParticipantSignaling#trackRemoved\n * @param {TrackSignaling} track\n */\n\nmodule.exports = ParticipantSignaling;\n", "'use strict';\n\nconst ParticipantSignaling = require('./participant');\n\n/**\n * A {@link Participant} implementation\n * @extends ParticipantSignaling\n * @property {string} identity\n * @property {Participant.SID} sid\n */\nclass RemoteParticipantSignaling extends ParticipantSignaling {\n /**\n * Construct a {@link RemoteParticipantSignaling}.\n * @param {Participant.SID} sid\n * @param {string} identity\n */\n constructor(sid, identity) {\n super();\n this.connect(sid, identity);\n }\n}\n\nmodule.exports = RemoteParticipantSignaling;\n", "'use strict';\n\nconst { EventEmitter } = require('events');\n\n/**\n * A {@link Track} implementation\n * @extends EventEmitter\n * @property {Track.Kind} kind\n * @property {string} name\n */\nclass TrackSignaling extends EventEmitter {\n /**\n * Construct a {@link TrackSignaling}.\n * @param {string} name\n * @param {Track.Kind} kind\n * @param {boolean} isEnabled\n * @param {Track.Priority} priority\n */\n constructor(name, kind, isEnabled, priority) {\n super();\n let sid = null;\n Object.defineProperties(this, {\n _error: {\n value: null,\n writable: true\n },\n _isEnabled: {\n value: isEnabled,\n writable: true\n },\n _priority: {\n value: priority,\n writable: true\n },\n _trackTransceiver: {\n value: null,\n writable: true\n },\n _sid: {\n get() {\n return sid;\n },\n set(_sid) {\n if (sid === null) {\n sid = _sid;\n }\n }\n },\n kind: {\n enumerable: true,\n value: kind\n },\n name: {\n enumerable: true,\n value: name\n }\n });\n }\n\n /**\n * Non-null if publication or subscription failed.\n * @property {?Error} error\n */\n get error() {\n return this._error;\n }\n\n /**\n * Whether the {@link TrackSignaling} is enabled.\n * @property {boolean}\n */\n get isEnabled() {\n return this._isEnabled;\n }\n\n /**\n * The {@link TrackSignaling}'s priority.\n * @property {Track.Priority}\n */\n get priority() {\n return this._priority;\n }\n\n /**\n * The {@link TrackSignaling}'s {@link Track.SID}.\n * @property {Track.SID}\n */\n get sid() {\n return this._sid;\n }\n\n /**\n * The {@link TrackSignaling}'s {@link TrackTransceiver}.\n * @property {TrackTransceiver}\n */\n get trackTransceiver() {\n return this._trackTransceiver;\n }\n\n /**\n * Disable the {@link TrackSignaling} if it is not already disabled.\n * @return {this}\n */\n disable() {\n return this.enable(false);\n }\n\n /**\n * Enable (or disable) the {@link TrackSignaling} if it is not already enabled\n * (or disabled).\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n if (this.isEnabled !== enabled) {\n this._isEnabled = enabled;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the {@link TrackTransceiver} on the {@link TrackSignaling}.\n * @param {TrackTransceiver} trackTransceiver\n * @returns {this}\n */\n\n setTrackTransceiver(trackTransceiver) {\n trackTransceiver = trackTransceiver || null;\n if (this.trackTransceiver !== trackTransceiver) {\n this._trackTransceiver = trackTransceiver;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the SID on the {@link TrackSignaling} once.\n * @param {string} sid\n * @returns {this}\n */\n setSid(sid) {\n if (this.sid === null) {\n this._sid = sid;\n this.emit('updated');\n }\n return this;\n }\n}\n\n/**\n * Emitted whenever the {@link TrackSignaling} is updated\n * @event TrackSignaling#updated\n */\n\nmodule.exports = TrackSignaling;\n", "'use strict';\n\nconst TrackSignaling = require('./track');\n\n/**\n * A {@link RemoteTrackPublication} implementation\n * @extends TrackSignaling\n */\nclass RemoteTrackPublicationSignaling extends TrackSignaling {\n /**\n * Construct a {@link RemoteTrackPublicationSignaling}.\n * @param {Track.SID} sid\n * @param {string} name\n * @param {Track.Kind} kind\n * @param {boolean} isEnabled\n * @param {Track.Priority} priority\n * @param {boolean} isSwitchedOff\n */\n constructor(sid, name, kind, isEnabled, priority, isSwitchedOff) {\n super(name, kind, isEnabled, priority);\n Object.defineProperties(this, {\n _isSwitchedOff: {\n value: isSwitchedOff,\n writable: true\n },\n });\n this.setSid(sid);\n }\n\n /**\n * Whether the {@link RemoteTrackPublicationSignaling} is subscribed to.\n * @property {boolean}\n */\n get isSubscribed() {\n return !!this.trackTransceiver;\n }\n\n /**\n * Whether the {@link RemoteTrackPublicationSignaling} is switched off.\n * @property {boolean}\n */\n get isSwitchedOff() {\n return this._isSwitchedOff;\n }\n\n /**\n * @param {Error} error\n * @returns {this}\n */\n subscribeFailed(error) {\n if (!this.error) {\n this._error = error;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Update the publish {@link Track.Priority}.\n * @param {Track.Priority} priority\n * @returns {this}\n */\n setPriority(priority) {\n if (this._priority !== priority) {\n this._priority = priority;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Updates track switch on/off state.\n * @param {boolean} isSwitchedOff\n * @returns {this}\n */\n setSwitchedOff(isSwitchedOff) {\n if (this._isSwitchedOff !== isSwitchedOff) {\n this._isSwitchedOff = isSwitchedOff;\n this.emit('updated');\n }\n return this;\n }\n}\n\n\nmodule.exports = RemoteTrackPublicationSignaling;\n", "'use strict';\n\nconst RemoteTrackPublicationSignaling = require('../remotetrackpublication');\n\n/**\n * @extends RemoteTrackPublicationSignaling\n */\nclass RemoteTrackPublicationV2 extends RemoteTrackPublicationSignaling {\n /**\n * Construct a {@link RemoteTrackPublicationV2}.\n * @param {RemoteTrackPublicationV2#Representation} track\n * @param {boolean} isSwitchedOff\n *\n */\n constructor(track, isSwitchedOff) {\n super(track.sid, track.name, track.kind, track.enabled, track.priority, isSwitchedOff);\n }\n\n /**\n * Compare the {@link RemoteTrackPublicationV2} to a\n * {@link RemoteTrackPublicationV2#Representation} of itself and perform any\n * updates necessary.\n * @param {RemoteTrackPublicationV2#Representation} track\n * @returns {this}\n * @fires TrackSignaling#updated\n */\n update(track) {\n this.enable(track.enabled);\n this.setPriority(track.priority);\n return this;\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link RemoteTrackPublicationV2}.\n * @typedef {LocalTrackPublicationV2#Representation} RemoteTrackPublicationV2#Representation\n * @property {boolean} subscribed\n */\n\nmodule.exports = RemoteTrackPublicationV2;\n", "'use strict';\n\nconst RemoteParticipantSignaling = require('../remoteparticipant');\nconst RemoteTrackPublicationV2 = require('./remotetrackpublication');\n\n/**\n * @extends RemoteParticipantSignaling\n * @property {?number} revision\n */\nclass RemoteParticipantV2 extends RemoteParticipantSignaling {\n /**\n * Construct a {@link RemoteParticipantV2}.\n * @param {object} participantState\n * @param {function(Track.SID): boolean} getInitialTrackSwitchOffState\n * @param {function(Track.SID, Track.Priority): boolean} setPriority\n * @param {function(Track.SID, ClientRenderHint): Promise} setRenderHint\n * @param {function(Track.SID): void} clearTrackHint\n * @param {object} [options]\n */\n constructor(participantState, getInitialTrackSwitchOffState, setPriority, setRenderHint, clearTrackHint, options) {\n super(participantState.sid, participantState.identity);\n\n options = Object.assign({\n RemoteTrackPublicationV2\n }, options);\n\n Object.defineProperties(this, {\n _revision: {\n writable: true,\n value: null\n },\n _RemoteTrackPublicationV2: {\n value: options.RemoteTrackPublicationV2\n },\n _getInitialTrackSwitchOffState: {\n value: getInitialTrackSwitchOffState\n },\n updateSubscriberTrackPriority: {\n value: (trackSid, priority) => setPriority(trackSid, priority)\n },\n updateTrackRenderHint: {\n value: (trackSid, renderHint) => setRenderHint(trackSid, renderHint)\n },\n clearTrackHint: {\n value: trackSid => clearTrackHint(trackSid)\n },\n revision: {\n enumerable: true,\n get() {\n return this._revision;\n }\n }\n });\n\n return this.update(participantState);\n }\n\n /**\n * @private\n */\n _getOrCreateTrack(trackState) {\n const RemoteTrackPublicationV2 = this._RemoteTrackPublicationV2;\n let track = this.tracks.get(trackState.sid);\n if (!track) {\n const isSwitchedOff = this._getInitialTrackSwitchOffState(trackState.sid);\n track = new RemoteTrackPublicationV2(trackState, isSwitchedOff);\n this.addTrack(track);\n }\n return track;\n }\n\n /**\n * Update the {@link RemoteParticipantV2} with the new state.\n * @param {object} participantState\n * @returns {this}\n */\n update(participantState) {\n if (this.revision !== null && participantState.revision <= this.revision) {\n return this;\n }\n this._revision = participantState.revision;\n\n const tracksToKeep = new Set();\n\n participantState.tracks.forEach(trackState => {\n const track = this._getOrCreateTrack(trackState);\n track.update(trackState);\n tracksToKeep.add(track);\n });\n\n this.tracks.forEach(track => {\n if (!tracksToKeep.has(track)) {\n this.removeTrack(track);\n }\n });\n\n switch (participantState.state) {\n case 'disconnected':\n this.disconnect();\n break;\n case 'reconnecting':\n this.reconnecting();\n break;\n case 'connected':\n this.connect(this.sid, this.identity);\n break;\n }\n\n return this;\n }\n}\n\nmodule.exports = RemoteParticipantV2;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nclass TrackPrioritySignaling extends MediaSignaling {\n /**\n * Construct a {@link TrackPrioritySignaling}.\n * @param {Promise} getReceiver\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'track_priority', options);\n\n Object.defineProperties(this, {\n _enqueuedPriorityUpdates: {\n value: new Map()\n },\n });\n\n this.on('ready', transport => {\n Array.from(this._enqueuedPriorityUpdates.keys()).forEach(trackSid => {\n transport.publish({\n type: 'track_priority',\n track: trackSid,\n subscribe: this._enqueuedPriorityUpdates.get(trackSid)\n });\n // NOTE(mpatwardhan)- we do not clear _enqueuedPriorityUpdates intentionally,\n // this cache will is used to re-send the priorities in case of VMS-FailOver.\n });\n });\n }\n\n /**\n * @param {Track.SID} trackSid\n * @param {'publish'|'subscribe'} publishOrSubscribe\n * @param {Track.Priority} priority\n */\n sendTrackPriorityUpdate(trackSid, publishOrSubscribe, priority) {\n if (publishOrSubscribe !== 'subscribe') {\n throw new Error('only subscribe priorities are supported, found: ' + publishOrSubscribe);\n }\n this._enqueuedPriorityUpdates.set(trackSid, priority);\n if (this._transport) {\n this._transport.publish({\n type: 'track_priority',\n track: trackSid,\n subscribe: priority\n });\n }\n }\n}\n\nmodule.exports = TrackPrioritySignaling;\n", "'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\n/**\n * @emits TrackSwitchOffSignalinging#updated\n */\nclass TrackSwitchOffSignaling extends MediaSignaling {\n /**\n * Construct a {@link TrackSwitchOffSignaling}.\n * @param {Promise} getReceiver\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'track_switch_off', options);\n this.on('ready', transport => {\n transport.on('message', message => {\n switch (message.type) {\n case 'track_switch_off':\n this._setTrackSwitchOffUpdates(message.off || [], message.on || []);\n break;\n default:\n break;\n }\n });\n });\n }\n\n /**\n * @private\n * @param {[Track.SID]} tracksSwitchedOff\n * @param {[Track.SID]} tracksSwitchedOn\n * @returns {void}\n */\n _setTrackSwitchOffUpdates(tracksSwitchedOff, tracksSwitchedOn) {\n this.emit('updated', tracksSwitchedOff, tracksSwitchedOn);\n }\n}\n\n/**\n * @event TrackSwitchOffSignaling#updated\n */\n\nmodule.exports = TrackSwitchOffSignaling;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\nconst Timeout = require('../../util/timeout');\nconst { isDeepEqual } = require('../../util');\nconst RENDER_HINT_RESPONSE_TIME_MS = 2000; // time to wait for server response (before resending all hints.)\n\nlet messageId = 1;\nclass RenderHintsSignaling extends MediaSignaling {\n /**\n * Construct a {@link RenderHintsSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'render_hints', options);\n Object.defineProperties(this, {\n _trackSidsToRenderHints: {\n value: new Map()\n },\n _responseTimer: {\n value: new Timeout(() => {\n this._sendAllHints();\n // once timer fires, for next round double the delay.\n this._responseTimer.setDelay(this._responseTimer.delay * 2);\n }, RENDER_HINT_RESPONSE_TIME_MS, false),\n }\n });\n\n this.on('ready', transport => {\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'render_hints':\n this._processHintResults((message && message.subscriber && message.subscriber.hints) || []);\n break;\n default:\n this._log.warn('Unknown message type: ', message.type);\n break;\n }\n });\n\n // NOTE(mpatwardhan): When transport is set (either 1st time of after vms failover)\n // resend all track states.\n this._sendAllHints();\n });\n }\n\n _sendAllHints() {\n // to force sending all hints simply mark all tracks as dirty.\n Array.from(this._trackSidsToRenderHints.keys()).forEach(trackSid => {\n const trackState = this._trackSidsToRenderHints.get(trackSid);\n if (trackState.renderDimensions) {\n trackState.isDimensionDirty = true;\n }\n\n if ('enabled' in trackState) {\n trackState.isEnabledDirty = true;\n }\n });\n this._sendHints();\n }\n\n _processHintResults(hintResults) {\n this._responseTimer.clear();\n this._responseTimer.setDelay(RENDER_HINT_RESPONSE_TIME_MS);\n hintResults.forEach(hintResult => {\n if (hintResult.result !== 'OK') {\n this._log.debug('Server error processing hint:', hintResult);\n }\n });\n this._sendHints();\n }\n\n _sendHints() {\n if (!this._transport || this._responseTimer.isSet) {\n return;\n }\n\n const hints = [];\n Array.from(this._trackSidsToRenderHints.keys()).forEach(trackSid => {\n const trackState = this._trackSidsToRenderHints.get(trackSid);\n if (trackState.isEnabledDirty || trackState.isDimensionDirty) {\n const mspHint = {\n 'track': trackSid,\n };\n if (trackState.isEnabledDirty) {\n mspHint.enabled = trackState.enabled;\n trackState.isEnabledDirty = false;\n }\n if (trackState.isDimensionDirty) {\n // eslint-disable-next-line camelcase\n mspHint.render_dimensions = trackState.renderDimensions;\n trackState.isDimensionDirty = false;\n }\n hints.push(mspHint);\n }\n });\n\n if (hints.length > 0) {\n const payLoad = {\n type: 'render_hints',\n subscriber: {\n id: messageId++,\n hints\n }\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n this._responseTimer.start();\n }\n }\n\n /**\n * @param {Track.SID} trackSid\n * @param {ClientRenderHint} renderHint\n */\n setTrackHint(trackSid, renderHint) {\n const trackState = this._trackSidsToRenderHints.get(trackSid) || { isEnabledDirty: false, isDimensionDirty: false };\n if ('enabled' in renderHint && trackState.enabled !== renderHint.enabled) {\n trackState.enabled = !!renderHint.enabled;\n trackState.isEnabledDirty = true;\n }\n\n if (renderHint.renderDimensions && !isDeepEqual(renderHint.renderDimensions, trackState.renderDimensions)) {\n // eslint-disable-next-line camelcase\n trackState.renderDimensions = renderHint.renderDimensions;\n trackState.isDimensionDirty = true;\n }\n this._trackSidsToRenderHints.set(trackSid, trackState);\n this._sendHints();\n }\n\n /**\n * must be called when track is unsubscribed.\n * @param {Track.SID} trackSid\n */\n clearTrackHint(trackSid) {\n this._trackSidsToRenderHints.delete(trackSid);\n }\n}\n\n\nmodule.exports = RenderHintsSignaling;\n", "/* eslint callback-return:0 */\n'use strict';\n\nconst MediaSignaling = require('./mediasignaling');\n\nlet messageId = 1;\nclass PublisherHintsSignaling extends MediaSignaling {\n /**\n * Construct a {@link RenderHintsSignaling}.\n */\n constructor(getReceiver, options) {\n super(getReceiver, 'publisher_hints', options);\n this.on('ready', transport => {\n this._log.debug('publisher_hints transport ready:', transport);\n transport.on('message', message => {\n this._log.debug('Incoming: ', message);\n switch (message.type) {\n case 'publisher_hints':\n if (message.publisher && message.publisher.hints && message.publisher.id) {\n this._processPublisherHints(message.publisher.hints, message.publisher.id);\n }\n break;\n default:\n this._log.warn('Unknown message type: ', message.type);\n break;\n }\n });\n });\n }\n\n sendTrackReplaced({ trackSid }) {\n if (!this._transport) {\n return;\n }\n\n const payLoad = {\n type: 'client_reset',\n track: trackSid,\n id: messageId++\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n }\n\n sendHintResponse({ id, hints }) {\n if (!this._transport) {\n return;\n }\n const payLoad = {\n type: 'publisher_hints',\n id,\n hints\n };\n this._log.debug('Outgoing: ', payLoad);\n this._transport.publish(payLoad);\n }\n\n /**\n * @private\n */\n _processPublisherHints(hints, id) {\n try {\n this.emit('updated', hints, id);\n } catch (ex) {\n this._log.error('error processing hints:', ex);\n }\n }\n}\n\n\nmodule.exports = PublisherHintsSignaling;\n", "/* eslint-disable no-console */\n'use strict';\n\nconst DominantSpeakerSignaling = require('./dominantspeakersignaling');\nconst NetworkQualityMonitor = require('./networkqualitymonitor');\nconst NetworkQualitySignaling = require('./networkqualitysignaling');\nconst RecordingV2 = require('./recording');\nconst RoomSignaling = require('../room');\nconst RemoteParticipantV2 = require('./remoteparticipant');\nconst StatsReport = require('../../stats/statsreport');\nconst TrackPrioritySignaling = require('./trackprioritysignaling');\nconst TrackSwitchOffSignaling = require('./trackswitchoffsignaling');\nconst RenderHintsSignaling = require('./renderhintssignaling');\nconst PublisherHintsSignaling = require('./publisherhintsignaling.js');\n\n\nconst {\n constants: { DEFAULT_SESSION_TIMEOUT_SEC },\n createBandwidthProfilePayload,\n defer,\n difference,\n filterObject,\n flatMap,\n oncePerTick\n} = require('../../util');\n\nconst MovingAverageDelta = require('../../util/movingaveragedelta');\nconst { createTwilioError } = require('../../util/twilio-video-errors');\n\nconst STATS_PUBLISH_INTERVAL_MS = 10000;\n\n/**\n * @extends RoomSignaling\n */\nclass RoomV2 extends RoomSignaling {\n constructor(localParticipant, initialState, transport, peerConnectionManager, options) {\n initialState.options = Object.assign({\n session_timeout: DEFAULT_SESSION_TIMEOUT_SEC\n }, initialState.options);\n\n options = Object.assign({\n DominantSpeakerSignaling,\n NetworkQualityMonitor,\n NetworkQualitySignaling,\n RecordingSignaling: RecordingV2,\n RemoteParticipantV2,\n TrackPrioritySignaling,\n TrackSwitchOffSignaling,\n bandwidthProfile: null,\n sessionTimeout: initialState.options.session_timeout * 1000,\n statsPublishIntervalMs: STATS_PUBLISH_INTERVAL_MS\n }, options);\n\n localParticipant.setBandwidthProfile(options.bandwidthProfile);\n\n const { options: { signaling_region: signalingRegion, audio_processors: audioProcessors = [] } } = initialState;\n localParticipant.setSignalingRegion(signalingRegion);\n\n\n if (audioProcessors.includes('krisp')) {\n // Note(mpatwardhan): we add rnnoise as allowed_processor to enable testing our pipeline e2e.\n audioProcessors.push('rnnoise');\n }\n\n localParticipant.setAudioProcessors(audioProcessors);\n\n peerConnectionManager.setIceReconnectTimeout(options.sessionTimeout);\n\n super(localParticipant, initialState.sid, initialState.name, options);\n\n const getTrackReceiver = id => this._getTrackReceiver(id);\n const log = this._log;\n\n Object.defineProperties(this, {\n _disconnectedParticipantRevisions: {\n value: new Map()\n },\n _NetworkQualityMonitor: {\n value: options.NetworkQualityMonitor\n },\n _lastBandwidthProfileRevision: {\n value: localParticipant.bandwidthProfileRevision,\n writable: true\n },\n _mediaStatesWarningsRevision: {\n value: 0,\n writable: true\n },\n _networkQualityMonitor: {\n value: null,\n writable: true\n },\n _networkQualityConfiguration: {\n value: localParticipant.networkQualityConfiguration\n },\n _peerConnectionManager: {\n value: peerConnectionManager\n },\n _published: {\n value: new Map()\n },\n _publishedRevision: {\n value: 0,\n writable: true\n },\n _RemoteParticipantV2: {\n value: options.RemoteParticipantV2\n },\n _subscribed: {\n value: new Map()\n },\n _subscribedRevision: {\n value: 0,\n writable: true\n },\n _subscriptionFailures: {\n value: new Map()\n },\n _dominantSpeakerSignaling: {\n value: new options.DominantSpeakerSignaling(getTrackReceiver, { log })\n },\n _networkQualitySignaling: {\n value: new options.NetworkQualitySignaling(\n getTrackReceiver,\n localParticipant.networkQualityConfiguration,\n { log }\n )\n },\n _renderHintsSignaling: {\n value: new RenderHintsSignaling(getTrackReceiver, { log }),\n },\n _publisherHintsSignaling: {\n value: new PublisherHintsSignaling(getTrackReceiver, { log }),\n },\n _trackPrioritySignaling: {\n value: new options.TrackPrioritySignaling(getTrackReceiver, { log }),\n },\n _trackSwitchOffSignaling: {\n value: new options.TrackSwitchOffSignaling(getTrackReceiver, { log }),\n },\n _pendingSwitchOffStates: {\n value: new Map()\n },\n _transport: {\n value: transport\n },\n _trackReceiverDeferreds: {\n value: new Map()\n },\n mediaRegion: {\n enumerable: true,\n value: initialState.options.media_region || null\n }\n });\n\n this._initTrackSwitchOffSignaling();\n this._initDominantSpeakerSignaling();\n this._initNetworkQualityMonitorSignaling();\n this._initPublisherHintSignaling();\n\n handleLocalParticipantEvents(this, localParticipant);\n handlePeerConnectionEvents(this, peerConnectionManager);\n handleTransportEvents(this, transport);\n periodicallyPublishStats(this, transport, options.statsPublishIntervalMs);\n\n this._update(initialState);\n\n // NOTE(mpatwardhan) after initial state we know if publisher_hints are enabled or not\n // if they are not enabled. we need to undo simulcast that was enabled with initial offer.\n this._peerConnectionManager.setEffectiveAdaptiveSimulcast(this._publisherHintsSignaling.isSetup);\n }\n\n /**\n * The PeerConnection state.\n * @property {RTCPeerConnectionState}\n */\n get connectionState() {\n return this._peerConnectionManager.connectionState;\n }\n\n /**\n * The Signaling Connection State.\n * @property {string} - \"connected\", \"reconnecting\", \"disconnected\"\n */\n get signalingConnectionState() {\n return this._transport.state === 'syncing'\n ? 'reconnecting'\n : this._transport.state;\n }\n\n /**\n * The Ice Connection State.\n * @property {RTCIceConnectionState}\n */\n get iceConnectionState() {\n return this._peerConnectionManager.iceConnectionState;\n }\n\n /**\n * @private\n */\n _deleteTrackReceiverDeferred(id) {\n return this._trackReceiverDeferreds.delete(id);\n }\n\n /**\n * @private\n */\n _getOrCreateTrackReceiverDeferred(id) {\n const deferred = this._trackReceiverDeferreds.get(id) || defer();\n const trackReceivers = this._peerConnectionManager.getTrackReceivers();\n\n // NOTE(mmalavalli): In Firefox, there can be instances where a MediaStreamTrack\n // for the given Track ID already exists, for example, when a Track is removed\n // and added back. If that is the case, then we should resolve 'deferred'.\n const trackReceiver = trackReceivers.find(trackReceiver => trackReceiver.id === id && trackReceiver.readyState !== 'ended');\n\n if (trackReceiver) {\n deferred.resolve(trackReceiver);\n } else {\n // NOTE(mmalavalli): Only add the 'deferred' to the map if it's not\n // resolved. This will prevent old copies of the MediaStreamTrack from\n // being used when the remote peer removes and re-adds a MediaStreamTrack.\n this._trackReceiverDeferreds.set(id, deferred);\n }\n\n return deferred;\n }\n\n /**\n * @private\n */\n _addTrackReceiver(trackReceiver) {\n const deferred = this._getOrCreateTrackReceiverDeferred(trackReceiver.id);\n deferred.resolve(trackReceiver);\n return this;\n }\n\n /**\n * @private\n */\n _disconnect(error) {\n const didDisconnect = super._disconnect.call(this, error);\n if (didDisconnect) {\n this._teardownNetworkQualityMonitor();\n this._transport.disconnect();\n this._peerConnectionManager.close();\n }\n\n this.localParticipant.tracks.forEach(track => {\n track.publishFailed(error || new Error('LocalParticipant disconnected'));\n });\n\n return didDisconnect;\n }\n\n /**\n * @private\n */\n _getTrackReceiver(id) {\n return this._getOrCreateTrackReceiverDeferred(id).promise.then(trackReceiver => {\n this._deleteTrackReceiverDeferred(id);\n return trackReceiver;\n });\n }\n\n /**\n * @private\n */\n _getInitialTrackSwitchOffState(trackSid) {\n const initiallySwitchedOff = this._pendingSwitchOffStates.get(trackSid) || false;\n this._pendingSwitchOffStates.delete(trackSid);\n if (initiallySwitchedOff) {\n this._log.warn(`[${trackSid}] was initially switched off! `);\n }\n return initiallySwitchedOff;\n }\n\n\n /**\n * @private\n */\n _getTrackSidsToTrackSignalings() {\n const trackSidsToTrackSignalings = flatMap(this.participants, participant => Array.from(participant.tracks));\n return new Map(trackSidsToTrackSignalings);\n }\n\n /**\n * @private\n */\n _getOrCreateRemoteParticipant(participantState) {\n const RemoteParticipantV2 = this._RemoteParticipantV2;\n let participant = this.participants.get(participantState.sid);\n const self = this;\n if (!participant) {\n participant = new RemoteParticipantV2(\n participantState,\n trackSid => this._getInitialTrackSwitchOffState(trackSid),\n (trackSid, priority) => this._trackPrioritySignaling.sendTrackPriorityUpdate(trackSid, 'subscribe', priority),\n (trackSid, hint) => this._renderHintsSignaling.setTrackHint(trackSid, hint),\n trackSid => this._renderHintsSignaling.clearTrackHint(trackSid)\n );\n participant.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n participant.removeListener('stateChanged', stateChanged);\n self.participants.delete(participant.sid);\n self._disconnectedParticipantRevisions.set(participant.sid, participant.revision);\n }\n });\n this.connectParticipant(participant);\n }\n return participant;\n }\n\n /**\n * @private\n */\n _getState() {\n return {\n participant: this.localParticipant.getState()\n };\n }\n\n /**\n * @private\n */\n _maybeAddBandwidthProfile(update) {\n const { bandwidthProfile, bandwidthProfileRevision } = this.localParticipant;\n if (bandwidthProfile && this._lastBandwidthProfileRevision < bandwidthProfileRevision) {\n this._lastBandwidthProfileRevision = bandwidthProfileRevision;\n return Object.assign({\n bandwidth_profile: createBandwidthProfilePayload(bandwidthProfile)\n }, update);\n }\n return update;\n }\n /**\n * @private\n */\n _publishNewLocalParticipantState() {\n this._transport.publish(this._maybeAddBandwidthProfile(this._getState()));\n }\n\n /**\n * @private\n */\n _publishPeerConnectionState(peerConnectionState) {\n /* eslint camelcase:0 */\n this._transport.publish(Object.assign({\n peer_connections: [peerConnectionState]\n }, this._getState()));\n }\n\n /**\n * @private\n */\n _update(roomState) {\n if (roomState.subscribed && roomState.subscribed.revision > this._subscribedRevision) {\n this._subscribedRevision = roomState.subscribed.revision;\n roomState.subscribed.tracks.forEach(trackState => {\n if (trackState.id) {\n this._subscriptionFailures.delete(trackState.sid);\n this._subscribed.set(trackState.sid, trackState.id);\n } else if (trackState.error && !this._subscriptionFailures.has(trackState.sid)) {\n this._subscriptionFailures.set(trackState.sid, trackState.error);\n }\n });\n\n const subscribedTrackSids = new Set(roomState.subscribed.tracks\n .filter(trackState => !!trackState.id)\n .map(trackState => trackState.sid));\n\n this._subscribed.forEach((trackId, trackSid) => {\n if (!subscribedTrackSids.has(trackSid)) {\n this._subscribed.delete(trackSid);\n }\n });\n }\n\n const participantsToKeep = new Set();\n\n // eslint-disable-next-line no-warning-comments\n // TODO(mroberts): Remove me once the Server is fixed.\n (roomState.participants || []).forEach(participantState => {\n if (participantState.sid === this.localParticipant.sid) {\n return;\n }\n\n // NOTE(mmalavalli): If the incoming revision for a disconnected Participant is less than or\n // equal to the revision when it was disconnected, then the state is old and can be ignored.\n // Otherwise, the Participant was most likely disconnected in a Large Group Room when it\n // stopped publishing media, and hence needs to be re-added.\n const disconnectedParticipantRevision = this._disconnectedParticipantRevisions.get(participantState.sid);\n if (disconnectedParticipantRevision && participantState.revision <= disconnectedParticipantRevision) {\n return;\n }\n\n if (disconnectedParticipantRevision) {\n this._disconnectedParticipantRevisions.delete(participantState.sid);\n }\n const participant = this._getOrCreateRemoteParticipant(participantState);\n participant.update(participantState);\n participantsToKeep.add(participant);\n });\n\n if (roomState.type === 'synced') {\n this.participants.forEach(participant => {\n if (!participantsToKeep.has(participant)) {\n participant.disconnect();\n }\n });\n }\n\n handleSubscriptions(this);\n\n // eslint-disable-next-line no-warning-comments\n // TODO(mroberts): Remove me once the Server is fixed.\n /* eslint camelcase:0 */\n if (roomState.peer_connections) {\n this._peerConnectionManager.update(roomState.peer_connections, roomState.type === 'synced');\n }\n\n if (roomState.recording) {\n this.recording.update(roomState.recording);\n }\n\n if (roomState.published && roomState.published.revision > this._publishedRevision) {\n this._publishedRevision = roomState.published.revision;\n roomState.published.tracks.forEach(track => {\n if (track.sid) {\n this._published.set(track.id, track.sid);\n }\n });\n this.localParticipant.update(roomState.published);\n }\n\n if (roomState.participant) {\n this.localParticipant.connect(\n roomState.participant.sid,\n roomState.participant.identity);\n }\n\n [\n this._dominantSpeakerSignaling,\n this._networkQualitySignaling,\n this._trackPrioritySignaling,\n this._trackSwitchOffSignaling,\n this._renderHintsSignaling,\n this._publisherHintsSignaling\n ].forEach(mediaSignaling => {\n const channel = mediaSignaling.channel;\n if (!mediaSignaling.isSetup\n && roomState.media_signaling\n && roomState.media_signaling[channel]\n && roomState.media_signaling[channel].transport\n && roomState.media_signaling[channel].transport.type === 'data-channel') {\n mediaSignaling.setup(roomState.media_signaling[channel].transport.label);\n }\n });\n\n if (roomState.type === 'warning' && roomState.states &&\n roomState.states.revision > this._mediaStatesWarningsRevision) {\n this._mediaStatesWarningsRevision = roomState.states.revision;\n this.localParticipant.updateMediaStates(roomState.states);\n }\n\n return this;\n }\n\n _initPublisherHintSignaling() {\n this._publisherHintsSignaling.on('updated', (hints, id) => {\n Promise.all(hints.map(hint => {\n return this.localParticipant.setPublisherHint(hint.track, hint.encodings).then(result => {\n return { track: hint.track, result };\n });\n })).then(hintResponses => {\n this._publisherHintsSignaling.sendHintResponse({ id, hints: hintResponses });\n });\n });\n\n const handleReplaced = track => {\n if (track.kind === 'video') {\n track.trackTransceiver.on('replaced', () => {\n this._publisherHintsSignaling.sendTrackReplaced({ trackSid: track.sid });\n });\n }\n };\n\n // hook up for any existing and new tracks getting replaced.\n Array.from(this.localParticipant.tracks.values()).forEach(track => handleReplaced(track));\n this.localParticipant.on('trackAdded', track => handleReplaced(track));\n }\n\n _initTrackSwitchOffSignaling() {\n this._trackSwitchOffSignaling.on('updated', (tracksOff, tracksOn) => {\n try {\n this._log.debug('received trackSwitch: ', { tracksOn, tracksOff });\n const trackUpdates = new Map();\n tracksOn.forEach(trackSid => trackUpdates.set(trackSid, true));\n tracksOff.forEach(trackSid => {\n if (trackUpdates.get(trackSid)) {\n // NOTE(mpatwardhan): This means that VIDEO-3762 has been reproduced.\n this._log.warn(`${trackSid} is DUPLICATED in both tracksOff and tracksOn list`);\n }\n trackUpdates.set(trackSid, false);\n });\n this.participants.forEach(participant => {\n participant.tracks.forEach(track => {\n const isOn = trackUpdates.get(track.sid);\n if (typeof isOn !== 'undefined') {\n track.setSwitchedOff(!isOn);\n trackUpdates.delete(track.sid);\n }\n });\n });\n // NOTE(mpatwardhan): Cache any notification about the tracks that we do not yet know about.\n trackUpdates.forEach((isOn, trackSid) => this._pendingSwitchOffStates.set(trackSid, !isOn));\n } catch (ex) {\n this._log.error('error processing track switch off:', ex);\n }\n });\n }\n\n _initDominantSpeakerSignaling() {\n this._dominantSpeakerSignaling.on('updated', () => this.setDominantSpeaker(this._dominantSpeakerSignaling.loudestParticipantSid));\n }\n\n _initNetworkQualityMonitorSignaling() {\n this._networkQualitySignaling.on('ready', () => {\n const networkQualityMonitor = new this._NetworkQualityMonitor(this._peerConnectionManager, this._networkQualitySignaling);\n this._networkQualityMonitor = networkQualityMonitor;\n networkQualityMonitor.on('updated', () => {\n if (this.iceConnectionState === 'failed') {\n return;\n }\n this.localParticipant.setNetworkQualityLevel(\n networkQualityMonitor.level,\n networkQualityMonitor.levels);\n this.participants.forEach(participant => {\n const levels = networkQualityMonitor.remoteLevels.get(participant.sid);\n if (levels) {\n participant.setNetworkQualityLevel(levels.level, levels);\n }\n });\n });\n networkQualityMonitor.start();\n });\n this._networkQualitySignaling.on('teardown', () => this._teardownNetworkQualityMonitor());\n }\n\n _teardownNetworkQualityMonitor() {\n if (this._networkQualityMonitor) {\n this._networkQualityMonitor.stop();\n this._networkQualityMonitor = null;\n }\n }\n\n /**\n * Get the {@link RoomV2}'s media statistics.\n * @returns {Promise.>}\n */\n getStats() {\n return this._peerConnectionManager.getStats().then(responses =>\n new Map(Array.from(responses).map(([id, response]) =>\n [id, Object.assign({}, response, {\n localAudioTrackStats: filterAndAddLocalTrackSids(this, response.localAudioTrackStats),\n localVideoTrackStats: filterAndAddLocalTrackSids(this, response.localVideoTrackStats),\n remoteAudioTrackStats: filterAndAddRemoteTrackSids(this, response.remoteAudioTrackStats),\n remoteVideoTrackStats: filterAndAddRemoteTrackSids(this, response.remoteVideoTrackStats)\n })]\n ))\n );\n }\n}\n\n/**\n * Filter out {@link TrackStats} that aren't in the collection while also\n * stamping their Track SIDs.\n * @param {Map} idToSid\n * @param {Array} trackStats\n * @returns {Array}\n */\nfunction filterAndAddTrackSids(idToSid, trackStats) {\n return trackStats.reduce((trackStats, trackStat) => {\n const trackSid = idToSid.get(trackStat.trackId);\n return trackSid\n ? [Object.assign({}, trackStat, { trackSid })].concat(trackStats)\n : trackStats;\n }, []);\n}\n\n/**\n * Filter out {@link LocalTrackStats} that aren't currently published while also\n * stamping their Track SIDs.\n * @param {RoomV2} roomV2\n * @param {Array} localTrackStats\n * @returns {Array}\n */\nfunction filterAndAddLocalTrackSids(roomV2, localTrackStats) {\n return filterAndAddTrackSids(roomV2._published, localTrackStats);\n}\n\n/**\n * Filter out {@link RemoteTrackStats} that aren't currently subscribed while\n * also stamping their Track SIDs.\n * @param {RoomV2} roomV2\n * @param {Array} remoteTrackStats\n * @returns {Array}\n */\nfunction filterAndAddRemoteTrackSids(roomV2, remoteTrackStats) {\n const idToSid = new Map(Array.from(roomV2._subscribed.entries()).map(([sid, id]) => [id, sid]));\n return filterAndAddTrackSids(idToSid, remoteTrackStats);\n}\n\n/**\n * @typedef {object} RoomV2#Representation\n * @property {string} name\n * @property {LocalParticipantV2#Representation} participant\n * @property {?Array} participants\n * @property {?Array} peer_connections\n * @property {?RecordingV2#Representation} recording\n * @property {string} sid\n */\n\nfunction handleLocalParticipantEvents(roomV2, localParticipant) {\n const localParticipantUpdated = oncePerTick(() => {\n roomV2._publishNewLocalParticipantState();\n });\n\n const renegotiate = oncePerTick(() => {\n const trackSenders = flatMap(localParticipant.tracks, trackV2 => trackV2.trackTransceiver);\n roomV2._peerConnectionManager.setTrackSenders(trackSenders);\n });\n\n localParticipant.on('trackAdded', renegotiate);\n localParticipant.on('trackRemoved', renegotiate);\n localParticipant.on('updated', localParticipantUpdated);\n\n roomV2.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n localParticipant.removeListener('trackAdded', renegotiate);\n localParticipant.removeListener('trackRemoved', renegotiate);\n localParticipant.removeListener('updated', localParticipantUpdated);\n roomV2.removeListener('stateChanged', stateChanged);\n localParticipant.disconnect();\n }\n });\n\n roomV2.on('signalingConnectionStateChanged', () => {\n const { localParticipant, signalingConnectionState } = roomV2;\n const { identity, sid } = localParticipant;\n switch (signalingConnectionState) {\n case 'connected':\n localParticipant.connect(sid, identity);\n break;\n case 'reconnecting':\n localParticipant.reconnecting();\n break;\n }\n });\n}\n\nfunction handlePeerConnectionEvents(roomV2, peerConnectionManager) {\n peerConnectionManager.on('description', function onDescription(description) {\n roomV2._publishPeerConnectionState(description);\n });\n peerConnectionManager.dequeue('description');\n\n peerConnectionManager.on('candidates', function onCandidates(candidates) {\n roomV2._publishPeerConnectionState(candidates);\n });\n peerConnectionManager.dequeue('candidates');\n\n peerConnectionManager.on('trackAdded', roomV2._addTrackReceiver.bind(roomV2));\n peerConnectionManager.dequeue('trackAdded');\n peerConnectionManager.getTrackReceivers().forEach(roomV2._addTrackReceiver, roomV2);\n\n peerConnectionManager.on('connectionStateChanged', () => {\n roomV2.emit('connectionStateChanged');\n });\n\n peerConnectionManager.on('iceConnectionStateChanged', () => {\n roomV2.emit('iceConnectionStateChanged');\n if (roomV2.iceConnectionState === 'failed') {\n if (roomV2.localParticipant.networkQualityLevel !== null) {\n roomV2.localParticipant.setNetworkQualityLevel(0);\n }\n roomV2.participants.forEach(participant => {\n if (participant.networkQualityLevel !== null) {\n participant.setNetworkQualityLevel(0);\n }\n });\n }\n });\n}\n\nfunction handleTransportEvents(roomV2, transport) {\n transport.on('message', roomV2._update.bind(roomV2));\n transport.on('stateChanged', function stateChanged(state, error) {\n if (state === 'disconnected') {\n if (roomV2.state !== 'disconnected') {\n roomV2._disconnect(error);\n }\n transport.removeListener('stateChanged', stateChanged);\n }\n roomV2.emit('signalingConnectionStateChanged');\n });\n}\n\n/**\n * Periodically publish {@link StatsReport}s.\n * @private\n * @param {RoomV2} roomV2\n * @param {Transport} transport\n * @param {Number} intervalMs\n */\nfunction periodicallyPublishStats(roomV2, transport, intervalMs) {\n const movingAverageDeltas = new Map();\n let oddPublishCount = false;\n const interval = setInterval(() => {\n roomV2.getStats().then(stats => {\n oddPublishCount = !oddPublishCount;\n stats.forEach((response, id) => {\n // NOTE(mmalavalli): A StatsReport is used to publish a \"stats-report\"\n // event instead of using StandardizedStatsResponse directly because\n // StatsReport will add zeros to properties that do not exist.\n const report = new StatsReport(id, response, true /* prepareForInsights */);\n\n // NOTE(mmalavalli): Since A/V sync metrics are not part of the StatsReport class,\n // we add them to the insights payload here.\n transport.publishEvent('quality', 'stats-report', 'info', {\n audioTrackStats: report.remoteAudioTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToRemoteTrackStats(trackStat, response.remoteAudioTrackStats[i], movingAverageDeltas)),\n localAudioTrackStats: report.localAudioTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToLocalTrackStats(trackStat, response.localAudioTrackStats[i], movingAverageDeltas)),\n localVideoTrackStats: report.localVideoTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToLocalTrackStats(trackStat, response.localVideoTrackStats[i], movingAverageDeltas)),\n peerConnectionId: report.peerConnectionId,\n videoTrackStats: report.remoteVideoTrackStats.map((trackStat, i) =>\n addAVSyncMetricsToRemoteTrackStats(trackStat, response.remoteVideoTrackStats[i], movingAverageDeltas)),\n });\n\n // NOTE(mmalavalli): Clean up entries for Tracks that are no longer published or subscribed to.\n const keys = flatMap([\n 'localAudioTrackStats',\n 'localVideoTrackStats',\n 'remoteAudioTrackStats',\n 'remoteVideoTrackStats'\n ], prop => report[prop].map(({ ssrc, trackSid }) => `${trackSid}+${ssrc}`));\n const movingAverageDeltaKeysToBeRemoved = difference(Array.from(movingAverageDeltas.keys()), keys);\n movingAverageDeltaKeysToBeRemoved.forEach(key => movingAverageDeltas.delete(key));\n\n if (oddPublishCount) {\n // NOTE(mmalavalli): null properties of the \"active-ice-candidate-pair\"\n // payload are assigned default values until the Insights gateway\n // accepts null values.\n const activeIceCandidatePair = replaceNullsWithDefaults(\n response.activeIceCandidatePair,\n report.peerConnectionId);\n\n transport.publishEvent(\n 'quality',\n 'active-ice-candidate-pair',\n 'info',\n activeIceCandidatePair);\n }\n });\n }, () => {\n // Do nothing.\n });\n }, intervalMs);\n\n roomV2.on('stateChanged', function onStateChanged(state) {\n if (state === 'disconnected') {\n clearInterval(interval);\n roomV2.removeListener('stateChanged', onStateChanged);\n }\n });\n}\n\nfunction handleSubscriptions(room) {\n const trackSidsToTrackSignalings = room._getTrackSidsToTrackSignalings();\n\n room._subscriptionFailures.forEach((error, trackSid) => {\n const trackSignaling = trackSidsToTrackSignalings.get(trackSid);\n if (trackSignaling) {\n room._subscriptionFailures.delete(trackSid);\n trackSignaling.subscribeFailed(createTwilioError(error.code, error.message));\n }\n });\n\n trackSidsToTrackSignalings.forEach(trackSignaling => {\n const trackId = room._subscribed.get(trackSignaling.sid);\n if (!trackId || (trackSignaling.isSubscribed && trackSignaling.trackTransceiver.id !== trackId)) {\n trackSignaling.setTrackTransceiver(null);\n }\n if (trackId) {\n room._getTrackReceiver(trackId).then(trackReceiver => trackSignaling.setTrackTransceiver(trackReceiver));\n }\n });\n}\n\n/**\n * NOTE(mmalavalli): Since A/V sync metrics are not part of the public StatsReport class, we add them\n * only for reporting purposes.\n * @private\n */\nfunction addAVSyncMetricsToLocalTrackStats(trackStats, trackResponse, movingAverageDeltas) {\n const {\n framesEncoded,\n packetsSent,\n totalEncodeTime,\n totalPacketSendDelay\n } = trackResponse;\n const augmentedTrackStats = Object.assign({}, trackStats);\n const key = `${trackStats.trackSid}+${trackStats.ssrc}`;\n const trackMovingAverageDeltas = movingAverageDeltas.get(key) || new Map();\n\n if (typeof totalEncodeTime === 'number' && typeof framesEncoded === 'number') {\n const trackAvgEncodeDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgEncodeDelay')\n || new MovingAverageDelta();\n trackAvgEncodeDelayMovingAverageDelta.putSample(totalEncodeTime * 1000, framesEncoded);\n augmentedTrackStats.avgEncodeDelay = Math.round(trackAvgEncodeDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgEncodeDelay', trackAvgEncodeDelayMovingAverageDelta);\n }\n if (typeof totalPacketSendDelay === 'number' && typeof packetsSent === 'number') {\n const trackAvgPacketSendDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgPacketSendDelay')\n || new MovingAverageDelta();\n trackAvgPacketSendDelayMovingAverageDelta.putSample(totalPacketSendDelay * 1000, packetsSent);\n augmentedTrackStats.avgPacketSendDelay = Math.round(trackAvgPacketSendDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgPacketSendDelay', trackAvgPacketSendDelayMovingAverageDelta);\n }\n movingAverageDeltas.set(key, trackMovingAverageDeltas);\n return augmentedTrackStats;\n}\n\n/**\n * NOTE(mmalavalli): Since A/V sync metrics are not part of the public StatsReport class, we add them\n * only for reporting purposes.\n * @private\n */\nfunction addAVSyncMetricsToRemoteTrackStats(trackStats, trackResponse, movingAverageDeltas) {\n const {\n estimatedPlayoutTimestamp,\n framesDecoded,\n jitterBufferDelay,\n jitterBufferEmittedCount,\n totalDecodeTime\n } = trackResponse;\n const augmentedTrackStats = Object.assign({}, trackStats);\n const key = `${trackStats.trackSid}+${trackStats.ssrc}`;\n const trackMovingAverageDeltas = movingAverageDeltas.get(key) || new Map();\n\n if (typeof estimatedPlayoutTimestamp === 'number') {\n augmentedTrackStats.estimatedPlayoutTimestamp = estimatedPlayoutTimestamp;\n }\n if (typeof framesDecoded === 'number' && typeof totalDecodeTime === 'number') {\n const trackAvgDecodeDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgDecodeDelay')\n || new MovingAverageDelta();\n trackAvgDecodeDelayMovingAverageDelta.putSample(totalDecodeTime * 1000, framesDecoded);\n augmentedTrackStats.avgDecodeDelay = Math.round(trackAvgDecodeDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgDecodeDelay', trackAvgDecodeDelayMovingAverageDelta);\n }\n if (typeof jitterBufferDelay === 'number' && typeof jitterBufferEmittedCount === 'number') {\n const trackAvgJitterBufferDelayMovingAverageDelta = trackMovingAverageDeltas.get('avgJitterBufferDelay')\n || new MovingAverageDelta();\n trackAvgJitterBufferDelayMovingAverageDelta.putSample(jitterBufferDelay * 1000, jitterBufferEmittedCount);\n augmentedTrackStats.avgJitterBufferDelay = Math.round(trackAvgJitterBufferDelayMovingAverageDelta.get());\n trackMovingAverageDeltas.set('avgJitterBufferDelay', trackAvgJitterBufferDelayMovingAverageDelta);\n }\n movingAverageDeltas.set(key, trackMovingAverageDeltas);\n return augmentedTrackStats;\n}\n\nfunction replaceNullsWithDefaults(activeIceCandidatePair, peerConnectionId) {\n activeIceCandidatePair = Object.assign({\n availableIncomingBitrate: 0,\n availableOutgoingBitrate: 0,\n bytesReceived: 0,\n bytesSent: 0,\n consentRequestsSent: 0,\n currentRoundTripTime: 0,\n lastPacketReceivedTimestamp: 0,\n lastPacketSentTimestamp: 0,\n nominated: false,\n peerConnectionId: peerConnectionId,\n priority: 0,\n readable: false,\n requestsReceived: 0,\n requestsSent: 0,\n responsesReceived: 0,\n responsesSent: 0,\n retransmissionsReceived: 0,\n retransmissionsSent: 0,\n state: 'failed',\n totalRoundTripTime: 0,\n transportId: '',\n writable: false\n }, filterObject(activeIceCandidatePair || {}, null));\n\n activeIceCandidatePair.localCandidate = Object.assign({\n candidateType: 'host',\n deleted: false,\n ip: '',\n port: 0,\n priority: 0,\n protocol: 'udp',\n url: ''\n }, filterObject(activeIceCandidatePair.localCandidate || {}, null));\n\n activeIceCandidatePair.remoteCandidate = Object.assign({\n candidateType: 'host',\n ip: '',\n port: 0,\n priority: 0,\n protocol: 'udp',\n url: ''\n }, filterObject(activeIceCandidatePair.remoteCandidate || {}, null));\n\n return activeIceCandidatePair;\n}\n\nmodule.exports = RoomV2;\n", "'use strict';\n\nconst StateMachine = require('../../statemachine');\nconst TwilioConnection = require('../../twilioconnection');\nconst DefaultBackoff = require('../../util/backoff');\nconst { reconnectBackoffConfig } = require('../../util/constants');\nconst Timeout = require('../../util/timeout');\nconst { SDK_NAME, SDK_VERSION, SDP_FORMAT } = require('../../util/constants');\n\nconst {\n createBandwidthProfilePayload,\n createMediaSignalingPayload,\n createMediaWarningsPayload,\n createSubscribePayload,\n getUserAgent,\n isNonArrayObject\n} = require('../../util');\n\nconst {\n createTwilioError,\n RoomCompletedError,\n SignalingConnectionError,\n SignalingServerBusyError,\n} = require('../../util/twilio-video-errors');\n\nconst ICE_VERSION = 1;\nconst RSP_VERSION = 2;\n\n/*\nTwilioConnectionTransport States\n----------------\n\n +-----------+\n | |\n | syncing |---------+\n | | |\n +-----------+ |\n ^ | |\n | | |\n | v v\n +------------+ +-----------+ +--------------+\n | | | | | |\n | connecting |--->| connected |--->| disconnected |\n | | | | | |\n +------------+ +-----------+ +--------------+\n | ^\n | |\n | |\n +------------------------------+\n\n*/\n\nconst states = {\n connecting: [\n 'connected',\n 'disconnected'\n ],\n connected: [\n 'disconnected',\n 'syncing'\n ],\n syncing: [\n 'connected',\n 'disconnected'\n ],\n disconnected: []\n};\n\n/**\n * A {@link TwilioConnectionTransport} supports sending and receiving Room Signaling Protocol\n * (RSP) messages. It also supports RSP requests, such as Sync and Disconnect.\n * @extends StateMachine\n * @emits TwilioConnectionTransport#connected\n * @emits TwilioConnectionTransport#message\n */\nclass TwilioConnectionTransport extends StateMachine {\n /**\n * Construct a {@link TwilioConnectionTransport}.\n * @param {?string} name\n * @param {string} accessToken\n * @param {ParticipantSignaling} localParticipant\n * @param {PeerConnectionManager} peerConnectionManager\n * @param {string} wsServer\n * @param {object} [options]\n */\n constructor(name, accessToken, localParticipant, peerConnectionManager, wsServer, options) {\n options = Object.assign({\n Backoff: DefaultBackoff,\n TwilioConnection,\n iceServers: null,\n trackPriority: true,\n trackSwitchOff: true,\n renderHints: true,\n userAgent: getUserAgent()\n }, options);\n super('connecting', states);\n\n\n Object.defineProperties(this, {\n _accessToken: {\n value: accessToken\n },\n _automaticSubscription: {\n value: options.automaticSubscription\n },\n _bandwidthProfile: {\n value: options.bandwidthProfile\n },\n _dominantSpeaker: {\n value: options.dominantSpeaker\n },\n _adaptiveSimulcast: {\n value: options.adaptiveSimulcast\n },\n _eventObserver: {\n value: options.eventObserver,\n writable: false\n },\n _renderHints: {\n value: options.renderHints\n },\n _iceServersStatus: {\n value: Array.isArray(options.iceServers)\n ? 'overrode'\n : 'acquire'\n },\n _localParticipant: {\n value: localParticipant\n },\n _name: {\n value: name,\n },\n _networkQuality: {\n value: isNonArrayObject(options.networkQuality) || options.networkQuality\n },\n _notifyWarnings: {\n value: options.notifyWarnings\n },\n _options: {\n value: options\n },\n _peerConnectionManager: {\n value: peerConnectionManager\n },\n _sessionTimer: {\n value: null,\n writable: true\n },\n _sessionTimeoutMS: {\n value: 0, // initially 0, set only after 1st successful connection.\n writable: true\n },\n _reconnectBackoff: {\n value: new options.Backoff(reconnectBackoffConfig)\n },\n _session: {\n value: null,\n writable: true\n },\n _trackPriority: {\n value: options.trackPriority\n },\n _trackSwitchOff: {\n value: options.trackSwitchOff\n },\n _twilioConnection: {\n value: null,\n writable: true\n },\n _updatesReceived: {\n value: []\n },\n _updatesToSend: {\n value: []\n },\n _userAgent: {\n value: options.userAgent\n },\n _wsServer: {\n value: wsServer\n }\n });\n\n\n setupTransport(this);\n }\n\n /**\n * Create a Connect, Sync or Disconnect RSP message.\n * @private\n * @returns {?object}\n */\n _createConnectOrSyncOrDisconnectMessage() {\n if (this.state === 'connected') {\n return null;\n }\n\n if (this.state === 'disconnected') {\n return {\n session: this._session,\n type: 'disconnect',\n version: RSP_VERSION\n };\n }\n\n const type = {\n connecting: 'connect',\n syncing: 'sync'\n }[this.state];\n\n const message = {\n name: this._name,\n participant: this._localParticipant.getState(),\n peer_connections: this._peerConnectionManager.getStates(),\n type,\n version: RSP_VERSION\n };\n\n if (message.type === 'connect') {\n message.ice_servers = this._iceServersStatus;\n\n message.publisher = {\n name: SDK_NAME,\n sdk_version: SDK_VERSION,\n user_agent: this._userAgent\n };\n\n if (this._bandwidthProfile) {\n message.bandwidth_profile = createBandwidthProfilePayload(\n this._bandwidthProfile);\n }\n\n if (this._notifyWarnings) {\n message.participant.media_warnings = createMediaWarningsPayload(\n this._notifyWarnings);\n }\n\n message.media_signaling = createMediaSignalingPayload(\n this._dominantSpeaker,\n this._networkQuality,\n this._trackPriority,\n this._trackSwitchOff,\n this._adaptiveSimulcast,\n this._renderHints);\n\n message.subscribe = createSubscribePayload(this._automaticSubscription);\n message.format = SDP_FORMAT;\n message.token = this._accessToken;\n } else if (message.type === 'sync') {\n message.session = this._session;\n message.token = this._accessToken;\n } else if (message.type === 'update') {\n message.session = this._session;\n }\n\n return message;\n }\n\n /**\n * Create an \"ice\" message.\n * @private\n */\n _createIceMessage() {\n return {\n edge: 'roaming', // roaming here means use same edge as signaling.\n token: this._accessToken,\n type: 'ice',\n version: ICE_VERSION\n };\n }\n\n /**\n * Send a Connect, Sync or Disconnect RSP message.\n * @private\n */\n _sendConnectOrSyncOrDisconnectMessage() {\n const message = this._createConnectOrSyncOrDisconnectMessage();\n if (message) {\n this._twilioConnection.sendMessage(message);\n }\n }\n\n /**\n * Disconnect the {@link TwilioConnectionTransport}. Returns true if calling the method resulted\n * in disconnection.\n * @param {TwilioError} [error]\n * @returns {boolean}\n */\n disconnect(error) {\n if (this.state !== 'disconnected') {\n this.preempt('disconnected', null, [error]);\n this._sendConnectOrSyncOrDisconnectMessage();\n this._twilioConnection.close();\n return true;\n }\n return false;\n }\n\n /**\n * Publish an RSP Update. Returns true if calling the method resulted in\n * publishing (or eventually publishing) the update.\n * @param {object} update\n * @returns {boolean}\n */\n publish(update) {\n switch (this.state) {\n case 'connected':\n this._twilioConnection.sendMessage(Object.assign({\n session: this._session,\n type: 'update',\n version: RSP_VERSION\n }, update));\n return true;\n case 'connecting':\n case 'syncing':\n this._updatesToSend.push(update);\n return true;\n case 'disconnected':\n default:\n return false;\n }\n }\n\n /**\n * Publish (or queue) an event to the Insights gateway.\n * @param {string} group - Event group name\n * @param {string} name - Event name\n * @param {string} level - Event level\n * @param {object} payload - Event payload\n * @returns {void}\n */\n publishEvent(group, name, level, payload) {\n this._eventObserver.emit('event', { group, name, level, payload });\n }\n\n /**\n * Sync the {@link TwilioConnectionTransport}. Returns true if calling the method resulted in\n * syncing.\n * @returns {boolean}\n */\n sync() {\n if (this.state === 'connected') {\n this.preempt('syncing');\n this._sendConnectOrSyncOrDisconnectMessage();\n return true;\n }\n return false;\n }\n\n /**\n * @private\n * @returns {void}\n */\n _setSession(session, sessionTimeout) {\n this._session = session;\n this._sessionTimeoutMS = sessionTimeout * 1000;\n }\n\n /**\n * Determines if we should attempt reconnect.\n * returns a Promise to wait on before attempting to\n * reconnect. returns null if its not okay to reconnect.\n * @private\n * @returns {Promise}\n */\n _getReconnectTimer() {\n if (this._sessionTimeoutMS === 0) {\n // this means either we have never connected.\n // or we timed out while trying to reconnect\n // In either case we do not want to reconnect.\n return null;\n }\n\n // start session timer\n if (!this._sessionTimer) {\n this._sessionTimer = new Timeout(() => {\n // ensure that _clearReconnectTimer wasn't\n // called while we were waiting.\n if (this._sessionTimer) {\n // do not allow any more reconnect attempts.\n this._sessionTimeoutMS = 0;\n }\n }, this._sessionTimeoutMS);\n }\n\n // return promise that waits with exponential backoff.\n return new Promise(resolve => {\n this._reconnectBackoff.backoff(resolve);\n });\n }\n\n /**\n * clears the session reconnect timer.\n *\n * @private\n * @returns {void}\n */\n _clearReconnectTimer() {\n this._reconnectBackoff.reset();\n if (this._sessionTimer) {\n this._sessionTimer.clear();\n this._sessionTimer = null;\n }\n }\n}\n\n/**\n * @event TwilioConnectionTransport#connected\n * @param {object} initialState\n */\n\n/**\n * @event TwilioConnectionTransport#message\n * @param {object} peerConnections\n */\n\nfunction reducePeerConnections(peerConnections) {\n return Array.from(peerConnections.reduce((peerConnectionsById, update) => {\n const reduced = peerConnectionsById.get(update.id) || update;\n\n // First, reduce the top-level `description` property.\n if (!reduced.description && update.description) {\n reduced.description = update.description;\n } else if (reduced.description && update.description) {\n if (update.description.revision > reduced.description.revision) {\n reduced.description = update.description;\n }\n }\n\n // Then, reduce the top-level `ice` property.\n if (!reduced.ice && update.ice) {\n reduced.ice = update.ice;\n } else if (reduced.ice && update.ice) {\n if (update.ice.revision > reduced.ice.revision) {\n reduced.ice = update.ice;\n }\n }\n\n // Finally, update the map.\n peerConnectionsById.set(reduced.id, reduced);\n return peerConnectionsById;\n }, new Map()).values());\n}\n\nfunction reduceUpdates(updates) {\n return updates.reduce((reduced, update) => {\n // First, reduce the top-level `participant` property.\n if (!reduced.participant && update.participant) {\n reduced.participant = update.participant;\n } else if (reduced.participant && update.participant) {\n if (update.participant.revision > reduced.participant.revision) {\n reduced.participant = update.participant;\n }\n }\n\n // Then, reduce the top-level `peer_connections` property.\n /* eslint camelcase:0 */\n if (!reduced.peer_connections && update.peer_connections) {\n reduced.peer_connections = reducePeerConnections(update.peer_connections);\n } else if (reduced.peer_connections && update.peer_connections) {\n reduced.peer_connections = reducePeerConnections(\n reduced.peer_connections.concat(update.peer_connections));\n }\n return reduced;\n }, {});\n}\n\nfunction setupTransport(transport) {\n function createOrResetTwilioConnection() {\n if (transport.state === 'disconnected') {\n return;\n }\n if (transport._twilioConnection) {\n transport._twilioConnection.removeListener('message', handleMessage);\n }\n const { _iceServersStatus, _options, _wsServer, state } = transport;\n const { TwilioConnection } = _options;\n\n const twilioConnection = new TwilioConnection(_wsServer, Object.assign({\n helloBody: state === 'connecting' && _iceServersStatus === 'acquire'\n ? transport._createIceMessage()\n : transport._createConnectOrSyncOrDisconnectMessage()\n }, _options));\n\n twilioConnection.once('close', reason => {\n if (reason === TwilioConnection.CloseReason.LOCAL) {\n disconnect();\n } else {\n disconnect(new Error(reason));\n }\n });\n\n twilioConnection.on('message', handleMessage);\n transport._twilioConnection = twilioConnection;\n }\n\n function disconnect(error) {\n if (transport.state === 'disconnected') {\n return;\n }\n if (!error) {\n transport.disconnect();\n return;\n }\n\n const reconnectTimer = transport._getReconnectTimer();\n if (!reconnectTimer) {\n const twilioError = error.message === TwilioConnection.CloseReason.BUSY\n ? new SignalingServerBusyError()\n : new SignalingConnectionError();\n transport.disconnect(twilioError);\n return;\n }\n\n if (transport.state === 'connected') {\n transport.preempt('syncing');\n }\n\n reconnectTimer.then(createOrResetTwilioConnection);\n }\n\n function handleMessage(message) {\n if (transport.state === 'disconnected') {\n return;\n }\n if (message.type === 'error') {\n transport.disconnect(createTwilioError(message.code, message.message));\n return;\n }\n switch (transport.state) {\n case 'connected':\n switch (message.type) {\n case 'connected':\n case 'synced':\n case 'update':\n case 'warning':\n transport.emit('message', message);\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n case 'connecting':\n switch (message.type) {\n case 'iced':\n transport._options.onIced(message.ice_servers).then(() => {\n transport._sendConnectOrSyncOrDisconnectMessage();\n });\n return;\n case 'connected':\n transport._setSession(message.session, message.options.session_timeout);\n transport.emit('connected', message);\n transport.preempt('connected');\n return;\n case 'synced':\n case 'update':\n transport._updatesReceived.push(message);\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n case 'syncing':\n switch (message.type) {\n case 'connected':\n case 'update':\n transport._updatesReceived.push(message);\n return;\n case 'synced':\n transport._clearReconnectTimer();\n transport.emit('message', message);\n transport.preempt('connected');\n return;\n case 'disconnected':\n transport.disconnect(message.status === 'completed'\n ? new RoomCompletedError()\n : null);\n return;\n default:\n // Do nothing.\n return;\n }\n default:\n // Impossible\n return;\n }\n }\n\n transport.on('stateChanged', function stateChanged(state) {\n switch (state) {\n case 'connected': {\n const updates = transport._updatesToSend.splice(0);\n if (updates.length) {\n transport.publish(reduceUpdates(updates));\n }\n transport._updatesReceived.splice(0).forEach(update => transport.emit('message', update));\n return;\n }\n case 'disconnected':\n transport._twilioConnection.removeListener('message', handleMessage);\n transport.removeListener('stateChanged', stateChanged);\n return;\n case 'syncing':\n // Do nothing.\n return;\n default:\n // Impossible\n return;\n }\n });\n\n const { _options, _iceServersStatus } = transport;\n const { iceServers, onIced } = _options;\n\n if (_iceServersStatus === 'overrode') {\n onIced(iceServers).then(createOrResetTwilioConnection);\n } else {\n createOrResetTwilioConnection();\n }\n}\n\nmodule.exports = TwilioConnectionTransport;\n", "'use strict';\n\nconst CancelablePromise = require('../../util/cancelablepromise');\nconst DefaultPeerConnectionManager = require('./peerconnectionmanager');\nconst DefaultRoomV2 = require('./room');\nconst DefaultTransport = require('./twilioconnectiontransport');\n\nconst {\n SignalingConnectionDisconnectedError,\n SignalingIncomingMessageInvalidError\n} = require('../../util/twilio-video-errors');\n\nconst { flatMap, createRoomConnectEventPayload } = require('../../util');\n\nfunction createCancelableRoomSignalingPromise(token, wsServer, localParticipant, encodingParameters, preferredCodecs, options) {\n options = Object.assign({\n PeerConnectionManager: DefaultPeerConnectionManager,\n RoomV2: DefaultRoomV2,\n Transport: DefaultTransport\n }, options);\n\n const adaptiveSimulcast = preferredCodecs.video[0] && preferredCodecs.video[0].adaptiveSimulcast === true;\n const { PeerConnectionManager, RoomV2, Transport, iceServers, log } = options;\n const peerConnectionManager = new PeerConnectionManager(encodingParameters, preferredCodecs, options);\n const trackSenders = flatMap(localParticipant.tracks, trackV2 => [trackV2.trackTransceiver]);\n peerConnectionManager.setTrackSenders(trackSenders);\n\n const cancellationError = new Error('Canceled');\n\n let transport;\n\n const cancelablePromise = new CancelablePromise((resolve, reject, isCanceled) => {\n const onIced = iceServers => {\n if (isCanceled()) {\n reject(cancellationError);\n return Promise.reject(cancellationError);\n }\n log.debug('Got ICE servers:', iceServers);\n options.iceServers = iceServers;\n peerConnectionManager.setConfiguration(options);\n\n return peerConnectionManager.createAndOffer().then(() => {\n if (isCanceled()) {\n reject(cancellationError);\n throw cancellationError;\n }\n log.debug('createAndOffer() succeeded.');\n // NOTE(mmalavalli): PeerConnectionManager#createAndOffer() queues the\n // initial offer in the event queue for the 'description' event. So,\n // we are dequeueing to prevent the spurious 'update' message sent by\n // the client after connecting to a room.\n peerConnectionManager.dequeue('description');\n }).catch(error => {\n log.error('createAndOffer() failed:', error);\n reject(error);\n throw error;\n });\n };\n\n const {\n automaticSubscription,\n bandwidthProfile,\n dominantSpeaker,\n environment,\n eventObserver,\n loggerName,\n logLevel,\n name,\n networkMonitor,\n networkQuality,\n notifyWarnings,\n realm,\n sdpSemantics,\n } = options;\n\n // decide which msp channels to request\n // dominantSpeaker, networkQuality\n const trackPriority = !!bandwidthProfile;\n const trackSwitchOff = !!bandwidthProfile;\n const renderHints = !!bandwidthProfile &&\n (options.clientTrackSwitchOffControl !== 'disabled' || options.contentPreferencesMode !== 'disabled');\n\n const transportOptions = Object.assign({\n adaptiveSimulcast,\n automaticSubscription,\n dominantSpeaker,\n environment,\n eventObserver,\n loggerName,\n logLevel,\n networkMonitor,\n networkQuality,\n notifyWarnings,\n iceServers,\n onIced,\n realm,\n renderHints,\n sdpSemantics,\n trackPriority,\n trackSwitchOff\n }, bandwidthProfile ? {\n bandwidthProfile\n } : {});\n\n transport = new Transport(\n name,\n token,\n localParticipant,\n peerConnectionManager,\n wsServer,\n transportOptions);\n\n const connectEventPayload = createRoomConnectEventPayload(options);\n eventObserver.emit('event', connectEventPayload);\n\n transport.once('connected', initialState => {\n log.debug('Transport connected:', initialState);\n if (isCanceled()) {\n reject(cancellationError);\n return;\n }\n const { participant: localParticipantState } = initialState;\n if (!localParticipantState) {\n reject(new SignalingIncomingMessageInvalidError());\n return;\n }\n resolve(new RoomV2(localParticipant, initialState, transport, peerConnectionManager, options));\n });\n\n transport.once('stateChanged', (state, error) => {\n if (state === 'disconnected') {\n transport = null;\n reject(error || new SignalingConnectionDisconnectedError());\n } else {\n log.debug('Transport state changed:', state);\n }\n });\n }, () => {\n if (transport) {\n transport.disconnect();\n transport = null;\n }\n });\n\n cancelablePromise.catch(() => {\n if (transport) {\n transport.disconnect();\n transport = null;\n }\n peerConnectionManager.close();\n });\n\n return cancelablePromise;\n}\n\nmodule.exports = createCancelableRoomSignalingPromise;\n", "'use strict';\n\nconst ParticipantSignaling = require('./participant');\n\nclass LocalParticipantSignaling extends ParticipantSignaling {\n constructor() {\n super();\n Object.defineProperties(this, {\n _publicationsToTrackSenders: {\n value: new Map()\n },\n _trackSendersToPublications: {\n value: new Map()\n }\n });\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @returns {LocalTrackPublicationSignaling} publication\n */\n addTrack(trackSender, name, priority, noiseCancellationVendor = null) {\n const publication = this._createLocalTrackPublicationSignaling(trackSender, name, priority, noiseCancellationVendor);\n this._trackSendersToPublications.set(trackSender, publication);\n this._publicationsToTrackSenders.set(publication, trackSender);\n super.addTrack(publication);\n return this;\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationSignaling}\n */\n getPublication(trackSender) {\n return this._trackSendersToPublications.get(trackSender) || null;\n }\n\n /**\n * @param {LocalTrackPublicationSignaling} trackPublication\n * @returns {?DataTrackSender|MediaTrackSender}\n */\n getSender(trackPublication) {\n return this._publicationsToTrackSenders.get(trackPublication) || null;\n }\n\n /**\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationSignaling}\n */\n removeTrack(trackSender) {\n const publication = this._trackSendersToPublications.get(trackSender);\n if (!publication) {\n return null;\n }\n this._trackSendersToPublications.delete(trackSender);\n this._publicationsToTrackSenders.delete(publication);\n const didDelete = super.removeTrack(publication);\n if (didDelete) {\n publication.stop();\n }\n return publication;\n }\n}\n\nmodule.exports = LocalParticipantSignaling;\n", "'use strict';\n\nconst TrackSignaling = require('./track');\n\n/**\n * A {@link LocalTrackPublication} implementation\n * @extends TrackSignaling\n * @property {Track.ID} id\n */\nclass LocalTrackPublicationSignaling extends TrackSignaling {\n /**\n * Construct a {@link LocalTrackPublicationSignaling}. {@link TrackSenders}\n * are always cloned.\n * @param {DataTrackSender|MediaTrackSender} trackSender - the {@link TrackSender}\n * of the {@link LocalTrack} to be published\n * @param {string} name - the name of the {@link LocalTrack} to be published\n * @param {Track.Priority} priority - initial {@link Track.Priority}\n */\n constructor(trackSender, name, priority) {\n trackSender = trackSender.clone();\n const enabled = trackSender.kind === 'data' ? true : trackSender.track.enabled;\n super(name, trackSender.kind, enabled, priority);\n this.setTrackTransceiver(trackSender);\n Object.defineProperties(this, {\n _updatedPriority: {\n value: priority,\n writable: true\n },\n id: {\n enumerable: true,\n value: trackSender.id\n }\n });\n }\n\n /**\n * The updated {@link Track.Priority} of the {@link LocalTrack}.\n * @property {Track.priority}\n */\n get updatedPriority() {\n return this._updatedPriority;\n }\n\n /**\n * Enable (or disable) the {@link LocalTrackPublicationSignaling} if it is not\n * already enabled (or disabled). This also updates the cloned\n * {@link MediaTrackSender}'s MediaStreamTracks `enabled` state.\n * @param {boolean} [enabled=true]\n * @return {this}\n */\n enable(enabled) {\n enabled = typeof enabled === 'boolean' ? enabled : true;\n this.trackTransceiver.track.enabled = enabled;\n return super.enable(enabled);\n }\n\n /**\n * Rejects the SID's deferred promise with the given Error.\n * @param {Error} error\n * @returns {this}\n */\n publishFailed(error) {\n if (setError(this, error)) {\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Update the {@link Track.Priority} of the published {@link LocalTrack}.\n * @param {Track.priority} priority\n * @returns {this}\n */\n setPriority(priority) {\n if (this._updatedPriority !== priority) {\n this._updatedPriority = priority;\n this.emit('updated');\n }\n return this;\n }\n\n /**\n * Set the published {@link LocalTrack}'s {@link Track.SID}.\n * @param {Track.SID} sid\n * @returns {this}\n */\n setSid(sid) {\n if (this._error) {\n return this;\n }\n return super.setSid.call(this, sid);\n }\n\n /**\n * Stop the cloned {@link TrackSender}.\n * @returns {void}\n */\n stop() {\n this.trackTransceiver.stop();\n }\n}\n\n/**\n * @param {LocalTrackPublication} publication\n * @param {Error} error\n * @returns {boolean} updated\n */\nfunction setError(publication, error) {\n if (publication._sid !== null || publication._error) {\n return false;\n }\n publication._error = error;\n return true;\n}\n\nmodule.exports = LocalTrackPublicationSignaling;\n", "'use strict';\n\nconst LocalTrackPublicationSignaling = require('../localtrackpublication');\nconst TwilioWarning = require('../../util/twiliowarning');\nconst createTwilioError = require('../../util/twilio-video-errors').createTwilioError;\n\n/**\n * @extends LocalTrackPublicationSignaling\n */\nclass LocalTrackPublicationV2 extends LocalTrackPublicationSignaling {\n /**\n * Construct a {@link LocalTrackPublicationV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @param {object} [options]\n */\n constructor(trackSender, name, priority, noiseCancellationVendor, options) {\n super(trackSender, name, priority);\n\n Object.defineProperties(this, {\n _log: {\n value: options.log.createLog('default', this)\n },\n _mediaStates: {\n value: { recordings: null },\n writable: true\n },\n _noiseCancellationVendor: {\n value: noiseCancellationVendor,\n }\n });\n }\n\n /**\n * Get the {@link LocalTrackPublicationV2#Representation} of a given {@link TrackSignaling}.\n * @returns {LocalTrackPublicationV2#Representation} - without the SID\n */\n getState() {\n const state = {\n enabled: this.isEnabled,\n id: this.id,\n kind: this.kind,\n name: this.name,\n priority: this.updatedPriority,\n };\n\n if (this._noiseCancellationVendor) {\n // eslint-disable-next-line camelcase\n state.audio_processor = this._noiseCancellationVendor;\n }\n return state;\n }\n\n toString() {\n return `[LocalTrackPublicationV2: ${this.sid}]`;\n }\n\n /**\n * Compare the {@link LocalTrackPublicationV2} to a {@link LocalTrackPublicationV2#Representation} of itself\n * and perform any updates necessary.\n * @param {PublishedTrack} track\n * @returns {this}\n * @fires TrackSignaling#updated\n */\n update(track) {\n switch (track.state) {\n case 'ready':\n this.setSid(track.sid);\n break;\n case 'failed': {\n const error = track.error;\n this.publishFailed(createTwilioError(error.code, error.message));\n break;\n }\n default: // 'created'\n break;\n }\n return this;\n }\n\n updateMediaStates(mediaStates) {\n if (!mediaStates || !mediaStates.recordings ||\n this._mediaStates.recordings === mediaStates.recordings) {\n return this;\n }\n this._mediaStates.recordings = mediaStates.recordings;\n switch (this._mediaStates.recordings) {\n case 'OK':\n this._log.info('Warnings have cleared.');\n this.emit('warningsCleared');\n break;\n case 'NO_MEDIA':\n this._log.warn('Recording media lost.');\n this.emit('warning', TwilioWarning.recordingMediaLost);\n break;\n default:\n this._log.warn(`Unknown media state detected: ${this._mediaStates.recordings}`);\n break;\n }\n return this;\n }\n}\n\n/**\n * The Room Signaling Protocol (RSP) representation of a {@link LocalTrackPublicationV2}.\n * @typedef {object} LocalTrackPublicationV2#Representation\n * @property {boolean} enabled\n * @property {Track.ID} id\n * @property {Track.Kind} kind\n * @property {string} name\n * @priority {Track.Priority} priority\n * @property {Track.SID} sid\n */\n\nmodule.exports = LocalTrackPublicationV2;\n", "'use strict';\n\nconst LocalParticipantSignaling = require('../localparticipant');\nconst LocalTrackPublicationV2 = require('./localtrackpublication');\nconst { DEFAULT_LOG_LEVEL } = require('../../util/constants');\nconst Log = require('../../util/log');\nconst { buildLogLevels, isDeepEqual } = require('../../util');\n\n/**\n * @extends ParticipantSignaling\n * @property {BandwidthProfileOptions} bandwidthProfile\n * @property {NetworkQualityConfigurationImpl} networkQualityConfiguration\n * @property {number} revision\n * @emits LocalParticipantV2#updated\n */\nclass LocalParticipantV2 extends LocalParticipantSignaling {\n /**\n * Construct a {@link LocalParticipantV2}.\n * @param {EncodingParametersImpl} encodingParameters\n * @param {NetworkQualityConfigurationImpl} networkQualityConfiguration\n * @param {object} [options]\n */\n constructor(encodingParameters, networkQualityConfiguration, options) {\n options = Object.assign({\n logLevel: DEFAULT_LOG_LEVEL,\n LocalTrackPublicationV2\n }, options);\n\n super();\n\n const logLevels = buildLogLevels(options.logLevel);\n\n Object.defineProperties(this, {\n _bandwidthProfile: {\n value: null,\n writable: true\n },\n _bandwidthProfileRevision: {\n value: 0,\n writable: true\n },\n _encodingParameters: {\n value: encodingParameters\n },\n _removeListeners: {\n value: new Map()\n },\n _LocalTrackPublicationV2: {\n value: options.LocalTrackPublicationV2\n },\n _log: {\n value: options.log\n ? options.log.createLog('default', this)\n : new Log('default', this, logLevels, options.loggerName)\n },\n _publishedRevision: {\n writable: true,\n value: 0\n },\n _revision: {\n writable: true,\n value: 1\n },\n _signalingRegion: {\n value: null,\n writable: true\n },\n audioProcessors: {\n value: [],\n writable: true\n },\n bandwidthProfile: {\n enumerable: true,\n get() {\n return this._bandwidthProfile;\n }\n },\n bandwidthProfileRevision: {\n enumerable: true,\n get() {\n return this._bandwidthProfileRevision;\n }\n },\n networkQualityConfiguration: {\n enumerable: true,\n value: networkQualityConfiguration\n },\n revision: {\n enumerable: true,\n get() {\n return this._revision;\n }\n },\n signalingRegion: {\n enumerable: true,\n get() {\n return this._signalingRegion;\n }\n }\n });\n }\n\n toString() {\n return `[LocalParticipantSignaling: ${this.sid}]`;\n }\n\n /**\n * Set the signalingRegion.\n * @param {string} signalingRegion.\n */\n setSignalingRegion(signalingRegion) {\n if (!this._signalingRegion) {\n this._signalingRegion = signalingRegion;\n }\n }\n\n /**\n * Update the {@link BandwidthProfileOptions}.\n * @param {BandwidthProfileOptions} bandwidthProfile\n */\n setBandwidthProfile(bandwidthProfile) {\n if (!isDeepEqual(this._bandwidthProfile, bandwidthProfile)) {\n // NOTE(mmalavalli): Object.assign() copies the values of only\n // the top level properties. In order to deep copy the object, we\n // stringify and parse the object.\n this._bandwidthProfile = JSON.parse(JSON.stringify(bandwidthProfile));\n this._bandwidthProfileRevision++;\n this.didUpdate();\n }\n }\n\n /**\n * Sets the AudioProcessors enabled for this room.\n * @param {string[]} audioProcessors\n */\n setAudioProcessors(audioProcessors) {\n this.audioProcessors = audioProcessors;\n }\n\n /**\n * returns current {@link EncodingParametersImpl}.\n * @returns {EncodingParametersImpl}\n */\n getParameters() {\n return this._encodingParameters;\n }\n\n /**\n * Set the {@link EncodingParameters}.\n * @param {?EncodingParameters} encodingParameters\n * @returns {this}\n */\n setParameters(encodingParameters) {\n this._encodingParameters.update(encodingParameters);\n return this;\n }\n\n /**\n * Update the {@link LocalParticipantV2} with the new state.\n * @param {Published} published\n * @returns {this}\n */\n update(published) {\n if (this._publishedRevision >= published.revision) {\n return this;\n }\n\n this._publishedRevision = published.revision;\n\n published.tracks.forEach(function(publicationState) {\n const localTrackPublicationV2 = this.tracks.get(publicationState.id);\n if (localTrackPublicationV2) {\n localTrackPublicationV2.update(publicationState);\n }\n }, this);\n\n return this;\n }\n\n updateMediaStates(mediaStates) {\n if (!mediaStates || !mediaStates.tracks) {\n return this;\n }\n\n Array.from(this.tracks.values()).forEach(publication => {\n const states = mediaStates.tracks[publication.sid];\n if (states) {\n publication.updateMediaStates(states);\n }\n });\n return this;\n }\n\n /**\n * @protected\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @param {?NoiseCancellationVendor} noiseCancellationVendor\n * @returns {LocalTrackPublicationV2}\n */\n _createLocalTrackPublicationSignaling(trackSender, name, priority, noiseCancellationVendor) {\n return new this._LocalTrackPublicationV2(trackSender, name, priority, noiseCancellationVendor, { log: this._log });\n }\n\n /**\n * Add a {@link LocalTrackPublicationV2} for the given {@link DataTrackSender}\n * or {@link MediaTrackSender} to the {@link LocalParticipantV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @param {string} name\n * @param {Track.Priority} priority\n * @returns {this}\n */\n addTrack(trackSender, name, priority, noiseCancellationVendor) {\n super.addTrack(trackSender, name, priority, noiseCancellationVendor);\n const publication = this.getPublication(trackSender);\n\n let {\n isEnabled,\n updatedPriority\n } = publication;\n\n const updated = () => {\n // NOTE(mmalavalli): The LocalParticipantV2's state is only published if\n // the \"updated\" event is emitted due to LocalTrackPublicationV2's\n // .isEnabled or .updatedPriority being changed. We do not publish if it is fired due to the\n // LocalTrackPublicationV2's .sid being set.\n if (isEnabled !== publication.isEnabled || updatedPriority !== publication.updatedPriority) {\n this.didUpdate();\n isEnabled = publication.isEnabled;\n updatedPriority = publication.updatedPriority;\n }\n };\n\n publication.on('updated', updated);\n\n this._removeListener(publication);\n this._removeListeners.set(publication, () => publication.removeListener('updated', updated));\n\n this.didUpdate();\n\n return this;\n }\n\n /**\n * @private\n * @param {LocalTrackPublicationV2} publication\n * @returns {void}\n */\n _removeListener(publication) {\n const removeListener = this._removeListeners.get(publication);\n if (removeListener) {\n removeListener();\n }\n }\n\n /**\n * Get the current state of the {@link LocalParticipantV2}.\n * @returns {object}\n */\n getState() {\n return {\n revision: this.revision,\n tracks: Array.from(this.tracks.values()).map(track => track.getState())\n };\n }\n\n /**\n * Increment the revision for the {@link LocalParticipantV2}.\n * @private\n * @returns {void}\n */\n didUpdate() {\n this._revision++;\n this.emit('updated');\n }\n\n /**\n * Remove the {@link LocalTrackPublicationV2} for the given {@link DataTrackSender}\n * or {@link MediaTrackSender} from the {@link LocalParticipantV2}.\n * @param {DataTrackSender|MediaTrackSender} trackSender\n * @returns {?LocalTrackPublicationV2}\n */\n removeTrack(trackSender) {\n const publication = super.removeTrack(trackSender);\n if (publication) {\n trackSender.removeClone(publication.trackTransceiver);\n this._removeListener(publication);\n this.didUpdate();\n }\n return publication;\n }\n\n /**\n * Updates the verbosity of network quality information.\n * @param {NetworkQualityConfiguration} networkQualityConfiguration\n * @returns {void}\n */\n setNetworkQualityConfiguration(networkQualityConfiguration) {\n this.networkQualityConfiguration.update(networkQualityConfiguration);\n }\n\n /**\n * updates encodings for simulcast layers.\n * @param {Track.SID} trackSid\n * @param {Array<{enabled: boolean, layer_index: number}>} encodings\n * @returns {Promise} string indicating result of the operation. can be one of\n * \"OK\", \"INVALID_HINT\", \"COULD_NOT_APPLY_HINT\", \"UNKNOWN_TRACK\"\n */\n setPublisherHint(trackSid, encodings) {\n const trackSignaling = Array.from(this.tracks.values()).find(trackPub => trackPub.sid === trackSid);\n if (!trackSignaling) {\n this._log.warn(`track:${trackSid} not found`);\n return Promise.resolve('UNKNOWN_TRACK');\n }\n return trackSignaling.trackTransceiver.setPublisherHint(encodings);\n }\n}\n\n\n/**\n * @interface Published\n * @property {number} revision\n * @property {Array} tracks\n */\n\n/**\n * @typedef {CreatedTrack|ReadyTrack|FailedTrack} PublishedTrack\n */\n\n/**\n * @interface CreatedTrack\n * @property {Track.ID} id\n * @property {string} state - \"created\"\n */\n\n/**\n * @interface ReadyTrack\n * @property {Track.ID} id\n * @property {Track.SID} sid\n * @property {string} state - \"ready\"\n */\n\n/**\n * @interface FailedTrack\n * @property {Track.ID} id\n * @property {TrackError} error\n * @property {string} state - \"failed\"\n */\n\n/**\n * @interface TrackError\n * @property {number} code\n * @property {string} message\n */\n\n/**\n * @event LocalParticipantV2#updated\n */\n\nmodule.exports = LocalParticipantV2;\n", "/* eslint consistent-return:0 */\n'use strict';\n\nconst ParticipantSignaling = require('./participant');\nconst RoomSignaling = require('./room');\nconst StateMachine = require('../statemachine');\n\n/*\nSignaling States\n----------------\n\n +---------+\n | |\n | opening |\n +--->| |\n | +---------+\n +--------+ | | +------+\n | |<--+ +-->| |\n | closed |<----------| open |\n | |<--+ +-->| |\n +--------+ | | +------+\n +---------+ |\n | |<--+\n | closing |\n | |\n +---------+\n\n*/\n\nconst states = {\n closed: [\n 'opening'\n ],\n opening: [\n 'closed',\n 'open'\n ],\n open: [\n 'closed',\n 'closing'\n ],\n closing: [\n 'closed',\n 'open'\n ]\n};\n\n/**\n * @extends StateMachine\n * @property {string} state - one of \"closed\", \"opening\", \"open\", or \"closing\"\n */\nclass Signaling extends StateMachine {\n /**\n * Construct {@link Signaling}.\n */\n constructor() {\n super('closed', states);\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _close(key) {\n this.transition('closing', key);\n this.transition('closed', key);\n return Promise.resolve(this);\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n localParticipant.connect('PA00000000000000000000000000000000', 'test');\n const sid = 'RM00000000000000000000000000000000';\n const promise = Promise.resolve(new RoomSignaling(localParticipant, sid, options));\n promise.cancel = function cancel() {};\n return promise;\n }\n\n /**\n * @private\n */\n // NOTE(mroberts): This is a dummy implementation suitable for testing.\n _open(key) {\n this.transition('opening', key);\n this.transition('open', key);\n return Promise.resolve(this);\n }\n\n /**\n * Close the {@link Signaling}.\n * @returns {Promise}\n */\n close() {\n return this.bracket('close', key => {\n switch (this.state) {\n case 'closed':\n return this;\n case 'open':\n return this._close(key);\n default:\n throw new Error(`Unexpected Signaling state \"${this.state}\"`);\n }\n });\n }\n\n /**\n * Connect to a {@link RoomSignaling}.\n * @param {ParticipantSignaling} localParticipant\n * @param {string} token\n * @param {EncodingParametersImpl} encodingParameters\n * @param {PreferredCodecs} preferredCodecs\n * @param {object} options\n * @returns {Promise>}\n */\n connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n const self = this;\n return this.bracket('connect', function transition(key) {\n switch (self.state) {\n case 'closed':\n return self._open(key).then(transition.bind(null, key));\n case 'open':\n // NOTE(mroberts): We don't need to hold the lock in _connect. Instead,\n // we just need to ensure the Signaling remains open.\n self.releaseLockCompletely(key);\n return self._connect(localParticipant, token, encodingParameters, preferredCodecs, options);\n default:\n throw new Error(`Unexpected Signaling state \"${self.state}\"`);\n }\n });\n }\n\n /**\n * Create a local {@link ParticipantSignaling}.\n * @returns {ParticipantSignaling}\n */\n createLocalParticipantSignaling() {\n return new ParticipantSignaling();\n }\n\n /**\n * Open the {@link Signaling}.\n * @returns {Promise}\n */\n open() {\n return this.bracket('open', key => {\n switch (this.state) {\n case 'closed':\n return this._open(key);\n case 'open':\n return this;\n default:\n throw new Error(`Unexpected Signaling state \"${this.state}\"`);\n }\n });\n }\n}\n\nmodule.exports = Signaling;\n", "'use strict';\n\nconst defaultCreateCancelableRoomSignalingPromise = require('./cancelableroomsignalingpromise');\nconst LocalParticipantV2 = require('./localparticipant');\nconst Signaling = require('../');\n\n/**\n * {@link SignalingV2} implements version 2 of our signaling protocol.\n * @extends Signaling\n */\nclass SignalingV2 extends Signaling {\n /**\n * Construct {@link SignalingV2}.\n * @param {string} wsServer\n * @param {?object} [options={}]\n */\n constructor(wsServer, options) {\n /* eslint new-cap:0 */\n options = Object.assign({\n createCancelableRoomSignalingPromise: defaultCreateCancelableRoomSignalingPromise\n }, options);\n\n super();\n\n Object.defineProperties(this, {\n _createCancelableRoomSignalingPromise: {\n value: options.createCancelableRoomSignalingPromise\n },\n _options: {\n value: options\n },\n _wsServer: {\n value: wsServer\n }\n });\n }\n\n /**\n * @private\n */\n _connect(\n localParticipant,\n token,\n encodingParameters,\n preferredCodecs,\n options\n ) {\n options = Object.assign({}, this._options, options);\n return this._createCancelableRoomSignalingPromise.bind(\n null,\n token,\n this._wsServer,\n localParticipant,\n encodingParameters,\n preferredCodecs,\n options);\n }\n\n createLocalParticipantSignaling(encodingParameters, networkQualityConfiguration) {\n return new LocalParticipantV2(encodingParameters, networkQualityConfiguration);\n }\n}\n\nmodule.exports = SignalingV2;\n", "'use strict';\n\nconst { MediaStreamTrack } = require('./webrtc');\nconst { guessBrowser, guessBrowserVersion, isCodecSupported } = require('./webrtc/util');\nconst createCancelableRoomPromise = require('./cancelableroompromise');\nconst EncodingParametersImpl = require('./encodingparameters');\nconst LocalParticipant = require('./localparticipant');\nconst InsightsPublisher = require('./util/insightspublisher');\nconst NullInsightsPublisher = require('./util/insightspublisher/null');\n\nconst {\n LocalAudioTrack,\n LocalDataTrack,\n LocalVideoTrack\n} = require('./media/track/es5');\n\nconst NetworkQualityConfigurationImpl = require('./networkqualityconfiguration');\nconst Room = require('./room');\nconst SignalingV2 = require('./signaling/v2');\n\nconst {\n asLocalTrack,\n buildLogLevels,\n filterObject,\n isNonArrayObject\n} = require('./util');\n\nconst {\n DEFAULT_ENVIRONMENT,\n DEFAULT_LOG_LEVEL,\n DEFAULT_LOGGER_NAME,\n DEFAULT_REALM,\n DEFAULT_REGION,\n WS_SERVER,\n SDK_NAME,\n SDK_VERSION,\n typeErrors: E\n} = require('./util/constants');\n\nconst CancelablePromise = require('./util/cancelablepromise');\nconst EventObserver = require('./util/eventobserver');\nconst DefaultLog = require('./util/log');\nconst { validateBandwidthProfile } = require('./util/validate');\n\nconst safariVersion = guessBrowser() === 'safari' && guessBrowserVersion();\n\n// This is used to make out which connect() call a particular Log statement\n// belongs to. Each call to connect() increments this counter.\nlet connectCalls = 0;\n\nlet didPrintSafariWarning = false;\nlet isSafariWithoutVP8Support = false;\n\nif (safariVersion) {\n const { major: safariMajorVersion, minor: safariMinorVersion } = safariVersion;\n isSafariWithoutVP8Support = safariMajorVersion < 12 || (safariMajorVersion === 12 && safariMinorVersion < 1);\n}\n\nconst deprecatedConnectOptionsProps = new Set([\n { didWarn: false, shouldDelete: true, name: 'abortOnIceServersTimeout' },\n { didWarn: false, shouldDelete: true, name: 'dscpTagging', newName: 'enableDscp' },\n { didWarn: false, shouldDelete: true, name: 'iceServersTimeout' },\n { didWarn: false, shouldDelete: false, name: 'eventListener', newName: 'Video.Logger' },\n { didWarn: false, shouldDelete: false, name: 'logLevel', newName: 'Video.Logger' },\n]);\n\nconst deprecatedBandwidthProfileOptions = new Set([\n { didWarn: false, shouldDelete: false, name: 'maxTracks', newName: 'bandwidthProfile.video.clientTrackSwitchOffControl' },\n { didWarn: false, shouldDelete: false, name: 'renderDimensions', newName: 'bandwidthProfile.video.contentPreferencesMode' },\n]);\n\n/**\n * Connect to a {@link Room}.\n *

\n * By default, this will automatically acquire an array containing a\n * {@link LocalAudioTrack} and {@link LocalVideoTrack} before connecting to\n * the {@link Room}. These will be stopped when you disconnect from the\n * {@link Room}.\n *

\n * You can override the default behavior by specifying\n * options. For example, rather than acquiring a\n * {@link LocalAudioTrack} and {@link LocalVideoTrack} automatically, you can\n * pass your own array which you can stop yourself. See {@link ConnectOptions}\n * for more information.\n * @alias module:twilio-video.connect\n * @param {string} token - The Access Token string\n * @param {ConnectOptions} [options] - Options to override the default behavior, invalid options are ignored.\n * @returns {CancelablePromise}\n * @throws {RangeError}\n * @throws {TwilioError}\n * @throws {TypeError}\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n * Video.connect(token, {\n * name: 'my-cool-room'\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with audio-only\n * Video.connect(token, {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n *\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with media acquired using getUserMedia()\n * navigator.mediaDevices.getUserMedia({\n * audio: true,\n * video: true\n * }).then(function(mediaStream) {\n * return Video.connect(token, {\n * name: 'my-cool-room',\n * tracks: mediaStream.getTracks()\n * });\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n *\n * room.once('disconnected', function() {\n * console.log('You left the Room:', room.name);\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * var Video = require('twilio-video');\n * var token = getAccessToken();\n *\n * // Connect with custom names for LocalAudioTrack and LocalVideoTrack\n * Video.connect(token, {\n * name: 'my-cool-room'\n * audio: { name: 'microphone' },\n * video: { name: 'camera' }\n * }).then(function(room) {\n * room.localParticipants.trackPublications.forEach(function(publication) {\n * console.log('The LocalTrack \"' + publication.trackName + '\" was successfully published');\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n * @example\n * // Accessing the SDK logger\n * var { Logger, connect } = require('twilio-video');\n * var token = getAccessToken();\n *\n * var logger = Logger.getLogger('twilio-video');\n *\n * // Listen for logs\n * var originalFactory = logger.methodFactory;\n * logger.methodFactory = function (methodName, logLevel, loggerName) {\n * var method = originalFactory(methodName, logLevel, loggerName);\n *\n * return function (datetime, logLevel, component, message, data) {\n * method(datetime, logLevel, component, message, data);\n * // Send to your own server\n * postDataToServer(arguments);\n * };\n * };\n * logger.setLevel('debug');\n *\n * connect(token, {\n * name: 'my-cool-room'\n * }).then(function(room) {\n * room.on('participantConnected', function(participant) {\n * console.log(participant.identity + ' has connected');\n * });\n * }).catch(error => {\n * console.log('Could not connect to the Room:', error.message);\n * });\n */\nfunction connect(token, options) {\n if (typeof options === 'undefined') {\n options = {};\n }\n if (!isNonArrayObject(options)) {\n return CancelablePromise.reject(E.INVALID_TYPE('options', 'object'));\n }\n\n const Log = options.Log || DefaultLog;\n const loggerName = options.loggerName || DEFAULT_LOGGER_NAME;\n const logLevel = options.logLevel || DEFAULT_LOG_LEVEL;\n const logLevels = buildLogLevels(logLevel);\n const logComponentName = `[connect #${++connectCalls}]`;\n\n let log;\n try {\n log = new Log('default', logComponentName, logLevels, loggerName);\n } catch (error) {\n return CancelablePromise.reject(error);\n }\n\n // NOTE(csantos): Log a warning for the deprecated ConnectOptions properties.\n // The warning is displayed only for the first call to connect() per browser session.\n // Additionally, the options that are no longer needed will be removed.\n deprecateOptions(options, log, deprecatedConnectOptionsProps);\n\n const adaptiveSimulcast = options.preferredVideoCodecs === 'auto';\n if (adaptiveSimulcast) {\n // NOTE(mpatwardhan): enable adaptiveSimulcast.\n options.preferredVideoCodecs = [{ codec: 'VP8', simulcast: true, adaptiveSimulcast: true }];\n }\n\n if (options.maxVideoBitrate && adaptiveSimulcast) {\n log.error('ConnectOptions \"maxVideoBitrate\" is not compatible with \"preferredVideoCodecs=auto\"');\n return CancelablePromise.reject(E.ILLEGAL_INVOKE('connect',\n 'ConnectOptions \"maxVideoBitrate\" is not compatible with \"preferredVideoCodecs=auto\"'));\n }\n\n options = Object.assign({\n automaticSubscription: true,\n dominantSpeaker: false,\n enableDscp: false,\n environment: DEFAULT_ENVIRONMENT,\n eventListener: null,\n insights: true,\n LocalAudioTrack,\n LocalDataTrack,\n LocalParticipant,\n LocalVideoTrack,\n Log,\n MediaStreamTrack,\n loggerName,\n logLevel,\n maxAudioBitrate: null,\n maxVideoBitrate: null,\n name: null,\n networkMonitor: true,\n networkQuality: false,\n preferredAudioCodecs: [],\n preferredVideoCodecs: [],\n realm: DEFAULT_REALM,\n region: DEFAULT_REGION,\n signaling: SignalingV2\n }, filterObject(options));\n\n /* eslint new-cap:0 */\n const eventPublisherOptions = {};\n if (typeof options.wsServerInsights === 'string') {\n eventPublisherOptions.gateway = options.wsServerInsights;\n }\n const EventPublisher = options.insights ? InsightsPublisher : NullInsightsPublisher;\n const eventPublisher = new EventPublisher(\n token,\n SDK_NAME,\n SDK_VERSION,\n options.environment,\n options.realm,\n eventPublisherOptions);\n\n const wsServer = WS_SERVER(options.environment, options.region);\n const eventObserver = new EventObserver(eventPublisher, Date.now(), log, options.eventListener);\n options = Object.assign({ eventObserver, wsServer }, options);\n options.log = log;\n\n // NOTE(mroberts): Print the Safari warning once if the log-level is at least\n // \"warn\", i.e. neither \"error\" nor \"off\".\n // NOTE(mmalavalli): Print the Safari warning only for versions 12.0 and below.\n if (isSafariWithoutVP8Support\n && !didPrintSafariWarning\n && (log.logLevel !== 'error' && log.logLevel !== 'off')) {\n didPrintSafariWarning = true;\n log.warn([\n 'Support for Safari 12.0 and below is limited because it does not support VP8.',\n 'This means you may experience codec issues in Group Rooms. You may also',\n 'experience codec issues in Peer-to-Peer (P2P) Rooms containing Android- or',\n 'iOS-based Participants who do not support H.264. However, P2P Rooms',\n 'with browser-based Participants should work. For more information, please',\n 'refer to this guide: https://www.twilio.com/docs/video/javascript-v2-developing-safari-11'\n ].join(' '));\n }\n\n if (typeof token !== 'string') {\n return CancelablePromise.reject(E.INVALID_TYPE('token', 'string'));\n }\n\n // NOTE(mmalavalli): The Room \"name\" in \"options\" was being used\n // as the LocalTrack name in asLocalTrack(). So we pass a copy of\n // \"options\" without the \"name\".\n const localTrackOptions = Object.assign({}, options);\n delete localTrackOptions.name;\n\n if ('tracks' in options) {\n if (!Array.isArray(options.tracks)) {\n return CancelablePromise.reject(E.INVALID_TYPE('options.tracks',\n 'Array of LocalAudioTrack, LocalVideoTrack or MediaStreamTrack'));\n }\n try {\n options.tracks = options.tracks.map(track => asLocalTrack(track, localTrackOptions));\n } catch (error) {\n return CancelablePromise.reject(error);\n }\n }\n\n const error = validateBandwidthProfile(options.bandwidthProfile);\n if (error) {\n return CancelablePromise.reject(error);\n }\n\n // Note(mpatwardhan): \"clientTrackSwitchOffControl\" allows tracks to be switched off\n // and \"contentPreferencesMode\" allows track dimensions to be specified dynamically.\n // The properties can have one of the three values internally:\n // 1) \"auto\" = sdk will decide and send the hints.\n // 2) \"manual\" - app can use api to send the hints.\n // 3) \"disabled\" = do not enable this feature. (this is internal only value)\n // 'disabled' is needed because clientTrackSwitchOffControl and contentPreferencesMode are incompatible with\n // deprecated properties maxTracks and renderDimensions respectively. once we make @breaking_version_change\n // we can remove 'disabled' state along with maxTracks and renderDimensions.\n options.clientTrackSwitchOffControl = 'disabled'; // should sdk turn off idle tracks automatically?\n options.contentPreferencesMode = 'disabled'; // should sdk use video element dimensions for content hints?\n if (options.bandwidthProfile) {\n options.clientTrackSwitchOffControl = 'auto';\n options.contentPreferencesMode = 'auto';\n if (options.bandwidthProfile.video) {\n\n // log any warnings about deprecated bwp options\n deprecateOptions(options.bandwidthProfile.video, log, deprecatedBandwidthProfileOptions);\n\n if ('maxTracks' in options.bandwidthProfile.video) {\n // when deprecated maxTracks is specified. disable clientTrackSwitchOffControl\n options.clientTrackSwitchOffControl = 'disabled';\n } else if (options.bandwidthProfile.video.clientTrackSwitchOffControl === 'manual') {\n options.clientTrackSwitchOffControl = 'manual';\n } else {\n options.clientTrackSwitchOffControl = 'auto';\n }\n\n if ('renderDimensions' in options.bandwidthProfile.video) {\n options.contentPreferencesMode = 'disabled';\n } else if (options.bandwidthProfile.video.contentPreferencesMode === 'manual') {\n options.contentPreferencesMode = 'manual';\n } else {\n options.contentPreferencesMode = 'auto';\n }\n }\n }\n\n const Signaling = options.signaling;\n const signaling = new Signaling(options.wsServer, options);\n\n log.info('Connecting to a Room');\n log.debug('Options:', options);\n\n const encodingParameters = new EncodingParametersImpl({\n maxAudioBitrate: options.maxAudioBitrate,\n maxVideoBitrate: options.maxVideoBitrate\n }, adaptiveSimulcast);\n\n const preferredCodecs = {\n audio: options.preferredAudioCodecs.map(normalizeCodecSettings),\n video: options.preferredVideoCodecs.map(normalizeCodecSettings)\n };\n\n const networkQualityConfiguration = new NetworkQualityConfigurationImpl(\n isNonArrayObject(options.networkQuality) ? options.networkQuality : {}\n );\n\n // Log warnings for any unsupported preferred codecs.\n ['audio', 'video'].forEach(\n kind => preferredCodecs[kind].forEach(\n ({ codec }) => isCodecSupported(codec, kind).then(\n isSupported => !isSupported && log.warn(\n `The preferred ${kind} codec \"${codec}\" will be ignored as it is not supported by the browser.`\n )\n )\n )\n );\n\n // Create a CancelableRoomPromise that resolves after these steps:\n // 1 - Get the LocalTracks.\n // 2 - Create the LocalParticipant using options.tracks.\n // 3 - Connect to rtc-room-service and create the RoomSignaling.\n // 4 - Create the Room and then resolve the CancelablePromise.\n const cancelableRoomPromise = createCancelableRoomPromise(\n getLocalTracks.bind(null, options),\n createLocalParticipant.bind(null, signaling, log, encodingParameters, networkQualityConfiguration, options),\n createRoomSignaling.bind(null, token, options, signaling, encodingParameters, preferredCodecs),\n createRoom.bind(null, options));\n\n cancelableRoomPromise.then(room => {\n eventPublisher.connect(room.sid, room.localParticipant.sid);\n log.info('Connected to Room:', room.toString());\n log.info('Room name:', room.name);\n log.debug('Room:', room);\n room.once('disconnected', () => eventPublisher.disconnect());\n return room;\n }, error => {\n eventPublisher.disconnect();\n if (cancelableRoomPromise._isCanceled) {\n log.info('Attempt to connect to a Room was canceled');\n } else {\n log.info('Error while connecting to a Room:', error);\n }\n });\n\n return cancelableRoomPromise;\n}\n\n/**\n * You may pass these options to {@link connect} in order to override the\n * default behavior.\n * @typedef {object} ConnectOptions\n * @property {boolean|CreateLocalTracksOptions|CreateLocalAudioTrackOptions} [audio=true] - Whether or not to\n * get local audio with getUserMedia when tracks\n * are not provided.\n * @property {boolean} [automaticSubscription=true] - By default, you will subscribe\n * to all RemoteTracks shared by other Participants in a Room. You can now override this\n * behavior by setting this flag to false. It will make sure that you will\n * not subscribe to any RemoteTrack in a Group or Small Group Room. Setting it to\n * true, or not setting it at all preserves the default behavior. This\n * flag does not have any effect in a Peer-to-Peer Room.\n * @property {BandwidthProfileOptions} [bandwidthProfile] - You can optionally configure\n * how your available downlink bandwidth is shared among the RemoteTracks you have subscribed\n * to in a Group Room. By default, bandwidth is shared equally among the RemoteTracks.\n * This has no effect in Peer-to-Peer Rooms.\n * @property {boolean} [dominantSpeaker=false] - Whether to enable the Dominant\n * Speaker API or not. This only takes effect in Group Rooms.\n * @property {boolean} [dscpTagging=false] - (deprecated: use \"enableDscp\" instead)\n * DSCP tagging allows you to request enhanced QoS treatment for RTP media packets from any\n * firewall that the client may be behind. Setting this option to true will\n * request DSCP tagging for media packets on supported browsers (only Chrome supports this\n * as of now). Audio packets will be sent with DSCP header value set to 0xb8 which corresponds\n * to Expedited Forwarding (EF). Video packets will be sent with DSCP header value set to 0x88\n * which corresponds to Assured Forwarding (AF41).\n * @property {boolean} [enableDscp=false] - DSCP tagging allows you to request enhanced\n * QoS treatment for RTP media packets from any firewall that the client may be behind.\n * Setting this option to true will request DSCP tagging for media packets\n * on supported browsers (only Chrome supports this as of now). Audio packets will be\n * sent with DSCP header value set to 0xb8 which corresponds to Expedited Forwarding (EF).\n * Video packets will be sent with DSCP header value set to 0x88 which corresponds to\n * Assured Forwarding (AF41).\n * @property {EventListener} [eventListener] - (deprecated: use [Video.Logger](module-twilio-video.html)\n * you can listen to fine-grained events related to signaling and media that are\n * not available in the public APIs. These events might be useful for your own reporting\n * and diagnostics.\n * @property {Array} iceServers - Override the STUN and TURN\n * servers used when connecting to {@link Room}s\n * @property {RTCIceTransportPolicy} [iceTransportPolicy=\"all\"] - Override the\n * ICE transport policy to be one of \"relay\" or \"all\"\n * @property {boolean} [insights=true] - Whether publishing events\n * to the Insights gateway is enabled or not\n * @property {?number} [maxAudioBitrate=null] - Max outgoing audio bitrate (bps);\n * A null or a 0 value does not set any bitrate limit;\n * This value is set as a hint for variable bitrate codecs, but will not take\n * effect for fixed bitrate codecs; Based on our tests, Chrome, Firefox and Safari\n * support a bitrate range of 12000 bps to 256000 bps for Opus codec; This parameter\n * has no effect on iSAC, PCMU and PCMA codecs\n * @property {?number} [maxVideoBitrate=null] - Max outgoing video bitrate (bps);\n * A null or 0 value does not set any bitrate limit;\n * This value is set as a hint for variable bitrate codecs, but will not take\n * effect for fixed bitrate codecs; Based on our tests, Chrome, Firefox and Safari\n * all seem to support an average bitrate range of 20000 bps (20 kbps) to\n * 8000000 bps (8 mbps) for a 720p VideoTrack\n * This parameter must not be set when when preferredVideoCodecs is set to `auto`.\n * @property {?string} [name=null] - Set to connect to a {@link Room} by name\n * @property {boolean|NetworkQualityConfiguration} [networkQuality=false] - Whether to enable the Network\n * Quality API or not. This only takes effect in Group Rooms. Pass a {@link NetworkQualityConfiguration}\n * to configure verbosity levels for network quality information for {@link LocalParticipant}\n * and {@link RemoteParticipant}s. A true value will set the {@link NetworkQualityVerbosity}\n * for the {@link LocalParticipant} to {@link NetworkQualityVerbosity}#minimal\n * and the {@link NetworkQualityVerbosity} for {@link RemoteParticipant}s to\n * {@link NetworkQualityVerbosity}#none.\n * @property {Array} [notifyWarnings=[]] - The SDK raises warning events when it\n * detects certain conditions. You can implement callbacks on these events to act on them, or to alert\n * the user of an issue. Subsequently, \"warningsCleared\" event is raised when conditions have returned\n * to normal. You can listen to these events by specifying an array of warning. By default,\n * this array is empty and no warning events will be raised.\n * Possible values include recording-media-lost, which is raised when the media server\n * has not detected any media on the published track that is being recorded in the past 30 seconds.\n * This usually happens when there are network interruptions or when the track has stopped.\n * This warning is raised by {@link LocalTrackPublication}, {@link LocalParticipant}, and {@link Room} object.\n * @property {string} [region='gll'] - Preferred signaling region; By default, you will be connected to the\n * nearest signaling server determined by latency based routing. Setting a value other\n * than gll bypasses routing and guarantees that signaling traffic will be\n * terminated in the region that you prefer. Please refer to this table\n * for the list of supported signaling regions.\n * @property {Array} [preferredAudioCodecs=[]] - Preferred audio codecs;\n * An empty array preserves the current audio codec preference order.\n * @property {Array|VideoEncodingMode} [preferredVideoCodecs=[]] -\n * Preferred video codecs; when set to 'VideoEncodingMode.Auto', SDK manages the video codec,\n * by preferring VP8 simulcast in group rooms. It also enables adaptive simulcast, which allows SDK\n * to turn off simulcast layers that are not needed for efficient bandwidth and CPU usage.\n * An empty array preserves the current video codec.\n * preference order. If you want to set a preferred video codec on a Group Room,\n * you will need to create the Room using the REST API and set the\n * VideoCodecs property.\n * See \n * here for more information.\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {Array} [tracks] - The\n * {@link LocalTrack}s or MediaStreamTracks with which to join the\n * {@link Room}. These tracks can be obtained either by calling\n * {@link createLocalTracks}, or by constructing them from the MediaStream\n * obtained by calling getUserMedia().\n * @property {boolean|CreateLocalTrackOptions} [video=true] - Whether or not to\n * get local video with getUserMedia when tracks\n * are not provided.\n */\n\n/**\n * {@link BandwidthProfileOptions} allows you to configure how your available downlink\n * bandwidth is shared among the RemoteTracks you have subscribed to in a Group Room.\n * @typedef {object} BandwidthProfileOptions\n * @property {VideoBandwidthProfileOptions} [video] - Optional parameter to configure\n * how your available downlink bandwidth is shared among the {@link RemoteVideoTrack}s you\n * have subscribed to in a Group Room.\n */\n\n/**\n * {@link VideoBandwidthProfileOptions} allows you to configure how your available downlink\n * bandwidth is shared among the {@link RemoteVideoTrack}s you have subscribed to in a Group Room.\n * @typedef {object} VideoBandwidthProfileOptions\n * @property {Track.Priority} [dominantSpeakerPriority=\"standard\"] - Optional parameter to\n * specify the minimum subscribe {@link Track.Priority} of the Dominant Speaker's {@link RemoteVideoTrack}s.\n * This means that the Dominant Speaker's {@link RemoteVideoTrack}s that are published with\n * lower {@link Track.Priority} will be subscribed to with the {@link Track.Priority} specified here.\n * This has no effect on {@link RemoteVideoTrack}s published with higher {@link Track.Priority}, which will\n * still be subscribed to with with the same {@link Track.Priority}. If not specified, this defaults to \"standard\".\n * This parameter only applies to a Group Room Participant when {@link ConnectOptions}.dominantSpeaker is set to true.\n * @property {number} [maxSubscriptionBitrate] - Optional parameter to specify the maximum\n * downlink video bandwidth in bits per second (bps). By default, there are no limits on\n * the downlink video bandwidth.\n * @property {ClientTrackSwitchOffControl} [clientTrackSwitchOffControl=\"auto\"] - Optional parameter that determines\n * when to turn the {@link RemoteVideoTrack} on or off. When set to \"auto\", SDK will use the visibility of the\n * attached elements to determine if the {@link RemoteVideoTrack} should be turned off or on. When the attached video elements become invisible the {@link RemoteVideoTrack} will\n * be turned off, and when elements become visible they will be turned on. When set to \"manual\" you can turn the {@link RemoteVideoTrack}\n * on and off using the api {@link RemoteVideoTrack#switchOn} and {@link RemoteVideoTrack#switchOff} respectively.\n * @property {VideoContentPreferencesMode} [contentPreferencesMode=\"auto\"] - This Optional parameter configures\n * the mode for specifying content preferences for the {@link RemoteVideoTrack}. When set to \"auto\" the\n * SDK determines the render dimensions by inspecting the attached video elements. {@link RemoteVideoTrack}s rendered in smaller video elements\n * will receive a lower resolution stream compared to the video rendered in larger video elements. When set to \"manual\" you can set\n * the dimensions programmatically by calling {@link RemoteVideoTrack#setContentPreferences}.\n * @property {number} [maxTracks] - (deprecated: use \"clientTrackSwitchOffControl\" instead). Optional\n * parameter to specify the maximum number of visible {@link RemoteVideoTrack}s, which will be selected based on\n * {@link Track.Priority} and an N-Loudest policy. By default there are no limits on the number of visible {@link RemoteVideoTrack}s.\n * 0 or a negative value will remove any limit on the maximum number of visible {@link RemoteVideoTrack}s.\n * @property {BandwidthProfileMode} [mode=\"grid\"] - Optional parameter to specify how the {@link RemoteVideoTrack}s'\n * TrackPriority values are mapped to bandwidth allocation in Group Rooms. This defaults to \"grid\",\n * which results in equal bandwidth share allocation to all {@link RemoteVideoTrack}s.\n * @property {VideoRenderDimensions} [renderDimensions] - (deprecated: use \"contentPreferencesMode\" instead). Optional\n * parameter to specify the desired render dimensions of {@link RemoteVideoTrack}s.\n * @property {TrackSwitchOffMode} [trackSwitchOffMode=\"predicted\"] - Optional parameter to configure\n * how {@link RemoteVideoTrack}s are switched off in response to bandwidth pressure. Defaults to \"predicted\".\n */\n\n/**\n * @deprecated\n * {@link VideoRenderDimensions} allows you to specify the desired render dimensions of {@link RemoteVideoTrack}s.\n * You can specify 'auto' for this field - which is also default value - based on {@link Track.Priority}. The bandwidth allocation algorithm will distribute the available downlink bandwidth\n * proportional to the requested render dimensions. This is just an input for calculating the bandwidth to be allocated\n * and does not affect the actual resolution of the {@link RemoteVideoTrack}s.\n * @typedef {object} VideoRenderDimensions\n * @property {VideoTrack.Dimensions} [high] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"high\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 1280 x 720 (HD).\n * @property {VideoTrack.Dimensions} [low] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"low\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 176 x 144 (QCIF).\n * @property {VideoTrack.Dimensions} [standard] - Optional parameter to specify the desired rendering dimensions of\n * {@link RemoteVideoTrack} whose {@link Track.Priority} is \"standard\". 0 or a negative value will result in the lowest\n * possible resolution. This defaults to 640 x 480 (VGA).\n */\n\n/**\n * Configure verbosity levels for network quality information for\n * {@link LocalParticipant} and {@link RemoteParticipant}s.\n * @typedef {object} NetworkQualityConfiguration\n * @property {NetworkQualityVerbosity} [local=1] - Verbosity level for {@link LocalParticipant}\n * @property {NetworkQualityVerbosity} [remote=0] - Verbosity level for {@link RemoteParticipant}s\n */\n\n/**\n * You may pass these levels to {@link ConnectOptions} to override\n * log levels for individual components.\n * @typedef {object} LogLevels\n * @property {LogLevel} [default='warn'] - Log level for 'default' modules.\n * @property {LogLevel} [media='warn'] - Log level for 'media' modules.\n * @property {LogLevel} [signaling='warn'] - Log level for 'signaling' modules.\n * @property {LogLevel} [webrtc='warn'] - Log level for 'webrtc' modules.\n */\n\n/**\n * Audio codec settings.\n * @typedef {object} AudioCodecSettings\n * @property {AudioCodec} codec - Audio codec name\n */\n\n/**\n * Opus codec settings.\n * @typedef {AudioCodecSettings} OpusCodecSettings\n * @property {AudioCodec} name - \"opus\"\n * @property {boolean} [dtx=true] - Enable/disable discontinuous transmission (DTX);\n * If enabled all published {@link LocalAudioTrack}s will reduce the outgoing bitrate\n * to near-zero whenever speech is not detected, resulting in bandwidth and CPU savings;\n * It defaults to true.\n */\n\n/**\n * Video codec settings.\n * @typedef {object} VideoCodecSettings\n * @property {VideoCodec} codec - Video codec name\n */\n\n/**\n * VP8 codec settings.\n * @typedef {VideoCodecSettings} VP8CodecSettings\n * @property {VideoCodec} name - \"VP8\"\n * @property {boolean} [simulcast=false] - Enable/disable VP8 simulcast; If\n * enabled, Twilio's Video SDK will send three video streams of different\n * qualities\n */\n\n/**\n * Names of the supported audio codecs.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst AudioCodec = {\n isac: 'isac',\n opus: 'opus',\n PCMA: 'PCMA',\n PCMU: 'PCMU'\n};\n\n/**\n * Names of the supported VideoEncodingMode.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoEncodingMode = {\n Auto: 'auto',\n};\n\n\n/**\n * Names of the supported video codecs.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoCodec = {\n H264: 'H264',\n VP8: 'VP8'\n};\n// VP9 is supported by most browsers, but backend doesn't at the moment.\n// Hide it from public documentation until then.\nVideoCodec.VP9 = 'VP9';\n\n/**\n * Levels for logging verbosity.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst LogLevel = {\n debug: 'debug',\n info: 'info',\n warn: 'warn',\n error: 'error',\n off: 'off'\n};\n\n/**\n * The verbosity level of network quality information of a {@link Participant}.\n * @enum {number}\n */\n// eslint-disable-next-line\nconst NetworkQualityVerbosity = {\n /**\n * Nothing is reported for the {@link Participant}. This has no effect and\n * defaults to {@link NetworkQualityVerbosity}#minimal\n * for the {@link LocalParticipant}.\n */\n none: 0,\n /**\n * Reports {@link NetworkQualityLevel} for the {@link Participant}.\n */\n minimal: 1,\n /**\n * Reports {@link NetworkQualityLevel} and {@link NetworkQualityStats} for the {@link Participant}.\n * {@link NetworkQualityStats} is populated with audio and video {@link NetworkQualityLevel}s\n * based on which the {@link Participant}'s {@link NetworkQualityLevel} is calculated.\n */\n moderate: 2,\n /**\n * Reports {@link NetworkQualityLevel} and {@link NetworkQualityStats} for the {@link Participant}.\n * {@link NetworkQualityStats} is populated with audio and Video {@link NetworkQualityLevel}s\n * and their corresponding {@link NetworkQualityMediaStats} based on which the\n * {@link Participant}'s {@link NetworkQualityLevel} is calculated.\n */\n detailed: 3\n};\n\n\n/**\n * {@link TrackSwitchOffMode} specifies when {@link RemoteVideoTrack}s' are switched off.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst TrackSwitchOffMode = {\n /**\n * In this mode, {@link RemoteVideoTrack}s are switched off only when network congestion\n * is detected.\n */\n detected: 'detected',\n\n /**\n * In this mode, {@link RemoteVideoTrack}s are pro-actively switched off when network\n * congestion is predicted by the bandwidth estimation mechanism.\n */\n predicted: 'predicted',\n\n /**\n * In this mode, {@link RemoteVideoTrack}s are not switched off. Instead in response to network\n * congestion, tracks will be adjusted to lower quality.\n */\n disabled: 'disabled'\n};\n\n/**\n * {@link BandwidthProfileMode} specifies how {@link RemoteVideoTrack}s' {@link Track.Priority} values\n * are mapped to bandwidth allocation in Group Rooms.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst BandwidthProfileMode = {\n /**\n * This mode is for use cases where all the subscribed {@link RemoteVideoTrack}s are\n * equally important. The bandwidth allocation algorithm will share the available\n * downlink bandwidth equally among the subscribed {@link RemoteVideoTrack}s, irrespective\n * of their {@link Track.Priority}. In case of insufficient downlink bandwidth, the lower\n * priority {@link RemoteVideoTrack}s are switched off.\n */\n grid: 'grid',\n /**\n * This mode is for use cases where some {@link RemoteVideoTrack}s are prioritized more than\n * others. However, the lower priority {@link RemoteVideoTrack}s still need to be visible.\n * The bandwidth allocation algorithm will share the available downlink bandwidth proportional\n * to the requested {@link VideoRenderDimensions} corresponding to their {@link Track.Priority}.\n * In case of insufficient downlink bandwidth, the quality of higher priority {@link RemoteVideoTrack}s\n * may be degraded to avoid switching off lower priority {@link RemoteVideoTrack}s.\n */\n collaboration: 'collaboration',\n /**\n * This mode is for use cases where some {@link RemoteVideoTrack}s are deemed critical and must\n * be preserved at any cost over the other {@link RemoteVideoTrack}s. The bandwidth allocation\n * algorithm will allocate as big a share of the available downlink bandwidth as it possibly\n * can to the higher priority {@link RemoteVideoTrack}s, and only then consider the lower priority\n * {@link RemoteVideoTrack}s. In case of insufficient downlink bandwidth, the lower priority\n * {@link RemoteVideoTrack}s are switched off in order to preserve the quality of the higher\n * priority {@link RemoteVideoTrack}s.\n */\n presentation: 'presentation'\n};\n\n/**\n * {@link VideoContentPreferencesMode} specifies how {@link RemoteVideoTrack}s' render dimensions are\n * decided by the SDK.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst VideoContentPreferencesMode = {\n /**\n * when set to auto, SDK uses the sizes of the video elements attached to the to the {@link RemoteVideoTrack} dynamically to\n * decide the render dimensions. {@link RemoteVideoTrack}s rendered in smaller video elements will be given smaller bandwidth allocation\n * compared to the tracks rendered in large video elements.\n */\n auto: 'auto',\n /**\n * When set to manual, application can use {@link RemoteVideoTrack#setContentPreference} to set the\n * desired render dimensions for the {@link RemoteVideoTrack}.\n */\n manual: 'manual'\n};\n\n\n/**\n * {@link ClientTrackSwitchOffControl} specifies how {@link RemoteVideoTrack}s' turned on and off\n * @enum {string}\n */\n// eslint-disable-next-line\nconst ClientTrackSwitchOffControl = {\n /**\n * when set to auto, SDK uses the visibility of the video elements attached to the to the {@link RemoteVideoTrack} to decide.\n * on turning tracks on or off. The track that are not attached to any video elements or not visible on the screen will be turned\n * off automatically.\n */\n auto: 'auto',\n\n /**\n * When set to manual, application can use {@link RemoteVideoTrack}s switchOff and switchOn apis to control turn the track on or off.\n */\n manual: 'manual'\n};\n\n\n/**\n * Names of the supported levels for {@link EventListenerEvent}s.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst EventListenerLevel = {\n debug: 'debug',\n error: 'error',\n info: 'info',\n warning: 'warning'\n};\n\n/**\n * Names of the supported groups for {@link EventListenerEvent}s.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst EventListenerGroup = {\n /**\n * Events associated with the connection to Twilio's signaling server\n */\n signaling: 'signaling'\n};\n\n/**\n * An {@link EventListener} allows you to listen to fine-grained {@link EventListenerEvent}s related\n * to signaling and media that are not available in the public APIs, which might be useful for your own\n * reporting and diagnostics.\n * @typedef {EventEmitter} EventListener\n * @example\n * const { EventEmitter } = require('events');\n * const { connect } = require('twilio-video');\n *\n * const eventListener = new EventEmitter();\n * eventListener.on('event', function(event) {\n * console.log('The SDK raised an event:', event);\n * });\n *\n * connect('token', {\n * eventListener: eventListener\n * });\n */\n\n/**\n * The SDK raised an {@link EventListenerEvent}.\n * @event EventListener#event\n * @param {EventListenerEvent} event - Context about the event raised by the SDK.\n * This can be one of the following:\n * * {@link EventListenerClosedEvent}\n * * {@link EventListenerConnectingEvent}\n * * {@link EventListenerEarlyEvent}\n * * {@link EventListenerOpenEvent}\n * * {@link EventListenerWaitingEvent}\n */\n\n/**\n * An {@link EventListenerEvent} provides context about an event raised by the SDK on the\n * {@link EventListener}. Apart from the properties listed here, it may also include some\n * event-specific data within an optional \"payload\" property. The different types of\n * {@link EventListenerEvent}s are listed below:\n * * {@link EventListenerClosedEvent}\n * * {@link EventListenerConnectingEvent}\n * * {@link EventListenerEarlyEvent}\n * * {@link EventListenerOpenEvent}\n * * {@link EventListenerWaitingEvent}\n * @typedef {object} EventListenerEvent\n * @property {number} elapsedTime - The time elapsed in milliseconds since connect() was called\n * @property {EventListenerGroup} group - The group under which the event is classified\n * @property {EventListenerLevel} level - The verbosity level of the event, which can be one of \"debug\", \"error\", \"info\", \"warning\"\n * @property {string} name - The name of the event\n * @property {*} [payload] - Optional event-specific data\n * @property {number} timestamp - The time in milliseconds relative to the Unix Epoch when the event was raised\n */\n\n/**\n * The connection to Twilio's signaling server was closed.\n * @typedef {EventListenerEvent} EventListenerClosedEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level - 'info' if the connection was closed by the client, 'error' otherwise\n * @property {string} name='closed'\n * @property {{reason: string}} payload - Reason for the connection being closed. It can be one of\n * 'busy', 'failed', 'local', 'remote' or 'timeout'\n */\n\n/**\n * The SDK is connecting to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerConnectingEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='connecting'\n */\n\n/**\n * The SDK is about to connect to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerEarlyEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='early'\n */\n\n/**\n * The SDK has established a signaling connection to Twilio's signaling server.\n * @typedef {EventListenerEvent} EventListenerOpenEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='info'\n * @property {string} name='open'\n */\n\n/**\n * The SDK is waiting to retry connecting th Twilio's signaling server. This can\n * happen if the server is busy with too many connection requests.\n * @typedef {EventListenerEvent} EventListenerWaitingEvent\n * @property {EventListenerGroup} group='signaling'\n * @property {EventListenerLevel} level='warning'\n * @property {string} name='waiting'\n */\n\n\nfunction deprecateOptions(options, log, deprecationTable) {\n deprecationTable.forEach(prop => {\n const { didWarn, name, newName, shouldDelete } = prop;\n if (name in options && typeof options[name] !== 'undefined') {\n if (newName && shouldDelete) {\n options[newName] = options[name];\n }\n if (shouldDelete) {\n delete options[name];\n }\n if (!didWarn && !['error', 'off'].includes(log.level)) {\n log.warn(`The ConnectOptions \"${name}\" is ${newName\n ? `deprecated and scheduled for removal. Please use \"${newName}\" instead.`\n : 'no longer applicable and will be ignored.'}`);\n prop.didWarn = true;\n }\n }\n });\n}\n\nfunction createLocalParticipant(signaling, log, encodingParameters, networkQualityConfiguration, options, localTracks) {\n const localParticipantSignaling = signaling.createLocalParticipantSignaling(encodingParameters, networkQualityConfiguration);\n log.debug('Creating a new LocalParticipant:', localParticipantSignaling);\n return new options.LocalParticipant(localParticipantSignaling, localTracks, options);\n}\n\nfunction createRoom(options, localParticipant, roomSignaling) {\n const room = new Room(localParticipant, roomSignaling, options);\n const log = options.log;\n\n log.debug('Creating a new Room:', room);\n roomSignaling.on('stateChanged', function stateChanged(state) {\n if (state === 'disconnected') {\n log.info('Disconnected from Room:', room.toString());\n roomSignaling.removeListener('stateChanged', stateChanged);\n }\n });\n\n return room;\n}\n\nfunction createRoomSignaling(token, options, signaling, encodingParameters, preferredCodecs, localParticipant) {\n options.log.debug('Creating a new RoomSignaling');\n return signaling.connect(\n localParticipant._signaling,\n token,\n encodingParameters,\n preferredCodecs,\n options);\n}\n\nfunction getLocalTracks(options, handleLocalTracks) {\n const log = options.log;\n\n options.shouldStopLocalTracks = !options.tracks;\n if (options.shouldStopLocalTracks) {\n log.info('LocalTracks were not provided, so they will be acquired '\n + 'automatically before connecting to the Room. LocalTracks will '\n + 'be released if connecting to the Room fails or if the Room '\n + 'is disconnected');\n } else {\n log.info('Getting LocalTracks');\n log.debug('Options:', options);\n }\n\n return options.createLocalTracks(options).then(function getLocalTracksSucceeded(localTracks) {\n const promise = handleLocalTracks(localTracks);\n\n promise.catch(function handleLocalTracksFailed() {\n if (options.shouldStopLocalTracks) {\n log.info('The automatically acquired LocalTracks will now be stopped');\n localTracks.forEach(track => {\n track.stop();\n });\n }\n });\n\n return promise;\n });\n}\n\nfunction normalizeCodecSettings(nameOrSettings) {\n const settings = typeof nameOrSettings === 'string'\n ? { codec: nameOrSettings }\n : nameOrSettings;\n switch (settings.codec.toLowerCase()) {\n case 'opus': {\n return Object.assign({ dtx: true }, settings);\n }\n case 'vp8': {\n return Object.assign({ simulcast: false }, settings);\n }\n default: {\n return settings;\n }\n }\n}\n\nmodule.exports = connect;\n", "'use strict';\n\nconst { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME } = require('./util/constants');\n\n/**\n * Request a {@link LocalAudioTrack} or {@link LocalVideoTrack}.\n * @param {Track.Kind} kind - \"audio\" or \"video\"\n * @param {CreateLocalTrackOptions} [options]\n * @returns {Promise}\n * @private\n */\nfunction createLocalTrack(kind, options) {\n options = Object.assign({\n loggerName: DEFAULT_LOGGER_NAME,\n logLevel: DEFAULT_LOG_LEVEL,\n }, options);\n\n const createOptions = {};\n createOptions.loggerName = options.loggerName;\n createOptions.logLevel = options.logLevel;\n delete options.loggerName;\n delete options.logLevel;\n\n const createLocalTracks = options.createLocalTracks;\n delete options.createLocalTracks;\n createOptions[kind] = Object.keys(options).length > 0 ? options : true;\n\n return createLocalTracks(createOptions).then(localTracks => localTracks[0]);\n}\n\n/**\n * Request a {@link LocalAudioTrack}.\n * @alias module:twilio-video.createLocalAudioTrack\n * @param {CreateLocalTracksOptions|CreateLocalAudioTrackOptions} [options] - Options for requesting a {@link LocalAudioTrack}\n * @returns {Promise}\n * @example\n * var Video = require('twilio-video');\n *\n * // Connect to the Room with just video\n * Video.connect('my-token', {\n * name: 'my-cool-room',\n * video: true\n * }).then(function(room) {\n * // Add audio after connecting to the Room\n * Video.createLocalAudioTrack().then(function(localTrack) {\n * room.localParticipant.publishTrack(localTrack);\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n *\n * // Request the LocalAudioTrack with a custom name\n * // and krisp noise cancellation\n * Video.createLocalAudioTrack({\n * name: 'microphone',\n * noiseCancellationOptions: {\n * vendor: 'krisp',\n * sdkAssetsPath: '/twilio-krisp-audio-plugin/1.0.0/dist'\n * }\n * });\n */\nfunction createLocalAudioTrack(options) {\n return createLocalTrack('audio', options);\n}\n\n/**\n * Request a {@link LocalVideoTrack}. Note that on mobile browsers,\n * the camera can be reserved by only one {@link LocalVideoTrack} at any given\n * time. If you attempt to create a second {@link LocalVideoTrack}, video frames\n * will no longer be supplied to the first {@link LocalVideoTrack}.\n * @alias module:twilio-video.createLocalVideoTrack\n * @param {CreateLocalTrackOptions} [options] - Options for requesting a {@link LocalVideoTrack}\n * @returns {Promise}\n * @example\n * var Video = require('twilio-video');\n *\n * // Connect to the Room with just audio\n * Video.connect('my-token', {\n * name: 'my-cool-room',\n * audio: true\n * }).then(function(room) {\n * // Add video after connecting to the Room\n * Video.createLocalVideoTrack().then(function(localTrack) {\n * room.localParticipant.publishTrack(localTrack);\n * });\n * });\n * @example\n * var Video = require('twilio-video');\n *\n * // Request the default LocalVideoTrack with a custom name\n * Video.createLocalVideoTrack({ name: 'camera' }).then(function(localTrack) {\n * console.log(localTrack.name); // 'camera'\n * });\n */\nfunction createLocalVideoTrack(options) {\n return createLocalTrack('video', options);\n}\n\n/**\n * {@link NoiseCancellationVendor} specifies the 3rd party plugin to use for noise cancellation.\n * @enum {string}\n */\n// eslint-disable-next-line\nconst NoiseCancellationVendor = {\n /**\n * This plugin can be found by requesting access with this form {@link https://forms.gle/eeFyoGJj1mgMrxN88}\n */\n krisp: 'krisp',\n};\n\n/**\n * You can use 3rd party noise cancellation plugin when creating {@link LocalAudioTrack}\n * By specifying these options. This is a beta feature.\n * @typedef {object} NoiseCancellationOptions\n * @property {NoiseCancellationVendor} vendor - Specifies the vendor library to use\n * You need to obtain and host the library files on your web server.\n * @property {string} sdkAssetsPath - Specifies path where vendor library files are\n * hosted on your web server.\n */\n\n/**\n * Create {@link LocalAudioTrack} options.\n * @typedef {CreateLocalTrackOptions} CreateLocalAudioTrackOptions\n * @property {boolean} [workaroundWebKitBug180748=false] - setting this\n * attempts to workaround WebKit Bug 180748, where, in Safari, getUserMedia may return a silent audio\n * MediaStreamTrack.\n * @property {DefaultDeviceCaptureMode} [defaultDeviceCaptureMode=\"auto\"] - This optional property only applies if the\n * {@link LocalAudioTrack} is capturing from the default audio input device connected to a desktop or laptop. When the\n * property is set to \"auto\", the LocalAudioTrack restarts whenever the default audio input device changes, in order to\n * capture audio from the new default audio input device. For example, when a bluetooth audio headset is connected to a\n * Macbook, the LocalAudioTrack will start capturing audio from the headset microphone. When the headset is disconnected,\n * the LocalAudioTrack will start capturing audio from the Macbook microphone. When the property is set to \"manual\", the\n * LocalAudioTrack continues to capture from the same audio input device even after the default audio input device changes.\n * When the property is not specified, it defaults to \"auto\".\n * @property {NoiseCancellationOptions} [noiseCancellationOptions] - This optional property enables using 3rd party plugins\n * for noise cancellation.\n */\n\n/**\n * Create {@link LocalTrack} options. Apart from the properties listed here, you can\n * also specify any of the MediaTrackConstraints\n * properties.\n * @typedef {MediaTrackConstraints} CreateLocalTrackOptions\n * @property {LogLevel|LogLevels} [logLevel='warn'] - (deprecated: use [Video.Logger](module-twilio-video.html) instead.\n * See [examples](module-twilio-video.html#.connect) for details)\n * Set the default log verbosity\n * of logging. Passing a {@link LogLevel} string will use the same\n * level for all components. Pass a {@link LogLevels} to set specific log\n * levels.\n * @property {string} [loggerName='twilio-video'] - The name of the logger. Use this name when accessing the logger used by the SDK.\n * See [examples](module-twilio-video.html#.connect) for details.\n * @property {string} [name] - The {@link LocalTrack}'s name; by default,\n * it is set to the {@link LocalTrack}'s ID.\n */\n\nmodule.exports = {\n audio: createLocalAudioTrack,\n video: createLocalVideoTrack\n};\n", "'use strict';\n\nconst { guessBrowser, support: isWebRTCSupported } = require('../webrtc/util');\nconst { getSdpFormat } = require('../webrtc/util/sdp');\nconst { isAndroid, isMobile, isNonChromiumEdge, rebrandedChromeBrowser, mobileWebKitBrowser } = require('./browserdetection');\n\nconst SUPPORTED_CHROME_BASED_BROWSERS = [\n 'crios',\n 'edg',\n 'edge',\n 'electron',\n 'headlesschrome'\n];\nconst SUPPORTED_ANDROID_BROWSERS = [\n 'chrome',\n 'firefox'\n];\nconst SUPPORTED_IOS_BROWSERS = [\n 'chrome',\n 'safari'\n];\n// Currently none. Add 'brave', 'edg', and 'edge' here once we start supporting them\nconst SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS = [];\n\n/**\n * Check if the current browser is officially supported by twilio-video.js.\n * @returns {boolean}\n */\nfunction isSupported() {\n const browser = guessBrowser();\n\n // NOTE (csantos): Return right away if there is no browser detected\n // to prevent unnecessary checks which could lead to errors\n if (!browser) {\n return false;\n }\n\n const rebrandedChrome = rebrandedChromeBrowser(browser);\n const mobileWebKit = mobileWebKitBrowser(browser);\n const supportedMobileBrowsers = isAndroid() ?\n SUPPORTED_ANDROID_BROWSERS : SUPPORTED_IOS_BROWSERS;\n\n return !!browser\n && isWebRTCSupported()\n && getSdpFormat() === 'unified'\n && (!rebrandedChrome || SUPPORTED_CHROME_BASED_BROWSERS.includes(rebrandedChrome))\n && !isNonChromiumEdge(browser)\n && (!mobileWebKit || SUPPORTED_MOBILE_WEBKIT_BASED_BROWSERS.includes(mobileWebKit))\n && (!isMobile() || supportedMobileBrowsers.includes(browser));\n}\n\nmodule.exports = isSupported;\n", "'use strict';\n\nimport type { ConnectOptions, CreateLocalTrackOptions, CreateLocalAudioTrackOptions } from '../tsdef/types';\nimport type { LocalAudioTrack as LocalAudioTrackType } from '../tsdef/LocalAudioTrack';\nimport type { LocalVideoTrack as LocalVideoTrackType } from '../tsdef/LocalVideoTrack';\nimport type { Log } from '../tsdef/loglevel';\nimport type { Room } from '../tsdef/Room';\nimport { createLocalTracks } from './createlocaltracks';\nimport { runPreflight } from './preflight/preflighttest';\n\n\nconst internals = {\n connect: require('./connect'),\n createLocalAudioTrack: require('./createlocaltrack').audio,\n createLocalVideoTrack: require('./createlocaltrack').video,\n isSupported: require('./util/support')(),\n version: require('../package.json').version,\n Logger: require('./vendor/loglevel'),\n LocalAudioTrack: require('./media/track/es5').LocalAudioTrack,\n LocalDataTrack: require('./media/track/es5').LocalDataTrack,\n LocalVideoTrack: require('./media/track/es5').LocalVideoTrack\n};\n\nfunction connect(token: string, options?: ConnectOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.connect(token, internalOptions);\n}\n\nfunction createLocalAudioTrack(options?: CreateLocalTrackOptions|CreateLocalAudioTrackOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.createLocalAudioTrack(internalOptions);\n}\n\nfunction createLocalVideoTrack(options?: CreateLocalTrackOptions): Promise {\n const internalOptions = {\n createLocalTracks,\n ...options\n };\n return internals.createLocalVideoTrack(internalOptions);\n}\n\n/**\n * @module twilio-video\n * @property {boolean} isSupported - true if the current browser is officially\n * supported by twilio-video.js; In this context, \"supported\" means that\n * twilio-video.js has been extensively tested with this browser; This\n * table\n * specifies the list of officially supported browsers.\n *\n * @property {object} Logger - The loglevel\n * module used by the SDK. Use this object to access the internal loggers and perform actions as defined by the\n * loglevel APIs.\n * See [connect](#.connect) for examples.\n *\n * @property {string} version - current version of twilio-video.js.\n */\n\nconst isSupported: boolean = internals.isSupported;\nconst version: boolean = internals.version;\nconst Logger: Log.RootLogger = internals.Logger;\nconst LocalAudioTrack = internals.LocalAudioTrack;\nconst LocalVideoTrack = internals.LocalVideoTrack;\nconst LocalDataTrack = internals.LocalDataTrack;\n\nmodule.exports = {\n connect,\n createLocalAudioTrack,\n createLocalVideoTrack,\n createLocalTracks,\n runPreflight,\n isSupported,\n version,\n Logger,\n LocalAudioTrack,\n LocalVideoTrack,\n LocalDataTrack,\n};\n", "// Generated by CoffeeScript 1.8.0\n(function() {\n var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup;\n\n floor = Math.floor, min = Math.min;\n\n\n /*\n Default comparison function to be used\n */\n\n defaultCmp = function(x, y) {\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n return 0;\n };\n\n\n /*\n Insert item x in list a, and keep it sorted assuming a is sorted.\n \n If x is already in a, insert it to the right of the rightmost x.\n \n Optional args lo (default 0) and hi (default a.length) bound the slice\n of a to be searched.\n */\n\n insort = function(a, x, lo, hi, cmp) {\n var mid;\n if (lo == null) {\n lo = 0;\n }\n if (cmp == null) {\n cmp = defaultCmp;\n }\n if (lo < 0) {\n throw new Error('lo must be non-negative');\n }\n if (hi == null) {\n hi = a.length;\n }\n while (lo < hi) {\n mid = floor((lo + hi) / 2);\n if (cmp(x, a[mid]) < 0) {\n hi = mid;\n } else {\n lo = mid + 1;\n }\n }\n return ([].splice.apply(a, [lo, lo - lo].concat(x)), x);\n };\n\n\n /*\n Push item onto heap, maintaining the heap invariant.\n */\n\n heappush = function(array, item, cmp) {\n if (cmp == null) {\n cmp = defaultCmp;\n }\n array.push(item);\n return _siftdown(array, 0, array.length - 1, cmp);\n };\n\n\n /*\n Pop the smallest item off the heap, maintaining the heap invariant.\n */\n\n heappop = function(array, cmp) {\n var lastelt, returnitem;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n lastelt = array.pop();\n if (array.length) {\n returnitem = array[0];\n array[0] = lastelt;\n _siftup(array, 0, cmp);\n } else {\n returnitem = lastelt;\n }\n return returnitem;\n };\n\n\n /*\n Pop and return the current smallest value, and add the new item.\n \n This is more efficient than heappop() followed by heappush(), and can be\n more appropriate when using a fixed size heap. Note that the value\n returned may be larger than item! That constrains reasonable use of\n this routine unless written as part of a conditional replacement:\n if item > array[0]\n item = heapreplace(array, item)\n */\n\n heapreplace = function(array, item, cmp) {\n var returnitem;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n returnitem = array[0];\n array[0] = item;\n _siftup(array, 0, cmp);\n return returnitem;\n };\n\n\n /*\n Fast version of a heappush followed by a heappop.\n */\n\n heappushpop = function(array, item, cmp) {\n var _ref;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n if (array.length && cmp(array[0], item) < 0) {\n _ref = [array[0], item], item = _ref[0], array[0] = _ref[1];\n _siftup(array, 0, cmp);\n }\n return item;\n };\n\n\n /*\n Transform list into a heap, in-place, in O(array.length) time.\n */\n\n heapify = function(array, cmp) {\n var i, _i, _j, _len, _ref, _ref1, _results, _results1;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n _ref1 = (function() {\n _results1 = [];\n for (var _j = 0, _ref = floor(array.length / 2); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j--){ _results1.push(_j); }\n return _results1;\n }).apply(this).reverse();\n _results = [];\n for (_i = 0, _len = _ref1.length; _i < _len; _i++) {\n i = _ref1[_i];\n _results.push(_siftup(array, i, cmp));\n }\n return _results;\n };\n\n\n /*\n Update the position of the given item in the heap.\n This function should be called every time the item is being modified.\n */\n\n updateItem = function(array, item, cmp) {\n var pos;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n pos = array.indexOf(item);\n if (pos === -1) {\n return;\n }\n _siftdown(array, 0, pos, cmp);\n return _siftup(array, pos, cmp);\n };\n\n\n /*\n Find the n largest elements in a dataset.\n */\n\n nlargest = function(array, n, cmp) {\n var elem, result, _i, _len, _ref;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n result = array.slice(0, n);\n if (!result.length) {\n return result;\n }\n heapify(result, cmp);\n _ref = array.slice(n);\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n elem = _ref[_i];\n heappushpop(result, elem, cmp);\n }\n return result.sort(cmp).reverse();\n };\n\n\n /*\n Find the n smallest elements in a dataset.\n */\n\n nsmallest = function(array, n, cmp) {\n var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n if (n * 10 <= array.length) {\n result = array.slice(0, n).sort(cmp);\n if (!result.length) {\n return result;\n }\n los = result[result.length - 1];\n _ref = array.slice(n);\n for (_i = 0, _len = _ref.length; _i < _len; _i++) {\n elem = _ref[_i];\n if (cmp(elem, los) < 0) {\n insort(result, elem, 0, null, cmp);\n result.pop();\n los = result[result.length - 1];\n }\n }\n return result;\n }\n heapify(array, cmp);\n _results = [];\n for (i = _j = 0, _ref1 = min(n, array.length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) {\n _results.push(heappop(array, cmp));\n }\n return _results;\n };\n\n _siftdown = function(array, startpos, pos, cmp) {\n var newitem, parent, parentpos;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n newitem = array[pos];\n while (pos > startpos) {\n parentpos = (pos - 1) >> 1;\n parent = array[parentpos];\n if (cmp(newitem, parent) < 0) {\n array[pos] = parent;\n pos = parentpos;\n continue;\n }\n break;\n }\n return array[pos] = newitem;\n };\n\n _siftup = function(array, pos, cmp) {\n var childpos, endpos, newitem, rightpos, startpos;\n if (cmp == null) {\n cmp = defaultCmp;\n }\n endpos = array.length;\n startpos = pos;\n newitem = array[pos];\n childpos = 2 * pos + 1;\n while (childpos < endpos) {\n rightpos = childpos + 1;\n if (rightpos < endpos && !(cmp(array[childpos], array[rightpos]) < 0)) {\n childpos = rightpos;\n }\n array[pos] = array[childpos];\n pos = childpos;\n childpos = 2 * pos + 1;\n }\n array[pos] = newitem;\n return _siftdown(array, startpos, pos, cmp);\n };\n\n Heap = (function() {\n Heap.push = heappush;\n\n Heap.pop = heappop;\n\n Heap.replace = heapreplace;\n\n Heap.pushpop = heappushpop;\n\n Heap.heapify = heapify;\n\n Heap.updateItem = updateItem;\n\n Heap.nlargest = nlargest;\n\n Heap.nsmallest = nsmallest;\n\n function Heap(cmp) {\n this.cmp = cmp != null ? cmp : defaultCmp;\n this.nodes = [];\n }\n\n Heap.prototype.push = function(x) {\n return heappush(this.nodes, x, this.cmp);\n };\n\n Heap.prototype.pop = function() {\n return heappop(this.nodes, this.cmp);\n };\n\n Heap.prototype.peek = function() {\n return this.nodes[0];\n };\n\n Heap.prototype.contains = function(x) {\n return this.nodes.indexOf(x) !== -1;\n };\n\n Heap.prototype.replace = function(x) {\n return heapreplace(this.nodes, x, this.cmp);\n };\n\n Heap.prototype.pushpop = function(x) {\n return heappushpop(this.nodes, x, this.cmp);\n };\n\n Heap.prototype.heapify = function() {\n return heapify(this.nodes, this.cmp);\n };\n\n Heap.prototype.updateItem = function(x) {\n return updateItem(this.nodes, x, this.cmp);\n };\n\n Heap.prototype.clear = function() {\n return this.nodes = [];\n };\n\n Heap.prototype.empty = function() {\n return this.nodes.length === 0;\n };\n\n Heap.prototype.size = function() {\n return this.nodes.length;\n };\n\n Heap.prototype.clone = function() {\n var heap;\n heap = new Heap();\n heap.nodes = this.nodes.slice(0);\n return heap;\n };\n\n Heap.prototype.toArray = function() {\n return this.nodes.slice(0);\n };\n\n Heap.prototype.insert = Heap.prototype.push;\n\n Heap.prototype.top = Heap.prototype.peek;\n\n Heap.prototype.front = Heap.prototype.peek;\n\n Heap.prototype.has = Heap.prototype.contains;\n\n Heap.prototype.copy = Heap.prototype.clone;\n\n return Heap;\n\n })();\n\n (function(root, factory) {\n if (typeof define === 'function' && define.amd) {\n return define([], factory);\n } else if (typeof exports === 'object') {\n return module.exports = factory();\n } else {\n return root.Heap = factory();\n }\n })(this, function() {\n return Heap;\n });\n\n}).call(this);\n", "module.exports = require('./lib/heap');\n", "/*!\n * Determine if an object is a Buffer\n *\n * @author Feross Aboukhadijeh \n * @license MIT\n */\n\nmodule.exports = function isBuffer (obj) {\n return obj != null && obj.constructor != null &&\n typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj)\n}\n", "'use strict';\n\nvar hasOwn = Object.prototype.hasOwnProperty;\nvar toStr = Object.prototype.toString;\nvar defineProperty = Object.defineProperty;\nvar gOPD = Object.getOwnPropertyDescriptor;\n\nvar isArray = function isArray(arr) {\n\tif (typeof Array.isArray === 'function') {\n\t\treturn Array.isArray(arr);\n\t}\n\n\treturn toStr.call(arr) === '[object Array]';\n};\n\nvar isPlainObject = function isPlainObject(obj) {\n\tif (!obj || toStr.call(obj) !== '[object Object]') {\n\t\treturn false;\n\t}\n\n\tvar hasOwnConstructor = hasOwn.call(obj, 'constructor');\n\tvar hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');\n\t// Not own constructor property must be Object\n\tif (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {\n\t\treturn false;\n\t}\n\n\t// Own properties are enumerated firstly, so to speed up,\n\t// if last one is own, then all properties are own.\n\tvar key;\n\tfor (key in obj) { /**/ }\n\n\treturn typeof key === 'undefined' || hasOwn.call(obj, key);\n};\n\n// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target\nvar setProperty = function setProperty(target, options) {\n\tif (defineProperty && options.name === '__proto__') {\n\t\tdefineProperty(target, options.name, {\n\t\t\tenumerable: true,\n\t\t\tconfigurable: true,\n\t\t\tvalue: options.newValue,\n\t\t\twritable: true\n\t\t});\n\t} else {\n\t\ttarget[options.name] = options.newValue;\n\t}\n};\n\n// Return undefined instead of __proto__ if '__proto__' is not an own property\nvar getProperty = function getProperty(obj, name) {\n\tif (name === '__proto__') {\n\t\tif (!hasOwn.call(obj, name)) {\n\t\t\treturn void 0;\n\t\t} else if (gOPD) {\n\t\t\t// In early versions of node, obj['__proto__'] is buggy when obj has\n\t\t\t// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.\n\t\t\treturn gOPD(obj, name).value;\n\t\t}\n\t}\n\n\treturn obj[name];\n};\n\nmodule.exports = function extend() {\n\tvar options, name, src, copy, copyIsArray, clone;\n\tvar target = arguments[0];\n\tvar i = 1;\n\tvar length = arguments.length;\n\tvar deep = false;\n\n\t// Handle a deep copy situation\n\tif (typeof target === 'boolean') {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\tif (target == null || (typeof target !== 'object' && typeof target !== 'function')) {\n\t\ttarget = {};\n\t}\n\n\tfor (; i < length; ++i) {\n\t\toptions = arguments[i];\n\t\t// Only deal with non-null/undefined values\n\t\tif (options != null) {\n\t\t\t// Extend the base object\n\t\t\tfor (name in options) {\n\t\t\t\tsrc = getProperty(target, name);\n\t\t\t\tcopy = getProperty(options, name);\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif (target !== copy) {\n\t\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\t\tif (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {\n\t\t\t\t\t\tif (copyIsArray) {\n\t\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\t\tclone = src && isArray(src) ? src : [];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tclone = src && isPlainObject(src) ? src : {};\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\t\tsetProperty(target, { name: name, newValue: extend(deep, clone, copy) });\n\n\t\t\t\t\t// Don't bring in undefined values\n\t\t\t\t\t} else if (typeof copy !== 'undefined') {\n\t\t\t\t\t\tsetProperty(target, { name: name, newValue: copy });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n", "\n'use strict';\n\n\nvar encodeCache = {};\n\n\n// Create a lookup array where anything but characters in `chars` string\n// and alphanumeric chars is percent-encoded.\n//\nfunction getEncodeCache(exclude) {\n var i, ch, cache = encodeCache[exclude];\n if (cache) { return cache; }\n\n cache = encodeCache[exclude] = [];\n\n for (i = 0; i < 128; i++) {\n ch = String.fromCharCode(i);\n\n if (/^[0-9a-z]$/i.test(ch)) {\n // always allow unencoded alphanumeric characters\n cache.push(ch);\n } else {\n cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2));\n }\n }\n\n for (i = 0; i < exclude.length; i++) {\n cache[exclude.charCodeAt(i)] = exclude[i];\n }\n\n return cache;\n}\n\n\n// Encode unsafe characters with percent-encoding, skipping already\n// encoded sequences.\n//\n// - string - string to encode\n// - exclude - list of characters to ignore (in addition to a-zA-Z0-9)\n// - keepEscaped - don't encode '%' in a correct escape sequence (default: true)\n//\nfunction encode(string, exclude, keepEscaped) {\n var i, l, code, nextCode, cache,\n result = '';\n\n if (typeof exclude !== 'string') {\n // encode(string, keepEscaped)\n keepEscaped = exclude;\n exclude = encode.defaultChars;\n }\n\n if (typeof keepEscaped === 'undefined') {\n keepEscaped = true;\n }\n\n cache = getEncodeCache(exclude);\n\n for (i = 0, l = string.length; i < l; i++) {\n code = string.charCodeAt(i);\n\n if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) {\n if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) {\n result += string.slice(i, i + 3);\n i += 2;\n continue;\n }\n }\n\n if (code < 128) {\n result += cache[code];\n continue;\n }\n\n if (code >= 0xD800 && code <= 0xDFFF) {\n if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) {\n nextCode = string.charCodeAt(i + 1);\n if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) {\n result += encodeURIComponent(string[i] + string[i + 1]);\n i++;\n continue;\n }\n }\n result += '%EF%BF%BD';\n continue;\n }\n\n result += encodeURIComponent(string[i]);\n }\n\n return result;\n}\n\nencode.defaultChars = \";/?:@&=+$,-_.!~*'()#\";\nencode.componentChars = \"-_.!~*'()\";\n\n\nmodule.exports = encode;\n", "// http://www.w3.org/TR/CSS21/grammar.html\n// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027\nvar COMMENT_REGEX = /\\/\\*[^*]*\\*+([^/*][^*]*\\*+)*\\//g;\n\nvar NEWLINE_REGEX = /\\n/g;\nvar WHITESPACE_REGEX = /^\\s*/;\n\n// declaration\nvar PROPERTY_REGEX = /^(\\*?[-#/*\\\\\\w]+(\\[[0-9a-z_-]+\\])?)\\s*/;\nvar COLON_REGEX = /^:\\s*/;\nvar VALUE_REGEX = /^((?:'(?:\\\\'|.)*?'|\"(?:\\\\\"|.)*?\"|\\([^)]*?\\)|[^};])+)/;\nvar SEMICOLON_REGEX = /^[;\\s]*/;\n\n// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill\nvar TRIM_REGEX = /^\\s+|\\s+$/g;\n\n// strings\nvar NEWLINE = '\\n';\nvar FORWARD_SLASH = '/';\nvar ASTERISK = '*';\nvar EMPTY_STRING = '';\n\n// types\nvar TYPE_COMMENT = 'comment';\nvar TYPE_DECLARATION = 'declaration';\n\n/**\n * @param {String} style\n * @param {Object} [options]\n * @return {Object[]}\n * @throws {TypeError}\n * @throws {Error}\n */\nmodule.exports = function(style, options) {\n if (typeof style !== 'string') {\n throw new TypeError('First argument must be a string');\n }\n\n if (!style) return [];\n\n options = options || {};\n\n /**\n * Positional.\n */\n var lineno = 1;\n var column = 1;\n\n /**\n * Update lineno and column based on `str`.\n *\n * @param {String} str\n */\n function updatePosition(str) {\n var lines = str.match(NEWLINE_REGEX);\n if (lines) lineno += lines.length;\n var i = str.lastIndexOf(NEWLINE);\n column = ~i ? str.length - i : column + str.length;\n }\n\n /**\n * Mark position and patch `node.position`.\n *\n * @return {Function}\n */\n function position() {\n var start = { line: lineno, column: column };\n return function(node) {\n node.position = new Position(start);\n whitespace();\n return node;\n };\n }\n\n /**\n * Store position information for a node.\n *\n * @constructor\n * @property {Object} start\n * @property {Object} end\n * @property {undefined|String} source\n */\n function Position(start) {\n this.start = start;\n this.end = { line: lineno, column: column };\n this.source = options.source;\n }\n\n /**\n * Non-enumerable source string.\n */\n Position.prototype.content = style;\n\n var errorsList = [];\n\n /**\n * Error `msg`.\n *\n * @param {String} msg\n * @throws {Error}\n */\n function error(msg) {\n var err = new Error(\n options.source + ':' + lineno + ':' + column + ': ' + msg\n );\n err.reason = msg;\n err.filename = options.source;\n err.line = lineno;\n err.column = column;\n err.source = style;\n\n if (options.silent) {\n errorsList.push(err);\n } else {\n throw err;\n }\n }\n\n /**\n * Match `re` and return captures.\n *\n * @param {RegExp} re\n * @return {undefined|Array}\n */\n function match(re) {\n var m = re.exec(style);\n if (!m) return;\n var str = m[0];\n updatePosition(str);\n style = style.slice(str.length);\n return m;\n }\n\n /**\n * Parse whitespace.\n */\n function whitespace() {\n match(WHITESPACE_REGEX);\n }\n\n /**\n * Parse comments.\n *\n * @param {Object[]} [rules]\n * @return {Object[]}\n */\n function comments(rules) {\n var c;\n rules = rules || [];\n while ((c = comment())) {\n if (c !== false) {\n rules.push(c);\n }\n }\n return rules;\n }\n\n /**\n * Parse comment.\n *\n * @return {Object}\n * @throws {Error}\n */\n function comment() {\n var pos = position();\n if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1)) return;\n\n var i = 2;\n while (\n EMPTY_STRING != style.charAt(i) &&\n (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1))\n ) {\n ++i;\n }\n i += 2;\n\n if (EMPTY_STRING === style.charAt(i - 1)) {\n return error('End of comment missing');\n }\n\n var str = style.slice(2, i - 2);\n column += 2;\n updatePosition(str);\n style = style.slice(i);\n column += 2;\n\n return pos({\n type: TYPE_COMMENT,\n comment: str\n });\n }\n\n /**\n * Parse declaration.\n *\n * @return {Object}\n * @throws {Error}\n */\n function declaration() {\n var pos = position();\n\n // prop\n var prop = match(PROPERTY_REGEX);\n if (!prop) return;\n comment();\n\n // :\n if (!match(COLON_REGEX)) return error(\"property missing ':'\");\n\n // val\n var val = match(VALUE_REGEX);\n\n var ret = pos({\n type: TYPE_DECLARATION,\n property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)),\n value: val\n ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING))\n : EMPTY_STRING\n });\n\n // ;\n match(SEMICOLON_REGEX);\n\n return ret;\n }\n\n /**\n * Parse declarations.\n *\n * @return {Object[]}\n */\n function declarations() {\n var decls = [];\n\n comments(decls);\n\n // declarations\n var decl;\n while ((decl = declaration())) {\n if (decl !== false) {\n decls.push(decl);\n comments(decls);\n }\n }\n\n return decls;\n }\n\n whitespace();\n return declarations();\n};\n\n/**\n * Trim `str`.\n *\n * @param {String} str\n * @return {String}\n */\nfunction trim(str) {\n return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING;\n}\n", "var parse = require('inline-style-parser');\n\n/**\n * Parses inline style to object.\n *\n * @example\n * // returns { 'line-height': '42' }\n * StyleToObject('line-height: 42;');\n *\n * @param {String} style - The inline style.\n * @param {Function} [iterator] - The iterator function.\n * @return {null|Object}\n */\nfunction StyleToObject(style, iterator) {\n var output = null;\n if (!style || typeof style !== 'string') {\n return output;\n }\n\n var declaration;\n var declarations = parse(style);\n var hasIterator = typeof iterator === 'function';\n var property;\n var value;\n\n for (var i = 0, len = declarations.length; i < len; i++) {\n declaration = declarations[i];\n property = declaration.property;\n value = declaration.value;\n\n if (hasIterator) {\n iterator(property, value, declaration);\n } else if (value) {\n output || (output = {});\n output[property] = value;\n }\n }\n\n return output;\n}\n\nmodule.exports = StyleToObject;\nmodule.exports.default = StyleToObject; // ESM support\n", "'use strict'\n\nmodule.exports = convert\n\nfunction convert(test) {\n if (typeof test === 'string') {\n return typeFactory(test)\n }\n\n if (test === null || test === undefined) {\n return ok\n }\n\n if (typeof test === 'object') {\n return ('length' in test ? anyFactory : matchesFactory)(test)\n }\n\n if (typeof test === 'function') {\n return test\n }\n\n throw new Error('Expected function, string, or object as test')\n}\n\nfunction convertAll(tests) {\n var results = []\n var length = tests.length\n var index = -1\n\n while (++index < length) {\n results[index] = convert(tests[index])\n }\n\n return results\n}\n\n// Utility assert each property in `test` is represented in `node`, and each\n// values are strictly equal.\nfunction matchesFactory(test) {\n return matches\n\n function matches(node) {\n var key\n\n for (key in test) {\n if (node[key] !== test[key]) {\n return false\n }\n }\n\n return true\n }\n}\n\nfunction anyFactory(tests) {\n var checks = convertAll(tests)\n var length = checks.length\n\n return matches\n\n function matches() {\n var index = -1\n\n while (++index < length) {\n if (checks[index].apply(this, arguments)) {\n return true\n }\n }\n\n return false\n }\n}\n\n// Utility to convert a string into a function which checks a given node\u2019s type\n// for said string.\nfunction typeFactory(test) {\n return type\n\n function type(node) {\n return Boolean(node && node.type === test)\n }\n}\n\n// Utility to return true.\nfunction ok() {\n return true\n}\n", "'use strict'\n\nmodule.exports = visitParents\n\nvar convert = require('unist-util-is/convert')\n\nvar CONTINUE = true\nvar SKIP = 'skip'\nvar EXIT = false\n\nvisitParents.CONTINUE = CONTINUE\nvisitParents.SKIP = SKIP\nvisitParents.EXIT = EXIT\n\nfunction visitParents(tree, test, visitor, reverse) {\n var is\n\n if (typeof test === 'function' && typeof visitor !== 'function') {\n reverse = visitor\n visitor = test\n test = null\n }\n\n is = convert(test)\n\n one(tree, null, [])\n\n // Visit a single node.\n function one(node, index, parents) {\n var result = []\n var subresult\n\n if (!test || is(node, index, parents[parents.length - 1] || null)) {\n result = toResult(visitor(node, parents))\n\n if (result[0] === EXIT) {\n return result\n }\n }\n\n if (node.children && result[0] !== SKIP) {\n subresult = toResult(all(node.children, parents.concat(node)))\n return subresult[0] === EXIT ? subresult : result\n }\n\n return result\n }\n\n // Visit children in `parent`.\n function all(children, parents) {\n var min = -1\n var step = reverse ? -1 : 1\n var index = (reverse ? children.length : min) + step\n var result\n\n while (index > min && index < children.length) {\n result = one(children[index], index, parents)\n\n if (result[0] === EXIT) {\n return result\n }\n\n index = typeof result[1] === 'number' ? result[1] : index + step\n }\n }\n}\n\nfunction toResult(value) {\n if (value !== null && typeof value === 'object' && 'length' in value) {\n return value\n }\n\n if (typeof value === 'number') {\n return [CONTINUE, value]\n }\n\n return [value]\n}\n", "'use strict'\n\nmodule.exports = visit\n\nvar visitParents = require('unist-util-visit-parents')\n\nvar CONTINUE = visitParents.CONTINUE\nvar SKIP = visitParents.SKIP\nvar EXIT = visitParents.EXIT\n\nvisit.CONTINUE = CONTINUE\nvisit.SKIP = SKIP\nvisit.EXIT = EXIT\n\nfunction visit(tree, test, visitor, reverse) {\n if (typeof test === 'function' && typeof visitor !== 'function') {\n reverse = visitor\n visitor = test\n test = null\n }\n\n visitParents(tree, test, overload, reverse)\n\n function overload(node, parents) {\n var parent = parents[parents.length - 1]\n var index = parent ? parent.children.indexOf(node) : null\n return visitor(node, index, parent)\n }\n}\n", "'use strict';\n\nvar visit = require('unist-util-visit');\n\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar hastCssPropertyMap = {\n align: 'text-align',\n valign: 'vertical-align',\n height: 'height',\n width: 'width',\n};\n\nmodule.exports = function tableCellStyle(node) {\n visit(node, 'element', visitor);\n return node;\n};\n\nfunction visitor(node) {\n if (node.tagName !== 'tr' && node.tagName !== 'td' && node.tagName !== 'th') {\n return;\n }\n\n var hastName;\n var cssName;\n for (hastName in hastCssPropertyMap) {\n if (\n !hasOwnProperty.call(hastCssPropertyMap, hastName) ||\n node.properties[hastName] === undefined\n ) {\n continue;\n }\n cssName = hastCssPropertyMap[hastName];\n appendStyle(node, cssName, node.properties[hastName]);\n delete node.properties[hastName];\n }\n}\n\nfunction appendStyle(node, property, value) {\n var prevStyle = (node.properties.style || '').trim();\n if (prevStyle && !/;\\s*/.test(prevStyle)) {\n prevStyle += ';';\n }\n if (prevStyle) {\n prevStyle += ' ';\n }\n var nextStyle = prevStyle + property + ': ' + value + ';';\n node.properties.style = nextStyle;\n}\n", "/*\r\nBreaks a Javascript string into individual user-perceived \"characters\" \r\ncalled extended grapheme clusters by implementing the Unicode UAX-29 standard, version 10.0.0\r\n\r\nUsage:\r\nvar splitter = new GraphemeSplitter();\r\n//returns an array of strings, one string for each grapheme cluster\r\nvar graphemes = splitter.splitGraphemes(string); \r\n\r\n*/\r\nfunction GraphemeSplitter(){\r\n\tvar CR = 0,\r\n\t\tLF = 1,\r\n\t\tControl = 2,\r\n\t\tExtend = 3,\r\n\t\tRegional_Indicator = 4,\r\n\t\tSpacingMark = 5,\r\n\t\tL = 6,\r\n\t\tV = 7,\r\n\t\tT = 8,\r\n\t\tLV = 9,\r\n\t\tLVT = 10,\r\n\t\tOther = 11,\r\n\t\tPrepend = 12,\r\n\t\tE_Base = 13,\r\n\t\tE_Modifier = 14,\r\n\t\tZWJ = 15,\r\n\t\tGlue_After_Zwj = 16,\r\n\t\tE_Base_GAZ = 17;\r\n\t\t\r\n\t// BreakTypes\r\n\tvar NotBreak = 0,\r\n\t\tBreakStart = 1,\r\n\t\tBreak = 2,\r\n\t\tBreakLastRegional = 3,\r\n\t\tBreakPenultimateRegional = 4;\r\n\t\t\r\n\tfunction isSurrogate(str, pos) {\r\n\t\treturn 0xd800 <= str.charCodeAt(pos) && str.charCodeAt(pos) <= 0xdbff && \r\n\t\t\t\t0xdc00 <= str.charCodeAt(pos + 1) && str.charCodeAt(pos + 1) <= 0xdfff;\r\n\t}\r\n\t\t\r\n\t// Private function, gets a Unicode code point from a JavaScript UTF-16 string\r\n\t// handling surrogate pairs appropriately\r\n\tfunction codePointAt(str, idx){\r\n\t\tif(idx === undefined){\r\n\t\t\tidx = 0;\r\n\t\t}\r\n\t\tvar code = str.charCodeAt(idx);\r\n\r\n\t\t// if a high surrogate\r\n\t\tif (0xD800 <= code && code <= 0xDBFF && \r\n\t\t\tidx < str.length - 1){\r\n\t\t\tvar hi = code;\r\n\t\t\tvar low = str.charCodeAt(idx + 1);\r\n\t\t\tif (0xDC00 <= low && low <= 0xDFFF){\r\n\t\t\t\treturn ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\r\n\t\t\t}\r\n\t\t\treturn hi;\r\n\t\t}\r\n\t\t\r\n\t\t// if a low surrogate\r\n\t\tif (0xDC00 <= code && code <= 0xDFFF &&\r\n\t\t\tidx >= 1){\r\n\t\t\tvar hi = str.charCodeAt(idx - 1);\r\n\t\t\tvar low = code;\r\n\t\t\tif (0xD800 <= hi && hi <= 0xDBFF){\r\n\t\t\t\treturn ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;\r\n\t\t\t}\r\n\t\t\treturn low;\r\n\t\t}\r\n\t\t\r\n\t\t//just return the char if an unmatched surrogate half or a \r\n\t\t//single-char codepoint\r\n\t\treturn code;\r\n\t}\r\n\t\r\n\t// Private function, returns whether a break is allowed between the \r\n\t// two given grapheme breaking classes\r\n\tfunction shouldBreak(start, mid, end){\r\n\t\tvar all = [start].concat(mid).concat([end]);\r\n\t\tvar previous = all[all.length - 2]\r\n\t\tvar next = end\r\n\t\t\r\n\t\t// Lookahead termintor for:\r\n\t\t// GB10. (E_Base | EBG) Extend* ?\tE_Modifier\r\n\t\tvar eModifierIndex = all.lastIndexOf(E_Modifier)\r\n\t\tif(eModifierIndex > 1 &&\r\n\t\t\tall.slice(1, eModifierIndex).every(function(c){return c == Extend}) &&\r\n\t\t\t[Extend, E_Base, E_Base_GAZ].indexOf(start) == -1){\r\n\t\t\treturn Break\r\n\t\t}\r\n\r\n\t\t// Lookahead termintor for:\r\n\t\t// GB12. ^ (RI RI)* RI\t?\tRI\r\n\t\t// GB13. [^RI] (RI RI)* RI\t?\tRI\r\n\t\tvar rIIndex = all.lastIndexOf(Regional_Indicator)\r\n\t\tif(rIIndex > 0 &&\r\n\t\t\tall.slice(1, rIIndex).every(function(c){return c == Regional_Indicator}) &&\r\n\t\t\t[Prepend, Regional_Indicator].indexOf(previous) == -1) { \r\n\t\t\tif(all.filter(function(c){return c == Regional_Indicator}).length % 2 == 1) {\r\n\t\t\t\treturn BreakLastRegional\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\treturn BreakPenultimateRegional\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// GB3. CR X LF\r\n\t\tif(previous == CR && next == LF){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB4. (Control|CR|LF) \u00F7\r\n\t\telse if(previous == Control || previous == CR || previous == LF){\r\n\t\t\tif(next == E_Modifier && mid.every(function(c){return c == Extend})){\r\n\t\t\t\treturn Break\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\treturn BreakStart\r\n\t\t\t}\r\n\t\t}\r\n\t\t// GB5. \u00F7 (Control|CR|LF)\r\n\t\telse if(next == Control || next == CR || next == LF){\r\n\t\t\treturn BreakStart;\r\n\t\t}\r\n\t\t// GB6. L X (L|V|LV|LVT)\r\n\t\telse if(previous == L && \r\n\t\t\t(next == L || next == V || next == LV || next == LVT)){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB7. (LV|V) X (V|T)\r\n\t\telse if((previous == LV || previous == V) && \r\n\t\t\t(next == V || next == T)){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB8. (LVT|T) X (T)\r\n\t\telse if((previous == LVT || previous == T) && \r\n\t\t\tnext == T){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB9. X (Extend|ZWJ)\r\n\t\telse if (next == Extend || next == ZWJ){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB9a. X SpacingMark\r\n\t\telse if(next == SpacingMark){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t// GB9b. Prepend X\r\n\t\telse if (previous == Prepend){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t\r\n\t\t// GB10. (E_Base | EBG) Extend* ?\tE_Modifier\r\n\t\tvar previousNonExtendIndex = all.indexOf(Extend) != -1 ? all.lastIndexOf(Extend) - 1 : all.length - 2;\r\n\t\tif([E_Base, E_Base_GAZ].indexOf(all[previousNonExtendIndex]) != -1 &&\r\n\t\t\tall.slice(previousNonExtendIndex + 1, -1).every(function(c){return c == Extend}) &&\r\n\t\t\tnext == E_Modifier){\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\t\t\r\n\t\t// GB11. ZWJ ? (Glue_After_Zwj | EBG)\r\n\t\tif(previous == ZWJ && [Glue_After_Zwj, E_Base_GAZ].indexOf(next) != -1) {\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\r\n\t\t// GB12. ^ (RI RI)* RI ? RI\r\n\t\t// GB13. [^RI] (RI RI)* RI ? RI\r\n\t\tif(mid.indexOf(Regional_Indicator) != -1) { \r\n\t\t\treturn Break;\r\n\t\t}\r\n\t\tif(previous == Regional_Indicator && next == Regional_Indicator) {\r\n\t\t\treturn NotBreak;\r\n\t\t}\r\n\r\n\t\t// GB999. Any ? Any\r\n\t\treturn BreakStart;\r\n\t}\r\n\t\r\n\t// Returns the next grapheme break in the string after the given index\r\n\tthis.nextBreak = function(string, index){\r\n\t\tif(index === undefined){\r\n\t\t\tindex = 0;\r\n\t\t}\r\n\t\tif(index < 0){\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t\tif(index >= string.length - 1){\r\n\t\t\treturn string.length;\r\n\t\t}\r\n\t\tvar prev = getGraphemeBreakProperty(codePointAt(string, index));\r\n\t\tvar mid = []\r\n\t\tfor (var i = index + 1; i < string.length; i++) {\r\n\t\t\t// check for already processed low surrogates\r\n\t\t\tif(isSurrogate(string, i - 1)){\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\r\n\t\t\tvar next = getGraphemeBreakProperty(codePointAt(string, i));\r\n\t\t\tif(shouldBreak(prev, mid, next)){\r\n\t\t\t\treturn i;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tmid.push(next);\r\n\t\t}\r\n\t\treturn string.length;\r\n\t};\r\n\t\r\n\t// Breaks the given string into an array of grapheme cluster strings\r\n\tthis.splitGraphemes = function(str){\r\n\t\tvar res = [];\r\n\t\tvar index = 0;\r\n\t\tvar brk;\r\n\t\twhile((brk = this.nextBreak(str, index)) < str.length){\r\n\t\t\tres.push(str.slice(index, brk));\r\n\t\t\tindex = brk;\r\n\t\t}\r\n\t\tif(index < str.length){\r\n\t\t\tres.push(str.slice(index));\r\n\t\t}\r\n\t\treturn res;\r\n\t};\r\n\r\n\t// Returns the iterator of grapheme clusters there are in the given string\r\n\tthis.iterateGraphemes = function(str) {\r\n\t\tvar index = 0;\r\n\t\tvar res = {\r\n\t\t\tnext: (function() {\r\n\t\t\t\tvar value;\r\n\t\t\t\tvar brk;\r\n\t\t\t\tif ((brk = this.nextBreak(str, index)) < str.length) {\r\n\t\t\t\t\tvalue = str.slice(index, brk);\r\n\t\t\t\t\tindex = brk;\r\n\t\t\t\t\treturn { value: value, done: false };\r\n\t\t\t\t}\r\n\t\t\t\tif (index < str.length) {\r\n\t\t\t\t\tvalue = str.slice(index);\r\n\t\t\t\t\tindex = str.length;\r\n\t\t\t\t\treturn { value: value, done: false };\r\n\t\t\t\t}\r\n\t\t\t\treturn { value: undefined, done: true };\r\n\t\t\t}).bind(this)\r\n\t\t};\r\n\t\t// ES2015 @@iterator method (iterable) for spread syntax and for...of statement\r\n\t\tif (typeof Symbol !== 'undefined' && Symbol.iterator) {\r\n\t\t\tres[Symbol.iterator] = function() {return res};\r\n\t\t}\r\n\t\treturn res;\r\n\t};\r\n\r\n\t// Returns the number of grapheme clusters there are in the given string\r\n\tthis.countGraphemes = function(str){\r\n\t\tvar count = 0;\r\n\t\tvar index = 0;\r\n\t\tvar brk;\r\n\t\twhile((brk = this.nextBreak(str, index)) < str.length){\r\n\t\t\tindex = brk;\r\n\t\t\tcount++;\r\n\t\t}\r\n\t\tif(index < str.length){\r\n\t\t\tcount++;\r\n\t\t}\r\n\t\treturn count;\r\n\t};\r\n\t\r\n\t//given a Unicode code point, determines this symbol's grapheme break property\r\n\tfunction getGraphemeBreakProperty(code){\r\n\t\t\r\n\t\t//grapheme break property for Unicode 10.0.0, \r\n\t\t//taken from http://www.unicode.org/Public/10.0.0/ucd/auxiliary/GraphemeBreakProperty.txt\r\n\t\t//and adapted to JavaScript rules\r\n\t\t\r\n\t\tif(\t\t\r\n\t\t(0x0600 <= code && code <= 0x0605) || // Cf [6] ARABIC NUMBER SIGN..ARABIC NUMBER MARK ABOVE\r\n\t\t0x06DD == code || // Cf ARABIC END OF AYAH\r\n\t\t0x070F == code || // Cf SYRIAC ABBREVIATION MARK\r\n\t\t0x08E2 == code || // Cf ARABIC DISPUTED END OF AYAH\r\n\t\t0x0D4E == code || // Lo MALAYALAM LETTER DOT REPH\r\n\t\t0x110BD == code || // Cf KAITHI NUMBER SIGN\r\n\t\t(0x111C2 <= code && code <= 0x111C3) || // Lo [2] SHARADA SIGN JIHVAMULIYA..SHARADA SIGN UPADHMANIYA\r\n\t\t0x11A3A == code || // Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA\r\n\t\t(0x11A86 <= code && code <= 0x11A89) || // Lo [4] SOYOMBO CLUSTER-INITIAL LETTER RA..SOYOMBO CLUSTER-INITIAL LETTER SA\r\n\t\t0x11D46 == code // Lo MASARAM GONDI REPHA\r\n\t\t){\r\n\t\t\treturn Prepend;\r\n\t\t}\r\n\t\tif(\r\n\t\t0x000D == code // Cc \r\n\t\t){\r\n\t\t\treturn CR;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t0x000A == code // Cc \r\n\t\t){\r\n\t\t\treturn LF;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\tif(\r\n\t\t(0x0000 <= code && code <= 0x0009) || // Cc [10] ..\r\n\t\t(0x000B <= code && code <= 0x000C) || // Cc [2] ..\r\n\t\t(0x000E <= code && code <= 0x001F) || // Cc [18] ..\r\n\t\t(0x007F <= code && code <= 0x009F) || // Cc [33] ..\r\n\t\t0x00AD == code || // Cf SOFT HYPHEN\r\n\t\t0x061C == code || // Cf ARABIC LETTER MARK\r\n\t\r\n\t\t0x180E == code || // Cf MONGOLIAN VOWEL SEPARATOR\r\n\t\t0x200B == code || // Cf ZERO WIDTH SPACE\r\n\t\t(0x200E <= code && code <= 0x200F) || // Cf [2] LEFT-TO-RIGHT MARK..RIGHT-TO-LEFT MARK\r\n\t\t0x2028 == code || // Zl LINE SEPARATOR\r\n\t\t0x2029 == code || // Zp PARAGRAPH SEPARATOR\r\n\t\t(0x202A <= code && code <= 0x202E) || // Cf [5] LEFT-TO-RIGHT EMBEDDING..RIGHT-TO-LEFT OVERRIDE\r\n\t\t(0x2060 <= code && code <= 0x2064) || // Cf [5] WORD JOINER..INVISIBLE PLUS\r\n\t\t0x2065 == code || // Cn \r\n\t\t(0x2066 <= code && code <= 0x206F) || // Cf [10] LEFT-TO-RIGHT ISOLATE..NOMINAL DIGIT SHAPES\r\n\t\t(0xD800 <= code && code <= 0xDFFF) || // Cs [2048] ..\r\n\t\t0xFEFF == code || // Cf ZERO WIDTH NO-BREAK SPACE\r\n\t\t(0xFFF0 <= code && code <= 0xFFF8) || // Cn [9] ..\r\n\t\t(0xFFF9 <= code && code <= 0xFFFB) || // Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLINEAR ANNOTATION TERMINATOR\r\n\t\t(0x1BCA0 <= code && code <= 0x1BCA3) || // Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP\r\n\t\t(0x1D173 <= code && code <= 0x1D17A) || // Cf [8] MUSICAL SYMBOL BEGIN BEAM..MUSICAL SYMBOL END PHRASE\r\n\t\t0xE0000 == code || // Cn \r\n\t\t0xE0001 == code || // Cf LANGUAGE TAG\r\n\t\t(0xE0002 <= code && code <= 0xE001F) || // Cn [30] ..\r\n\t\t(0xE0080 <= code && code <= 0xE00FF) || // Cn [128] ..\r\n\t\t(0xE01F0 <= code && code <= 0xE0FFF) // Cn [3600] ..\r\n\t\t){\r\n\t\t\treturn Control;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\tif(\r\n\t\t(0x0300 <= code && code <= 0x036F) || // Mn [112] COMBINING GRAVE ACCENT..COMBINING LATIN SMALL LETTER X\r\n\t\t(0x0483 <= code && code <= 0x0487) || // Mn [5] COMBINING CYRILLIC TITLO..COMBINING CYRILLIC POKRYTIE\r\n\t\t(0x0488 <= code && code <= 0x0489) || // Me [2] COMBINING CYRILLIC HUNDRED THOUSANDS SIGN..COMBINING CYRILLIC MILLIONS SIGN\r\n\t\t(0x0591 <= code && code <= 0x05BD) || // Mn [45] HEBREW ACCENT ETNAHTA..HEBREW POINT METEG\r\n\t\t0x05BF == code || // Mn HEBREW POINT RAFE\r\n\t\t(0x05C1 <= code && code <= 0x05C2) || // Mn [2] HEBREW POINT SHIN DOT..HEBREW POINT SIN DOT\r\n\t\t(0x05C4 <= code && code <= 0x05C5) || // Mn [2] HEBREW MARK UPPER DOT..HEBREW MARK LOWER DOT\r\n\t\t0x05C7 == code || // Mn HEBREW POINT QAMATS QATAN\r\n\t\t(0x0610 <= code && code <= 0x061A) || // Mn [11] ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM..ARABIC SMALL KASRA\r\n\t\t(0x064B <= code && code <= 0x065F) || // Mn [21] ARABIC FATHATAN..ARABIC WAVY HAMZA BELOW\r\n\t\t0x0670 == code || // Mn ARABIC LETTER SUPERSCRIPT ALEF\r\n\t\t(0x06D6 <= code && code <= 0x06DC) || // Mn [7] ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA..ARABIC SMALL HIGH SEEN\r\n\t\t(0x06DF <= code && code <= 0x06E4) || // Mn [6] ARABIC SMALL HIGH ROUNDED ZERO..ARABIC SMALL HIGH MADDA\r\n\t\t(0x06E7 <= code && code <= 0x06E8) || // Mn [2] ARABIC SMALL HIGH YEH..ARABIC SMALL HIGH NOON\r\n\t\t(0x06EA <= code && code <= 0x06ED) || // Mn [4] ARABIC EMPTY CENTRE LOW STOP..ARABIC SMALL LOW MEEM\r\n\t\t0x0711 == code || // Mn SYRIAC LETTER SUPERSCRIPT ALAPH\r\n\t\t(0x0730 <= code && code <= 0x074A) || // Mn [27] SYRIAC PTHAHA ABOVE..SYRIAC BARREKH\r\n\t\t(0x07A6 <= code && code <= 0x07B0) || // Mn [11] THAANA ABAFILI..THAANA SUKUN\r\n\t\t(0x07EB <= code && code <= 0x07F3) || // Mn [9] NKO COMBINING SHORT HIGH TONE..NKO COMBINING DOUBLE DOT ABOVE\r\n\t\t(0x0816 <= code && code <= 0x0819) || // Mn [4] SAMARITAN MARK IN..SAMARITAN MARK DAGESH\r\n\t\t(0x081B <= code && code <= 0x0823) || // Mn [9] SAMARITAN MARK EPENTHETIC YUT..SAMARITAN VOWEL SIGN A\r\n\t\t(0x0825 <= code && code <= 0x0827) || // Mn [3] SAMARITAN VOWEL SIGN SHORT A..SAMARITAN VOWEL SIGN U\r\n\t\t(0x0829 <= code && code <= 0x082D) || // Mn [5] SAMARITAN VOWEL SIGN LONG I..SAMARITAN MARK NEQUDAA\r\n\t\t(0x0859 <= code && code <= 0x085B) || // Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK\r\n\t\t(0x08D4 <= code && code <= 0x08E1) || // Mn [14] ARABIC SMALL HIGH WORD AR-RUB..ARABIC SMALL HIGH SIGN SAFHA\r\n\t\t(0x08E3 <= code && code <= 0x0902) || // Mn [32] ARABIC TURNED DAMMA BELOW..DEVANAGARI SIGN ANUSVARA\r\n\t\t0x093A == code || // Mn DEVANAGARI VOWEL SIGN OE\r\n\t\t0x093C == code || // Mn DEVANAGARI SIGN NUKTA\r\n\t\t(0x0941 <= code && code <= 0x0948) || // Mn [8] DEVANAGARI VOWEL SIGN U..DEVANAGARI VOWEL SIGN AI\r\n\t\t0x094D == code || // Mn DEVANAGARI SIGN VIRAMA\r\n\t\t(0x0951 <= code && code <= 0x0957) || // Mn [7] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI VOWEL SIGN UUE\r\n\t\t(0x0962 <= code && code <= 0x0963) || // Mn [2] DEVANAGARI VOWEL SIGN VOCALIC L..DEVANAGARI VOWEL SIGN VOCALIC LL\r\n\t\t0x0981 == code || // Mn BENGALI SIGN CANDRABINDU\r\n\t\t0x09BC == code || // Mn BENGALI SIGN NUKTA\r\n\t\t0x09BE == code || // Mc BENGALI VOWEL SIGN AA\r\n\t\t(0x09C1 <= code && code <= 0x09C4) || // Mn [4] BENGALI VOWEL SIGN U..BENGALI VOWEL SIGN VOCALIC RR\r\n\t\t0x09CD == code || // Mn BENGALI SIGN VIRAMA\r\n\t\t0x09D7 == code || // Mc BENGALI AU LENGTH MARK\r\n\t\t(0x09E2 <= code && code <= 0x09E3) || // Mn [2] BENGALI VOWEL SIGN VOCALIC L..BENGALI VOWEL SIGN VOCALIC LL\r\n\t\t(0x0A01 <= code && code <= 0x0A02) || // Mn [2] GURMUKHI SIGN ADAK BINDI..GURMUKHI SIGN BINDI\r\n\t\t0x0A3C == code || // Mn GURMUKHI SIGN NUKTA\r\n\t\t(0x0A41 <= code && code <= 0x0A42) || // Mn [2] GURMUKHI VOWEL SIGN U..GURMUKHI VOWEL SIGN UU\r\n\t\t(0x0A47 <= code && code <= 0x0A48) || // Mn [2] GURMUKHI VOWEL SIGN EE..GURMUKHI VOWEL SIGN AI\r\n\t\t(0x0A4B <= code && code <= 0x0A4D) || // Mn [3] GURMUKHI VOWEL SIGN OO..GURMUKHI SIGN VIRAMA\r\n\t\t0x0A51 == code || // Mn GURMUKHI SIGN UDAAT\r\n\t\t(0x0A70 <= code && code <= 0x0A71) || // Mn [2] GURMUKHI TIPPI..GURMUKHI ADDAK\r\n\t\t0x0A75 == code || // Mn GURMUKHI SIGN YAKASH\r\n\t\t(0x0A81 <= code && code <= 0x0A82) || // Mn [2] GUJARATI SIGN CANDRABINDU..GUJARATI SIGN ANUSVARA\r\n\t\t0x0ABC == code || // Mn GUJARATI SIGN NUKTA\r\n\t\t(0x0AC1 <= code && code <= 0x0AC5) || // Mn [5] GUJARATI VOWEL SIGN U..GUJARATI VOWEL SIGN CANDRA E\r\n\t\t(0x0AC7 <= code && code <= 0x0AC8) || // Mn [2] GUJARATI VOWEL SIGN E..GUJARATI VOWEL SIGN AI\r\n\t\t0x0ACD == code || // Mn GUJARATI SIGN VIRAMA\r\n\t\t(0x0AE2 <= code && code <= 0x0AE3) || // Mn [2] GUJARATI VOWEL SIGN VOCALIC L..GUJARATI VOWEL SIGN VOCALIC LL\r\n\t\t(0x0AFA <= code && code <= 0x0AFF) || // Mn [6] GUJARATI SIGN SUKUN..GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE\r\n\t\t0x0B01 == code || // Mn ORIYA SIGN CANDRABINDU\r\n\t\t0x0B3C == code || // Mn ORIYA SIGN NUKTA\r\n\t\t0x0B3E == code || // Mc ORIYA VOWEL SIGN AA\r\n\t\t0x0B3F == code || // Mn ORIYA VOWEL SIGN I\r\n\t\t(0x0B41 <= code && code <= 0x0B44) || // Mn [4] ORIYA VOWEL SIGN U..ORIYA VOWEL SIGN VOCALIC RR\r\n\t\t0x0B4D == code || // Mn ORIYA SIGN VIRAMA\r\n\t\t0x0B56 == code || // Mn ORIYA AI LENGTH MARK\r\n\t\t0x0B57 == code || // Mc ORIYA AU LENGTH MARK\r\n\t\t(0x0B62 <= code && code <= 0x0B63) || // Mn [2] ORIYA VOWEL SIGN VOCALIC L..ORIYA VOWEL SIGN VOCALIC LL\r\n\t\t0x0B82 == code || // Mn TAMIL SIGN ANUSVARA\r\n\t\t0x0BBE == code || // Mc TAMIL VOWEL SIGN AA\r\n\t\t0x0BC0 == code || // Mn TAMIL VOWEL SIGN II\r\n\t\t0x0BCD == code || // Mn TAMIL SIGN VIRAMA\r\n\t\t0x0BD7 == code || // Mc TAMIL AU LENGTH MARK\r\n\t\t0x0C00 == code || // Mn TELUGU SIGN COMBINING CANDRABINDU ABOVE\r\n\t\t(0x0C3E <= code && code <= 0x0C40) || // Mn [3] TELUGU VOWEL SIGN AA..TELUGU VOWEL SIGN II\r\n\t\t(0x0C46 <= code && code <= 0x0C48) || // Mn [3] TELUGU VOWEL SIGN E..TELUGU VOWEL SIGN AI\r\n\t\t(0x0C4A <= code && code <= 0x0C4D) || // Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA\r\n\t\t(0x0C55 <= code && code <= 0x0C56) || // Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK\r\n\t\t(0x0C62 <= code && code <= 0x0C63) || // Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL\r\n\t\t0x0C81 == code || // Mn KANNADA SIGN CANDRABINDU\r\n\t\t0x0CBC == code || // Mn KANNADA SIGN NUKTA\r\n\t\t0x0CBF == code || // Mn KANNADA VOWEL SIGN I\r\n\t\t0x0CC2 == code || // Mc KANNADA VOWEL SIGN UU\r\n\t\t0x0CC6 == code || // Mn KANNADA VOWEL SIGN E\r\n\t\t(0x0CCC <= code && code <= 0x0CCD) || // Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA\r\n\t\t(0x0CD5 <= code && code <= 0x0CD6) || // Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK\r\n\t\t(0x0CE2 <= code && code <= 0x0CE3) || // Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL\r\n\t\t(0x0D00 <= code && code <= 0x0D01) || // Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU\r\n\t\t(0x0D3B <= code && code <= 0x0D3C) || // Mn [2] MALAYALAM SIGN VERTICAL BAR VIRAMA..MALAYALAM SIGN CIRCULAR VIRAMA\r\n\t\t0x0D3E == code || // Mc MALAYALAM VOWEL SIGN AA\r\n\t\t(0x0D41 <= code && code <= 0x0D44) || // Mn [4] MALAYALAM VOWEL SIGN U..MALAYALAM VOWEL SIGN VOCALIC RR\r\n\t\t0x0D4D == code || // Mn MALAYALAM SIGN VIRAMA\r\n\t\t0x0D57 == code || // Mc MALAYALAM AU LENGTH MARK\r\n\t\t(0x0D62 <= code && code <= 0x0D63) || // Mn [2] MALAYALAM VOWEL SIGN VOCALIC L..MALAYALAM VOWEL SIGN VOCALIC LL\r\n\t\t0x0DCA == code || // Mn SINHALA SIGN AL-LAKUNA\r\n\t\t0x0DCF == code || // Mc SINHALA VOWEL SIGN AELA-PILLA\r\n\t\t(0x0DD2 <= code && code <= 0x0DD4) || // Mn [3] SINHALA VOWEL SIGN KETTI IS-PILLA..SINHALA VOWEL SIGN KETTI PAA-PILLA\r\n\t\t0x0DD6 == code || // Mn SINHALA VOWEL SIGN DIGA PAA-PILLA\r\n\t\t0x0DDF == code || // Mc SINHALA VOWEL SIGN GAYANUKITTA\r\n\t\t0x0E31 == code || // Mn THAI CHARACTER MAI HAN-AKAT\r\n\t\t(0x0E34 <= code && code <= 0x0E3A) || // Mn [7] THAI CHARACTER SARA I..THAI CHARACTER PHINTHU\r\n\t\t(0x0E47 <= code && code <= 0x0E4E) || // Mn [8] THAI CHARACTER MAITAIKHU..THAI CHARACTER YAMAKKAN\r\n\t\t0x0EB1 == code || // Mn LAO VOWEL SIGN MAI KAN\r\n\t\t(0x0EB4 <= code && code <= 0x0EB9) || // Mn [6] LAO VOWEL SIGN I..LAO VOWEL SIGN UU\r\n\t\t(0x0EBB <= code && code <= 0x0EBC) || // Mn [2] LAO VOWEL SIGN MAI KON..LAO SEMIVOWEL SIGN LO\r\n\t\t(0x0EC8 <= code && code <= 0x0ECD) || // Mn [6] LAO TONE MAI EK..LAO NIGGAHITA\r\n\t\t(0x0F18 <= code && code <= 0x0F19) || // Mn [2] TIBETAN ASTROLOGICAL SIGN -KHYUD PA..TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS\r\n\t\t0x0F35 == code || // Mn TIBETAN MARK NGAS BZUNG NYI ZLA\r\n\t\t0x0F37 == code || // Mn TIBETAN MARK NGAS BZUNG SGOR RTAGS\r\n\t\t0x0F39 == code || // Mn TIBETAN MARK TSA -PHRU\r\n\t\t(0x0F71 <= code && code <= 0x0F7E) || // Mn [14] TIBETAN VOWEL SIGN AA..TIBETAN SIGN RJES SU NGA RO\r\n\t\t(0x0F80 <= code && code <= 0x0F84) || // Mn [5] TIBETAN VOWEL SIGN REVERSED I..TIBETAN MARK HALANTA\r\n\t\t(0x0F86 <= code && code <= 0x0F87) || // Mn [2] TIBETAN SIGN LCI RTAGS..TIBETAN SIGN YANG RTAGS\r\n\t\t(0x0F8D <= code && code <= 0x0F97) || // Mn [11] TIBETAN SUBJOINED SIGN LCE TSA CAN..TIBETAN SUBJOINED LETTER JA\r\n\t\t(0x0F99 <= code && code <= 0x0FBC) || // Mn [36] TIBETAN SUBJOINED LETTER NYA..TIBETAN SUBJOINED LETTER FIXED-FORM RA\r\n\t\t0x0FC6 == code || // Mn TIBETAN SYMBOL PADMA GDAN\r\n\t\t(0x102D <= code && code <= 0x1030) || // Mn [4] MYANMAR VOWEL SIGN I..MYANMAR VOWEL SIGN UU\r\n\t\t(0x1032 <= code && code <= 0x1037) || // Mn [6] MYANMAR VOWEL SIGN AI..MYANMAR SIGN DOT BELOW\r\n\t\t(0x1039 <= code && code <= 0x103A) || // Mn [2] MYANMAR SIGN VIRAMA..MYANMAR SIGN ASAT\r\n\t\t(0x103D <= code && code <= 0x103E) || // Mn [2] MYANMAR CONSONANT SIGN MEDIAL WA..MYANMAR CONSONANT SIGN MEDIAL HA\r\n\t\t(0x1058 <= code && code <= 0x1059) || // Mn [2] MYANMAR VOWEL SIGN VOCALIC L..MYANMAR VOWEL SIGN VOCALIC LL\r\n\t\t(0x105E <= code && code <= 0x1060) || // Mn [3] MYANMAR CONSONANT SIGN MON MEDIAL NA..MYANMAR CONSONANT SIGN MON MEDIAL LA\r\n\t\t(0x1071 <= code && code <= 0x1074) || // Mn [4] MYANMAR VOWEL SIGN GEBA KAREN I..MYANMAR VOWEL SIGN KAYAH EE\r\n\t\t0x1082 == code || // Mn MYANMAR CONSONANT SIGN SHAN MEDIAL WA\r\n\t\t(0x1085 <= code && code <= 0x1086) || // Mn [2] MYANMAR VOWEL SIGN SHAN E ABOVE..MYANMAR VOWEL SIGN SHAN FINAL Y\r\n\t\t0x108D == code || // Mn MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE\r\n\t\t0x109D == code || // Mn MYANMAR VOWEL SIGN AITON AI\r\n\t\t(0x135D <= code && code <= 0x135F) || // Mn [3] ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK..ETHIOPIC COMBINING GEMINATION MARK\r\n\t\t(0x1712 <= code && code <= 0x1714) || // Mn [3] TAGALOG VOWEL SIGN I..TAGALOG SIGN VIRAMA\r\n\t\t(0x1732 <= code && code <= 0x1734) || // Mn [3] HANUNOO VOWEL SIGN I..HANUNOO SIGN PAMUDPOD\r\n\t\t(0x1752 <= code && code <= 0x1753) || // Mn [2] BUHID VOWEL SIGN I..BUHID VOWEL SIGN U\r\n\t\t(0x1772 <= code && code <= 0x1773) || // Mn [2] TAGBANWA VOWEL SIGN I..TAGBANWA VOWEL SIGN U\r\n\t\t(0x17B4 <= code && code <= 0x17B5) || // Mn [2] KHMER VOWEL INHERENT AQ..KHMER VOWEL INHERENT AA\r\n\t\t(0x17B7 <= code && code <= 0x17BD) || // Mn [7] KHMER VOWEL SIGN I..KHMER VOWEL SIGN UA\r\n\t\t0x17C6 == code || // Mn KHMER SIGN NIKAHIT\r\n\t\t(0x17C9 <= code && code <= 0x17D3) || // Mn [11] KHMER SIGN MUUSIKATOAN..KHMER SIGN BATHAMASAT\r\n\t\t0x17DD == code || // Mn KHMER SIGN ATTHACAN\r\n\t\t(0x180B <= code && code <= 0x180D) || // Mn [3] MONGOLIAN FREE VARIATION SELECTOR ONE..MONGOLIAN FREE VARIATION SELECTOR THREE\r\n\t\t(0x1885 <= code && code <= 0x1886) || // Mn [2] MONGOLIAN LETTER ALI GALI BALUDA..MONGOLIAN LETTER ALI GALI THREE BALUDA\r\n\t\t0x18A9 == code || // Mn MONGOLIAN LETTER ALI GALI DAGALGA\r\n\t\t(0x1920 <= code && code <= 0x1922) || // Mn [3] LIMBU VOWEL SIGN A..LIMBU VOWEL SIGN U\r\n\t\t(0x1927 <= code && code <= 0x1928) || // Mn [2] LIMBU VOWEL SIGN E..LIMBU VOWEL SIGN O\r\n\t\t0x1932 == code || // Mn LIMBU SMALL LETTER ANUSVARA\r\n\t\t(0x1939 <= code && code <= 0x193B) || // Mn [3] LIMBU SIGN MUKPHRENG..LIMBU SIGN SA-I\r\n\t\t(0x1A17 <= code && code <= 0x1A18) || // Mn [2] BUGINESE VOWEL SIGN I..BUGINESE VOWEL SIGN U\r\n\t\t0x1A1B == code || // Mn BUGINESE VOWEL SIGN AE\r\n\t\t0x1A56 == code || // Mn TAI THAM CONSONANT SIGN MEDIAL LA\r\n\t\t(0x1A58 <= code && code <= 0x1A5E) || // Mn [7] TAI THAM SIGN MAI KANG LAI..TAI THAM CONSONANT SIGN SA\r\n\t\t0x1A60 == code || // Mn TAI THAM SIGN SAKOT\r\n\t\t0x1A62 == code || // Mn TAI THAM VOWEL SIGN MAI SAT\r\n\t\t(0x1A65 <= code && code <= 0x1A6C) || // Mn [8] TAI THAM VOWEL SIGN I..TAI THAM VOWEL SIGN OA BELOW\r\n\t\t(0x1A73 <= code && code <= 0x1A7C) || // Mn [10] TAI THAM VOWEL SIGN OA ABOVE..TAI THAM SIGN KHUEN-LUE KARAN\r\n\t\t0x1A7F == code || // Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT\r\n\t\t(0x1AB0 <= code && code <= 0x1ABD) || // Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW\r\n\t\t0x1ABE == code || // Me COMBINING PARENTHESES OVERLAY\r\n\t\t(0x1B00 <= code && code <= 0x1B03) || // Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG\r\n\t\t0x1B34 == code || // Mn BALINESE SIGN REREKAN\r\n\t\t(0x1B36 <= code && code <= 0x1B3A) || // Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA\r\n\t\t0x1B3C == code || // Mn BALINESE VOWEL SIGN LA LENGA\r\n\t\t0x1B42 == code || // Mn BALINESE VOWEL SIGN PEPET\r\n\t\t(0x1B6B <= code && code <= 0x1B73) || // Mn [9] BALINESE MUSICAL SYMBOL COMBINING TEGEH..BALINESE MUSICAL SYMBOL COMBINING GONG\r\n\t\t(0x1B80 <= code && code <= 0x1B81) || // Mn [2] SUNDANESE SIGN PANYECEK..SUNDANESE SIGN PANGLAYAR\r\n\t\t(0x1BA2 <= code && code <= 0x1BA5) || // Mn [4] SUNDANESE CONSONANT SIGN PANYAKRA..SUNDANESE VOWEL SIGN PANYUKU\r\n\t\t(0x1BA8 <= code && code <= 0x1BA9) || // Mn [2] SUNDANESE VOWEL SIGN PAMEPET..SUNDANESE VOWEL SIGN PANEULEUNG\r\n\t\t(0x1BAB <= code && code <= 0x1BAD) || // Mn [3] SUNDANESE SIGN VIRAMA..SUNDANESE CONSONANT SIGN PASANGAN WA\r\n\t\t0x1BE6 == code || // Mn BATAK SIGN TOMPI\r\n\t\t(0x1BE8 <= code && code <= 0x1BE9) || // Mn [2] BATAK VOWEL SIGN PAKPAK E..BATAK VOWEL SIGN EE\r\n\t\t0x1BED == code || // Mn BATAK VOWEL SIGN KARO O\r\n\t\t(0x1BEF <= code && code <= 0x1BF1) || // Mn [3] BATAK VOWEL SIGN U FOR SIMALUNGUN SA..BATAK CONSONANT SIGN H\r\n\t\t(0x1C2C <= code && code <= 0x1C33) || // Mn [8] LEPCHA VOWEL SIGN E..LEPCHA CONSONANT SIGN T\r\n\t\t(0x1C36 <= code && code <= 0x1C37) || // Mn [2] LEPCHA SIGN RAN..LEPCHA SIGN NUKTA\r\n\t\t(0x1CD0 <= code && code <= 0x1CD2) || // Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA\r\n\t\t(0x1CD4 <= code && code <= 0x1CE0) || // Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA\r\n\t\t(0x1CE2 <= code && code <= 0x1CE8) || // Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL\r\n\t\t0x1CED == code || // Mn VEDIC SIGN TIRYAK\r\n\t\t0x1CF4 == code || // Mn VEDIC TONE CANDRA ABOVE\r\n\t\t(0x1CF8 <= code && code <= 0x1CF9) || // Mn [2] VEDIC TONE RING ABOVE..VEDIC TONE DOUBLE RING ABOVE\r\n\t\t(0x1DC0 <= code && code <= 0x1DF9) || // Mn [58] COMBINING DOTTED GRAVE ACCENT..COMBINING WIDE INVERTED BRIDGE BELOW\r\n\t\t(0x1DFB <= code && code <= 0x1DFF) || // Mn [5] COMBINING DELETION MARK..COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW\r\n\t\t0x200C == code || // Cf ZERO WIDTH NON-JOINER\r\n\t\t(0x20D0 <= code && code <= 0x20DC) || // Mn [13] COMBINING LEFT HARPOON ABOVE..COMBINING FOUR DOTS ABOVE\r\n\t\t(0x20DD <= code && code <= 0x20E0) || // Me [4] COMBINING ENCLOSING CIRCLE..COMBINING ENCLOSING CIRCLE BACKSLASH\r\n\t\t0x20E1 == code || // Mn COMBINING LEFT RIGHT ARROW ABOVE\r\n\t\t(0x20E2 <= code && code <= 0x20E4) || // Me [3] COMBINING ENCLOSING SCREEN..COMBINING ENCLOSING UPWARD POINTING TRIANGLE\r\n\t\t(0x20E5 <= code && code <= 0x20F0) || // Mn [12] COMBINING REVERSE SOLIDUS OVERLAY..COMBINING ASTERISK ABOVE\r\n\t\t(0x2CEF <= code && code <= 0x2CF1) || // Mn [3] COPTIC COMBINING NI ABOVE..COPTIC COMBINING SPIRITUS LENIS\r\n\t\t0x2D7F == code || // Mn TIFINAGH CONSONANT JOINER\r\n\t\t(0x2DE0 <= code && code <= 0x2DFF) || // Mn [32] COMBINING CYRILLIC LETTER BE..COMBINING CYRILLIC LETTER IOTIFIED BIG YUS\r\n\t\t(0x302A <= code && code <= 0x302D) || // Mn [4] IDEOGRAPHIC LEVEL TONE MARK..IDEOGRAPHIC ENTERING TONE MARK\r\n\t\t(0x302E <= code && code <= 0x302F) || // Mc [2] HANGUL SINGLE DOT TONE MARK..HANGUL DOUBLE DOT TONE MARK\r\n\t\t(0x3099 <= code && code <= 0x309A) || // Mn [2] COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK..COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK\r\n\t\t0xA66F == code || // Mn COMBINING CYRILLIC VZMET\r\n\t\t(0xA670 <= code && code <= 0xA672) || // Me [3] COMBINING CYRILLIC TEN MILLIONS SIGN..COMBINING CYRILLIC THOUSAND MILLIONS SIGN\r\n\t\t(0xA674 <= code && code <= 0xA67D) || // Mn [10] COMBINING CYRILLIC LETTER UKRAINIAN IE..COMBINING CYRILLIC PAYEROK\r\n\t\t(0xA69E <= code && code <= 0xA69F) || // Mn [2] COMBINING CYRILLIC LETTER EF..COMBINING CYRILLIC LETTER IOTIFIED E\r\n\t\t(0xA6F0 <= code && code <= 0xA6F1) || // Mn [2] BAMUM COMBINING MARK KOQNDON..BAMUM COMBINING MARK TUKWENTIS\r\n\t\t0xA802 == code || // Mn SYLOTI NAGRI SIGN DVISVARA\r\n\t\t0xA806 == code || // Mn SYLOTI NAGRI SIGN HASANTA\r\n\t\t0xA80B == code || // Mn SYLOTI NAGRI SIGN ANUSVARA\r\n\t\t(0xA825 <= code && code <= 0xA826) || // Mn [2] SYLOTI NAGRI VOWEL SIGN U..SYLOTI NAGRI VOWEL SIGN E\r\n\t\t(0xA8C4 <= code && code <= 0xA8C5) || // Mn [2] SAURASHTRA SIGN VIRAMA..SAURASHTRA SIGN CANDRABINDU\r\n\t\t(0xA8E0 <= code && code <= 0xA8F1) || // Mn [18] COMBINING DEVANAGARI DIGIT ZERO..COMBINING DEVANAGARI SIGN AVAGRAHA\r\n\t\t(0xA926 <= code && code <= 0xA92D) || // Mn [8] KAYAH LI VOWEL UE..KAYAH LI TONE CALYA PLOPHU\r\n\t\t(0xA947 <= code && code <= 0xA951) || // Mn [11] REJANG VOWEL SIGN I..REJANG CONSONANT SIGN R\r\n\t\t(0xA980 <= code && code <= 0xA982) || // Mn [3] JAVANESE SIGN PANYANGGA..JAVANESE SIGN LAYAR\r\n\t\t0xA9B3 == code || // Mn JAVANESE SIGN CECAK TELU\r\n\t\t(0xA9B6 <= code && code <= 0xA9B9) || // Mn [4] JAVANESE VOWEL SIGN WULU..JAVANESE VOWEL SIGN SUKU MENDUT\r\n\t\t0xA9BC == code || // Mn JAVANESE VOWEL SIGN PEPET\r\n\t\t0xA9E5 == code || // Mn MYANMAR SIGN SHAN SAW\r\n\t\t(0xAA29 <= code && code <= 0xAA2E) || // Mn [6] CHAM VOWEL SIGN AA..CHAM VOWEL SIGN OE\r\n\t\t(0xAA31 <= code && code <= 0xAA32) || // Mn [2] CHAM VOWEL SIGN AU..CHAM VOWEL SIGN UE\r\n\t\t(0xAA35 <= code && code <= 0xAA36) || // Mn [2] CHAM CONSONANT SIGN LA..CHAM CONSONANT SIGN WA\r\n\t\t0xAA43 == code || // Mn CHAM CONSONANT SIGN FINAL NG\r\n\t\t0xAA4C == code || // Mn CHAM CONSONANT SIGN FINAL M\r\n\t\t0xAA7C == code || // Mn MYANMAR SIGN TAI LAING TONE-2\r\n\t\t0xAAB0 == code || // Mn TAI VIET MAI KANG\r\n\t\t(0xAAB2 <= code && code <= 0xAAB4) || // Mn [3] TAI VIET VOWEL I..TAI VIET VOWEL U\r\n\t\t(0xAAB7 <= code && code <= 0xAAB8) || // Mn [2] TAI VIET MAI KHIT..TAI VIET VOWEL IA\r\n\t\t(0xAABE <= code && code <= 0xAABF) || // Mn [2] TAI VIET VOWEL AM..TAI VIET TONE MAI EK\r\n\t\t0xAAC1 == code || // Mn TAI VIET TONE MAI THO\r\n\t\t(0xAAEC <= code && code <= 0xAAED) || // Mn [2] MEETEI MAYEK VOWEL SIGN UU..MEETEI MAYEK VOWEL SIGN AAI\r\n\t\t0xAAF6 == code || // Mn MEETEI MAYEK VIRAMA\r\n\t\t0xABE5 == code || // Mn MEETEI MAYEK VOWEL SIGN ANAP\r\n\t\t0xABE8 == code || // Mn MEETEI MAYEK VOWEL SIGN UNAP\r\n\t\t0xABED == code || // Mn MEETEI MAYEK APUN IYEK\r\n\t\t0xFB1E == code || // Mn HEBREW POINT JUDEO-SPANISH VARIKA\r\n\t\t(0xFE00 <= code && code <= 0xFE0F) || // Mn [16] VARIATION SELECTOR-1..VARIATION SELECTOR-16\r\n\t\t(0xFE20 <= code && code <= 0xFE2F) || // Mn [16] COMBINING LIGATURE LEFT HALF..COMBINING CYRILLIC TITLO RIGHT HALF\r\n\t\t(0xFF9E <= code && code <= 0xFF9F) || // Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK\r\n\t\t0x101FD == code || // Mn PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE\r\n\t\t0x102E0 == code || // Mn COPTIC EPACT THOUSANDS MARK\r\n\t\t(0x10376 <= code && code <= 0x1037A) || // Mn [5] COMBINING OLD PERMIC LETTER AN..COMBINING OLD PERMIC LETTER SII\r\n\t\t(0x10A01 <= code && code <= 0x10A03) || // Mn [3] KHAROSHTHI VOWEL SIGN I..KHAROSHTHI VOWEL SIGN VOCALIC R\r\n\t\t(0x10A05 <= code && code <= 0x10A06) || // Mn [2] KHAROSHTHI VOWEL SIGN E..KHAROSHTHI VOWEL SIGN O\r\n\t\t(0x10A0C <= code && code <= 0x10A0F) || // Mn [4] KHAROSHTHI VOWEL LENGTH MARK..KHAROSHTHI SIGN VISARGA\r\n\t\t(0x10A38 <= code && code <= 0x10A3A) || // Mn [3] KHAROSHTHI SIGN BAR ABOVE..KHAROSHTHI SIGN DOT BELOW\r\n\t\t0x10A3F == code || // Mn KHAROSHTHI VIRAMA\r\n\t\t(0x10AE5 <= code && code <= 0x10AE6) || // Mn [2] MANICHAEAN ABBREVIATION MARK ABOVE..MANICHAEAN ABBREVIATION MARK BELOW\r\n\t\t0x11001 == code || // Mn BRAHMI SIGN ANUSVARA\r\n\t\t(0x11038 <= code && code <= 0x11046) || // Mn [15] BRAHMI VOWEL SIGN AA..BRAHMI VIRAMA\r\n\t\t(0x1107F <= code && code <= 0x11081) || // Mn [3] BRAHMI NUMBER JOINER..KAITHI SIGN ANUSVARA\r\n\t\t(0x110B3 <= code && code <= 0x110B6) || // Mn [4] KAITHI VOWEL SIGN U..KAITHI VOWEL SIGN AI\r\n\t\t(0x110B9 <= code && code <= 0x110BA) || // Mn [2] KAITHI SIGN VIRAMA..KAITHI SIGN NUKTA\r\n\t\t(0x11100 <= code && code <= 0x11102) || // Mn [3] CHAKMA SIGN CANDRABINDU..CHAKMA SIGN VISARGA\r\n\t\t(0x11127 <= code && code <= 0x1112B) || // Mn [5] CHAKMA VOWEL SIGN A..CHAKMA VOWEL SIGN UU\r\n\t\t(0x1112D <= code && code <= 0x11134) || // Mn [8] CHAKMA VOWEL SIGN AI..CHAKMA MAAYYAA\r\n\t\t0x11173 == code || // Mn MAHAJANI SIGN NUKTA\r\n\t\t(0x11180 <= code && code <= 0x11181) || // Mn [2] SHARADA SIGN CANDRABINDU..SHARADA SIGN ANUSVARA\r\n\t\t(0x111B6 <= code && code <= 0x111BE) || // Mn [9] SHARADA VOWEL SIGN U..SHARADA VOWEL SIGN O\r\n\t\t(0x111CA <= code && code <= 0x111CC) || // Mn [3] SHARADA SIGN NUKTA..SHARADA EXTRA SHORT VOWEL MARK\r\n\t\t(0x1122F <= code && code <= 0x11231) || // Mn [3] KHOJKI VOWEL SIGN U..KHOJKI VOWEL SIGN AI\r\n\t\t0x11234 == code || // Mn KHOJKI SIGN ANUSVARA\r\n\t\t(0x11236 <= code && code <= 0x11237) || // Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA\r\n\t\t0x1123E == code || // Mn KHOJKI SIGN SUKUN\r\n\t\t0x112DF == code || // Mn KHUDAWADI SIGN ANUSVARA\r\n\t\t(0x112E3 <= code && code <= 0x112EA) || // Mn [8] KHUDAWADI VOWEL SIGN U..KHUDAWADI SIGN VIRAMA\r\n\t\t(0x11300 <= code && code <= 0x11301) || // Mn [2] GRANTHA SIGN COMBINING ANUSVARA ABOVE..GRANTHA SIGN CANDRABINDU\r\n\t\t0x1133C == code || // Mn GRANTHA SIGN NUKTA\r\n\t\t0x1133E == code || // Mc GRANTHA VOWEL SIGN AA\r\n\t\t0x11340 == code || // Mn GRANTHA VOWEL SIGN II\r\n\t\t0x11357 == code || // Mc GRANTHA AU LENGTH MARK\r\n\t\t(0x11366 <= code && code <= 0x1136C) || // Mn [7] COMBINING GRANTHA DIGIT ZERO..COMBINING GRANTHA DIGIT SIX\r\n\t\t(0x11370 <= code && code <= 0x11374) || // Mn [5] COMBINING GRANTHA LETTER A..COMBINING GRANTHA LETTER PA\r\n\t\t(0x11438 <= code && code <= 0x1143F) || // Mn [8] NEWA VOWEL SIGN U..NEWA VOWEL SIGN AI\r\n\t\t(0x11442 <= code && code <= 0x11444) || // Mn [3] NEWA SIGN VIRAMA..NEWA SIGN ANUSVARA\r\n\t\t0x11446 == code || // Mn NEWA SIGN NUKTA\r\n\t\t0x114B0 == code || // Mc TIRHUTA VOWEL SIGN AA\r\n\t\t(0x114B3 <= code && code <= 0x114B8) || // Mn [6] TIRHUTA VOWEL SIGN U..TIRHUTA VOWEL SIGN VOCALIC LL\r\n\t\t0x114BA == code || // Mn TIRHUTA VOWEL SIGN SHORT E\r\n\t\t0x114BD == code || // Mc TIRHUTA VOWEL SIGN SHORT O\r\n\t\t(0x114BF <= code && code <= 0x114C0) || // Mn [2] TIRHUTA SIGN CANDRABINDU..TIRHUTA SIGN ANUSVARA\r\n\t\t(0x114C2 <= code && code <= 0x114C3) || // Mn [2] TIRHUTA SIGN VIRAMA..TIRHUTA SIGN NUKTA\r\n\t\t0x115AF == code || // Mc SIDDHAM VOWEL SIGN AA\r\n\t\t(0x115B2 <= code && code <= 0x115B5) || // Mn [4] SIDDHAM VOWEL SIGN U..SIDDHAM VOWEL SIGN VOCALIC RR\r\n\t\t(0x115BC <= code && code <= 0x115BD) || // Mn [2] SIDDHAM SIGN CANDRABINDU..SIDDHAM SIGN ANUSVARA\r\n\t\t(0x115BF <= code && code <= 0x115C0) || // Mn [2] SIDDHAM SIGN VIRAMA..SIDDHAM SIGN NUKTA\r\n\t\t(0x115DC <= code && code <= 0x115DD) || // Mn [2] SIDDHAM VOWEL SIGN ALTERNATE U..SIDDHAM VOWEL SIGN ALTERNATE UU\r\n\t\t(0x11633 <= code && code <= 0x1163A) || // Mn [8] MODI VOWEL SIGN U..MODI VOWEL SIGN AI\r\n\t\t0x1163D == code || // Mn MODI SIGN ANUSVARA\r\n\t\t(0x1163F <= code && code <= 0x11640) || // Mn [2] MODI SIGN VIRAMA..MODI SIGN ARDHACANDRA\r\n\t\t0x116AB == code || // Mn TAKRI SIGN ANUSVARA\r\n\t\t0x116AD == code || // Mn TAKRI VOWEL SIGN AA\r\n\t\t(0x116B0 <= code && code <= 0x116B5) || // Mn [6] TAKRI VOWEL SIGN U..TAKRI VOWEL SIGN AU\r\n\t\t0x116B7 == code || // Mn TAKRI SIGN NUKTA\r\n\t\t(0x1171D <= code && code <= 0x1171F) || // Mn [3] AHOM CONSONANT SIGN MEDIAL LA..AHOM CONSONANT SIGN MEDIAL LIGATING RA\r\n\t\t(0x11722 <= code && code <= 0x11725) || // Mn [4] AHOM VOWEL SIGN I..AHOM VOWEL SIGN UU\r\n\t\t(0x11727 <= code && code <= 0x1172B) || // Mn [5] AHOM VOWEL SIGN AW..AHOM SIGN KILLER\r\n\t\t(0x11A01 <= code && code <= 0x11A06) || // Mn [6] ZANABAZAR SQUARE VOWEL SIGN I..ZANABAZAR SQUARE VOWEL SIGN O\r\n\t\t(0x11A09 <= code && code <= 0x11A0A) || // Mn [2] ZANABAZAR SQUARE VOWEL SIGN REVERSED I..ZANABAZAR SQUARE VOWEL LENGTH MARK\r\n\t\t(0x11A33 <= code && code <= 0x11A38) || // Mn [6] ZANABAZAR SQUARE FINAL CONSONANT MARK..ZANABAZAR SQUARE SIGN ANUSVARA\r\n\t\t(0x11A3B <= code && code <= 0x11A3E) || // Mn [4] ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA..ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA\r\n\t\t0x11A47 == code || // Mn ZANABAZAR SQUARE SUBJOINER\r\n\t\t(0x11A51 <= code && code <= 0x11A56) || // Mn [6] SOYOMBO VOWEL SIGN I..SOYOMBO VOWEL SIGN OE\r\n\t\t(0x11A59 <= code && code <= 0x11A5B) || // Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK\r\n\t\t(0x11A8A <= code && code <= 0x11A96) || // Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA\r\n\t\t(0x11A98 <= code && code <= 0x11A99) || // Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER\r\n\t\t(0x11C30 <= code && code <= 0x11C36) || // Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L\r\n\t\t(0x11C38 <= code && code <= 0x11C3D) || // Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA\r\n\t\t0x11C3F == code || // Mn BHAIKSUKI SIGN VIRAMA\r\n\t\t(0x11C92 <= code && code <= 0x11CA7) || // Mn [22] MARCHEN SUBJOINED LETTER KA..MARCHEN SUBJOINED LETTER ZA\r\n\t\t(0x11CAA <= code && code <= 0x11CB0) || // Mn [7] MARCHEN SUBJOINED LETTER RA..MARCHEN VOWEL SIGN AA\r\n\t\t(0x11CB2 <= code && code <= 0x11CB3) || // Mn [2] MARCHEN VOWEL SIGN U..MARCHEN VOWEL SIGN E\r\n\t\t(0x11CB5 <= code && code <= 0x11CB6) || // Mn [2] MARCHEN SIGN ANUSVARA..MARCHEN SIGN CANDRABINDU\r\n\t\t(0x11D31 <= code && code <= 0x11D36) || // Mn [6] MASARAM GONDI VOWEL SIGN AA..MASARAM GONDI VOWEL SIGN VOCALIC R\r\n\t\t0x11D3A == code || // Mn MASARAM GONDI VOWEL SIGN E\r\n\t\t(0x11D3C <= code && code <= 0x11D3D) || // Mn [2] MASARAM GONDI VOWEL SIGN AI..MASARAM GONDI VOWEL SIGN O\r\n\t\t(0x11D3F <= code && code <= 0x11D45) || // Mn [7] MASARAM GONDI VOWEL SIGN AU..MASARAM GONDI VIRAMA\r\n\t\t0x11D47 == code || // Mn MASARAM GONDI RA-KARA\r\n\t\t(0x16AF0 <= code && code <= 0x16AF4) || // Mn [5] BASSA VAH COMBINING HIGH TONE..BASSA VAH COMBINING HIGH-LOW TONE\r\n\t\t(0x16B30 <= code && code <= 0x16B36) || // Mn [7] PAHAWH HMONG MARK CIM TUB..PAHAWH HMONG MARK CIM TAUM\r\n\t\t(0x16F8F <= code && code <= 0x16F92) || // Mn [4] MIAO TONE RIGHT..MIAO TONE BELOW\r\n\t\t(0x1BC9D <= code && code <= 0x1BC9E) || // Mn [2] DUPLOYAN THICK LETTER SELECTOR..DUPLOYAN DOUBLE MARK\r\n\t\t0x1D165 == code || // Mc MUSICAL SYMBOL COMBINING STEM\r\n\t\t(0x1D167 <= code && code <= 0x1D169) || // Mn [3] MUSICAL SYMBOL COMBINING TREMOLO-1..MUSICAL SYMBOL COMBINING TREMOLO-3\r\n\t\t(0x1D16E <= code && code <= 0x1D172) || // Mc [5] MUSICAL SYMBOL COMBINING FLAG-1..MUSICAL SYMBOL COMBINING FLAG-5\r\n\t\t(0x1D17B <= code && code <= 0x1D182) || // Mn [8] MUSICAL SYMBOL COMBINING ACCENT..MUSICAL SYMBOL COMBINING LOURE\r\n\t\t(0x1D185 <= code && code <= 0x1D18B) || // Mn [7] MUSICAL SYMBOL COMBINING DOIT..MUSICAL SYMBOL COMBINING TRIPLE TONGUE\r\n\t\t(0x1D1AA <= code && code <= 0x1D1AD) || // Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO\r\n\t\t(0x1D242 <= code && code <= 0x1D244) || // Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME\r\n\t\t(0x1DA00 <= code && code <= 0x1DA36) || // Mn [55] SIGNWRITING HEAD RIM..SIGNWRITING AIR SUCKING IN\r\n\t\t(0x1DA3B <= code && code <= 0x1DA6C) || // Mn [50] SIGNWRITING MOUTH CLOSED NEUTRAL..SIGNWRITING EXCITEMENT\r\n\t\t0x1DA75 == code || // Mn SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS\r\n\t\t0x1DA84 == code || // Mn SIGNWRITING LOCATION HEAD NECK\r\n\t\t(0x1DA9B <= code && code <= 0x1DA9F) || // Mn [5] SIGNWRITING FILL MODIFIER-2..SIGNWRITING FILL MODIFIER-6\r\n\t\t(0x1DAA1 <= code && code <= 0x1DAAF) || // Mn [15] SIGNWRITING ROTATION MODIFIER-2..SIGNWRITING ROTATION MODIFIER-16\r\n\t\t(0x1E000 <= code && code <= 0x1E006) || // Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE\r\n\t\t(0x1E008 <= code && code <= 0x1E018) || // Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU\r\n\t\t(0x1E01B <= code && code <= 0x1E021) || // Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI\r\n\t\t(0x1E023 <= code && code <= 0x1E024) || // Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS\r\n\t\t(0x1E026 <= code && code <= 0x1E02A) || // Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA\r\n\t\t(0x1E8D0 <= code && code <= 0x1E8D6) || // Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS\r\n\t\t(0x1E944 <= code && code <= 0x1E94A) || // Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA\r\n\t\t(0xE0020 <= code && code <= 0xE007F) || // Cf [96] TAG SPACE..CANCEL TAG\r\n\t\t(0xE0100 <= code && code <= 0xE01EF) // Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256\r\n\t\t){\r\n\t\t\treturn Extend;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\tif(\r\n\t\t(0x1F1E6 <= code && code <= 0x1F1FF) // So [26] REGIONAL INDICATOR SYMBOL LETTER A..REGIONAL INDICATOR SYMBOL LETTER Z\r\n\t\t){\r\n\t\t\treturn Regional_Indicator;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t0x0903 == code || // Mc DEVANAGARI SIGN VISARGA\r\n\t\t0x093B == code || // Mc DEVANAGARI VOWEL SIGN OOE\r\n\t\t(0x093E <= code && code <= 0x0940) || // Mc [3] DEVANAGARI VOWEL SIGN AA..DEVANAGARI VOWEL SIGN II\r\n\t\t(0x0949 <= code && code <= 0x094C) || // Mc [4] DEVANAGARI VOWEL SIGN CANDRA O..DEVANAGARI VOWEL SIGN AU\r\n\t\t(0x094E <= code && code <= 0x094F) || // Mc [2] DEVANAGARI VOWEL SIGN PRISHTHAMATRA E..DEVANAGARI VOWEL SIGN AW\r\n\t\t(0x0982 <= code && code <= 0x0983) || // Mc [2] BENGALI SIGN ANUSVARA..BENGALI SIGN VISARGA\r\n\t\t(0x09BF <= code && code <= 0x09C0) || // Mc [2] BENGALI VOWEL SIGN I..BENGALI VOWEL SIGN II\r\n\t\t(0x09C7 <= code && code <= 0x09C8) || // Mc [2] BENGALI VOWEL SIGN E..BENGALI VOWEL SIGN AI\r\n\t\t(0x09CB <= code && code <= 0x09CC) || // Mc [2] BENGALI VOWEL SIGN O..BENGALI VOWEL SIGN AU\r\n\t\t0x0A03 == code || // Mc GURMUKHI SIGN VISARGA\r\n\t\t(0x0A3E <= code && code <= 0x0A40) || // Mc [3] GURMUKHI VOWEL SIGN AA..GURMUKHI VOWEL SIGN II\r\n\t\t0x0A83 == code || // Mc GUJARATI SIGN VISARGA\r\n\t\t(0x0ABE <= code && code <= 0x0AC0) || // Mc [3] GUJARATI VOWEL SIGN AA..GUJARATI VOWEL SIGN II\r\n\t\t0x0AC9 == code || // Mc GUJARATI VOWEL SIGN CANDRA O\r\n\t\t(0x0ACB <= code && code <= 0x0ACC) || // Mc [2] GUJARATI VOWEL SIGN O..GUJARATI VOWEL SIGN AU\r\n\t\t(0x0B02 <= code && code <= 0x0B03) || // Mc [2] ORIYA SIGN ANUSVARA..ORIYA SIGN VISARGA\r\n\t\t0x0B40 == code || // Mc ORIYA VOWEL SIGN II\r\n\t\t(0x0B47 <= code && code <= 0x0B48) || // Mc [2] ORIYA VOWEL SIGN E..ORIYA VOWEL SIGN AI\r\n\t\t(0x0B4B <= code && code <= 0x0B4C) || // Mc [2] ORIYA VOWEL SIGN O..ORIYA VOWEL SIGN AU\r\n\t\t0x0BBF == code || // Mc TAMIL VOWEL SIGN I\r\n\t\t(0x0BC1 <= code && code <= 0x0BC2) || // Mc [2] TAMIL VOWEL SIGN U..TAMIL VOWEL SIGN UU\r\n\t\t(0x0BC6 <= code && code <= 0x0BC8) || // Mc [3] TAMIL VOWEL SIGN E..TAMIL VOWEL SIGN AI\r\n\t\t(0x0BCA <= code && code <= 0x0BCC) || // Mc [3] TAMIL VOWEL SIGN O..TAMIL VOWEL SIGN AU\r\n\t\t(0x0C01 <= code && code <= 0x0C03) || // Mc [3] TELUGU SIGN CANDRABINDU..TELUGU SIGN VISARGA\r\n\t\t(0x0C41 <= code && code <= 0x0C44) || // Mc [4] TELUGU VOWEL SIGN U..TELUGU VOWEL SIGN VOCALIC RR\r\n\t\t(0x0C82 <= code && code <= 0x0C83) || // Mc [2] KANNADA SIGN ANUSVARA..KANNADA SIGN VISARGA\r\n\t\t0x0CBE == code || // Mc KANNADA VOWEL SIGN AA\r\n\t\t(0x0CC0 <= code && code <= 0x0CC1) || // Mc [2] KANNADA VOWEL SIGN II..KANNADA VOWEL SIGN U\r\n\t\t(0x0CC3 <= code && code <= 0x0CC4) || // Mc [2] KANNADA VOWEL SIGN VOCALIC R..KANNADA VOWEL SIGN VOCALIC RR\r\n\t\t(0x0CC7 <= code && code <= 0x0CC8) || // Mc [2] KANNADA VOWEL SIGN EE..KANNADA VOWEL SIGN AI\r\n\t\t(0x0CCA <= code && code <= 0x0CCB) || // Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO\r\n\t\t(0x0D02 <= code && code <= 0x0D03) || // Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA\r\n\t\t(0x0D3F <= code && code <= 0x0D40) || // Mc [2] MALAYALAM VOWEL SIGN I..MALAYALAM VOWEL SIGN II\r\n\t\t(0x0D46 <= code && code <= 0x0D48) || // Mc [3] MALAYALAM VOWEL SIGN E..MALAYALAM VOWEL SIGN AI\r\n\t\t(0x0D4A <= code && code <= 0x0D4C) || // Mc [3] MALAYALAM VOWEL SIGN O..MALAYALAM VOWEL SIGN AU\r\n\t\t(0x0D82 <= code && code <= 0x0D83) || // Mc [2] SINHALA SIGN ANUSVARAYA..SINHALA SIGN VISARGAYA\r\n\t\t(0x0DD0 <= code && code <= 0x0DD1) || // Mc [2] SINHALA VOWEL SIGN KETTI AEDA-PILLA..SINHALA VOWEL SIGN DIGA AEDA-PILLA\r\n\t\t(0x0DD8 <= code && code <= 0x0DDE) || // Mc [7] SINHALA VOWEL SIGN GAETTA-PILLA..SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA\r\n\t\t(0x0DF2 <= code && code <= 0x0DF3) || // Mc [2] SINHALA VOWEL SIGN DIGA GAETTA-PILLA..SINHALA VOWEL SIGN DIGA GAYANUKITTA\r\n\t\t0x0E33 == code || // Lo THAI CHARACTER SARA AM\r\n\t\t0x0EB3 == code || // Lo LAO VOWEL SIGN AM\r\n\t\t(0x0F3E <= code && code <= 0x0F3F) || // Mc [2] TIBETAN SIGN YAR TSHES..TIBETAN SIGN MAR TSHES\r\n\t\t0x0F7F == code || // Mc TIBETAN SIGN RNAM BCAD\r\n\t\t0x1031 == code || // Mc MYANMAR VOWEL SIGN E\r\n\t\t(0x103B <= code && code <= 0x103C) || // Mc [2] MYANMAR CONSONANT SIGN MEDIAL YA..MYANMAR CONSONANT SIGN MEDIAL RA\r\n\t\t(0x1056 <= code && code <= 0x1057) || // Mc [2] MYANMAR VOWEL SIGN VOCALIC R..MYANMAR VOWEL SIGN VOCALIC RR\r\n\t\t0x1084 == code || // Mc MYANMAR VOWEL SIGN SHAN E\r\n\t\t0x17B6 == code || // Mc KHMER VOWEL SIGN AA\r\n\t\t(0x17BE <= code && code <= 0x17C5) || // Mc [8] KHMER VOWEL SIGN OE..KHMER VOWEL SIGN AU\r\n\t\t(0x17C7 <= code && code <= 0x17C8) || // Mc [2] KHMER SIGN REAHMUK..KHMER SIGN YUUKALEAPINTU\r\n\t\t(0x1923 <= code && code <= 0x1926) || // Mc [4] LIMBU VOWEL SIGN EE..LIMBU VOWEL SIGN AU\r\n\t\t(0x1929 <= code && code <= 0x192B) || // Mc [3] LIMBU SUBJOINED LETTER YA..LIMBU SUBJOINED LETTER WA\r\n\t\t(0x1930 <= code && code <= 0x1931) || // Mc [2] LIMBU SMALL LETTER KA..LIMBU SMALL LETTER NGA\r\n\t\t(0x1933 <= code && code <= 0x1938) || // Mc [6] LIMBU SMALL LETTER TA..LIMBU SMALL LETTER LA\r\n\t\t(0x1A19 <= code && code <= 0x1A1A) || // Mc [2] BUGINESE VOWEL SIGN E..BUGINESE VOWEL SIGN O\r\n\t\t0x1A55 == code || // Mc TAI THAM CONSONANT SIGN MEDIAL RA\r\n\t\t0x1A57 == code || // Mc TAI THAM CONSONANT SIGN LA TANG LAI\r\n\t\t(0x1A6D <= code && code <= 0x1A72) || // Mc [6] TAI THAM VOWEL SIGN OY..TAI THAM VOWEL SIGN THAM AI\r\n\t\t0x1B04 == code || // Mc BALINESE SIGN BISAH\r\n\t\t0x1B35 == code || // Mc BALINESE VOWEL SIGN TEDUNG\r\n\t\t0x1B3B == code || // Mc BALINESE VOWEL SIGN RA REPA TEDUNG\r\n\t\t(0x1B3D <= code && code <= 0x1B41) || // Mc [5] BALINESE VOWEL SIGN LA LENGA TEDUNG..BALINESE VOWEL SIGN TALING REPA TEDUNG\r\n\t\t(0x1B43 <= code && code <= 0x1B44) || // Mc [2] BALINESE VOWEL SIGN PEPET TEDUNG..BALINESE ADEG ADEG\r\n\t\t0x1B82 == code || // Mc SUNDANESE SIGN PANGWISAD\r\n\t\t0x1BA1 == code || // Mc SUNDANESE CONSONANT SIGN PAMINGKAL\r\n\t\t(0x1BA6 <= code && code <= 0x1BA7) || // Mc [2] SUNDANESE VOWEL SIGN PANAELAENG..SUNDANESE VOWEL SIGN PANOLONG\r\n\t\t0x1BAA == code || // Mc SUNDANESE SIGN PAMAAEH\r\n\t\t0x1BE7 == code || // Mc BATAK VOWEL SIGN E\r\n\t\t(0x1BEA <= code && code <= 0x1BEC) || // Mc [3] BATAK VOWEL SIGN I..BATAK VOWEL SIGN O\r\n\t\t0x1BEE == code || // Mc BATAK VOWEL SIGN U\r\n\t\t(0x1BF2 <= code && code <= 0x1BF3) || // Mc [2] BATAK PANGOLAT..BATAK PANONGONAN\r\n\t\t(0x1C24 <= code && code <= 0x1C2B) || // Mc [8] LEPCHA SUBJOINED LETTER YA..LEPCHA VOWEL SIGN UU\r\n\t\t(0x1C34 <= code && code <= 0x1C35) || // Mc [2] LEPCHA CONSONANT SIGN NYIN-DO..LEPCHA CONSONANT SIGN KANG\r\n\t\t0x1CE1 == code || // Mc VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA\r\n\t\t(0x1CF2 <= code && code <= 0x1CF3) || // Mc [2] VEDIC SIGN ARDHAVISARGA..VEDIC SIGN ROTATED ARDHAVISARGA\r\n\t\t0x1CF7 == code || // Mc VEDIC SIGN ATIKRAMA\r\n\t\t(0xA823 <= code && code <= 0xA824) || // Mc [2] SYLOTI NAGRI VOWEL SIGN A..SYLOTI NAGRI VOWEL SIGN I\r\n\t\t0xA827 == code || // Mc SYLOTI NAGRI VOWEL SIGN OO\r\n\t\t(0xA880 <= code && code <= 0xA881) || // Mc [2] SAURASHTRA SIGN ANUSVARA..SAURASHTRA SIGN VISARGA\r\n\t\t(0xA8B4 <= code && code <= 0xA8C3) || // Mc [16] SAURASHTRA CONSONANT SIGN HAARU..SAURASHTRA VOWEL SIGN AU\r\n\t\t(0xA952 <= code && code <= 0xA953) || // Mc [2] REJANG CONSONANT SIGN H..REJANG VIRAMA\r\n\t\t0xA983 == code || // Mc JAVANESE SIGN WIGNYAN\r\n\t\t(0xA9B4 <= code && code <= 0xA9B5) || // Mc [2] JAVANESE VOWEL SIGN TARUNG..JAVANESE VOWEL SIGN TOLONG\r\n\t\t(0xA9BA <= code && code <= 0xA9BB) || // Mc [2] JAVANESE VOWEL SIGN TALING..JAVANESE VOWEL SIGN DIRGA MURE\r\n\t\t(0xA9BD <= code && code <= 0xA9C0) || // Mc [4] JAVANESE CONSONANT SIGN KERET..JAVANESE PANGKON\r\n\t\t(0xAA2F <= code && code <= 0xAA30) || // Mc [2] CHAM VOWEL SIGN O..CHAM VOWEL SIGN AI\r\n\t\t(0xAA33 <= code && code <= 0xAA34) || // Mc [2] CHAM CONSONANT SIGN YA..CHAM CONSONANT SIGN RA\r\n\t\t0xAA4D == code || // Mc CHAM CONSONANT SIGN FINAL H\r\n\t\t0xAAEB == code || // Mc MEETEI MAYEK VOWEL SIGN II\r\n\t\t(0xAAEE <= code && code <= 0xAAEF) || // Mc [2] MEETEI MAYEK VOWEL SIGN AU..MEETEI MAYEK VOWEL SIGN AAU\r\n\t\t0xAAF5 == code || // Mc MEETEI MAYEK VOWEL SIGN VISARGA\r\n\t\t(0xABE3 <= code && code <= 0xABE4) || // Mc [2] MEETEI MAYEK VOWEL SIGN ONAP..MEETEI MAYEK VOWEL SIGN INAP\r\n\t\t(0xABE6 <= code && code <= 0xABE7) || // Mc [2] MEETEI MAYEK VOWEL SIGN YENAP..MEETEI MAYEK VOWEL SIGN SOUNAP\r\n\t\t(0xABE9 <= code && code <= 0xABEA) || // Mc [2] MEETEI MAYEK VOWEL SIGN CHEINAP..MEETEI MAYEK VOWEL SIGN NUNG\r\n\t\t0xABEC == code || // Mc MEETEI MAYEK LUM IYEK\r\n\t\t0x11000 == code || // Mc BRAHMI SIGN CANDRABINDU\r\n\t\t0x11002 == code || // Mc BRAHMI SIGN VISARGA\r\n\t\t0x11082 == code || // Mc KAITHI SIGN VISARGA\r\n\t\t(0x110B0 <= code && code <= 0x110B2) || // Mc [3] KAITHI VOWEL SIGN AA..KAITHI VOWEL SIGN II\r\n\t\t(0x110B7 <= code && code <= 0x110B8) || // Mc [2] KAITHI VOWEL SIGN O..KAITHI VOWEL SIGN AU\r\n\t\t0x1112C == code || // Mc CHAKMA VOWEL SIGN E\r\n\t\t0x11182 == code || // Mc SHARADA SIGN VISARGA\r\n\t\t(0x111B3 <= code && code <= 0x111B5) || // Mc [3] SHARADA VOWEL SIGN AA..SHARADA VOWEL SIGN II\r\n\t\t(0x111BF <= code && code <= 0x111C0) || // Mc [2] SHARADA VOWEL SIGN AU..SHARADA SIGN VIRAMA\r\n\t\t(0x1122C <= code && code <= 0x1122E) || // Mc [3] KHOJKI VOWEL SIGN AA..KHOJKI VOWEL SIGN II\r\n\t\t(0x11232 <= code && code <= 0x11233) || // Mc [2] KHOJKI VOWEL SIGN O..KHOJKI VOWEL SIGN AU\r\n\t\t0x11235 == code || // Mc KHOJKI SIGN VIRAMA\r\n\t\t(0x112E0 <= code && code <= 0x112E2) || // Mc [3] KHUDAWADI VOWEL SIGN AA..KHUDAWADI VOWEL SIGN II\r\n\t\t(0x11302 <= code && code <= 0x11303) || // Mc [2] GRANTHA SIGN ANUSVARA..GRANTHA SIGN VISARGA\r\n\t\t0x1133F == code || // Mc GRANTHA VOWEL SIGN I\r\n\t\t(0x11341 <= code && code <= 0x11344) || // Mc [4] GRANTHA VOWEL SIGN U..GRANTHA VOWEL SIGN VOCALIC RR\r\n\t\t(0x11347 <= code && code <= 0x11348) || // Mc [2] GRANTHA VOWEL SIGN EE..GRANTHA VOWEL SIGN AI\r\n\t\t(0x1134B <= code && code <= 0x1134D) || // Mc [3] GRANTHA VOWEL SIGN OO..GRANTHA SIGN VIRAMA\r\n\t\t(0x11362 <= code && code <= 0x11363) || // Mc [2] GRANTHA VOWEL SIGN VOCALIC L..GRANTHA VOWEL SIGN VOCALIC LL\r\n\t\t(0x11435 <= code && code <= 0x11437) || // Mc [3] NEWA VOWEL SIGN AA..NEWA VOWEL SIGN II\r\n\t\t(0x11440 <= code && code <= 0x11441) || // Mc [2] NEWA VOWEL SIGN O..NEWA VOWEL SIGN AU\r\n\t\t0x11445 == code || // Mc NEWA SIGN VISARGA\r\n\t\t(0x114B1 <= code && code <= 0x114B2) || // Mc [2] TIRHUTA VOWEL SIGN I..TIRHUTA VOWEL SIGN II\r\n\t\t0x114B9 == code || // Mc TIRHUTA VOWEL SIGN E\r\n\t\t(0x114BB <= code && code <= 0x114BC) || // Mc [2] TIRHUTA VOWEL SIGN AI..TIRHUTA VOWEL SIGN O\r\n\t\t0x114BE == code || // Mc TIRHUTA VOWEL SIGN AU\r\n\t\t0x114C1 == code || // Mc TIRHUTA SIGN VISARGA\r\n\t\t(0x115B0 <= code && code <= 0x115B1) || // Mc [2] SIDDHAM VOWEL SIGN I..SIDDHAM VOWEL SIGN II\r\n\t\t(0x115B8 <= code && code <= 0x115BB) || // Mc [4] SIDDHAM VOWEL SIGN E..SIDDHAM VOWEL SIGN AU\r\n\t\t0x115BE == code || // Mc SIDDHAM SIGN VISARGA\r\n\t\t(0x11630 <= code && code <= 0x11632) || // Mc [3] MODI VOWEL SIGN AA..MODI VOWEL SIGN II\r\n\t\t(0x1163B <= code && code <= 0x1163C) || // Mc [2] MODI VOWEL SIGN O..MODI VOWEL SIGN AU\r\n\t\t0x1163E == code || // Mc MODI SIGN VISARGA\r\n\t\t0x116AC == code || // Mc TAKRI SIGN VISARGA\r\n\t\t(0x116AE <= code && code <= 0x116AF) || // Mc [2] TAKRI VOWEL SIGN I..TAKRI VOWEL SIGN II\r\n\t\t0x116B6 == code || // Mc TAKRI SIGN VIRAMA\r\n\t\t(0x11720 <= code && code <= 0x11721) || // Mc [2] AHOM VOWEL SIGN A..AHOM VOWEL SIGN AA\r\n\t\t0x11726 == code || // Mc AHOM VOWEL SIGN E\r\n\t\t(0x11A07 <= code && code <= 0x11A08) || // Mc [2] ZANABAZAR SQUARE VOWEL SIGN AI..ZANABAZAR SQUARE VOWEL SIGN AU\r\n\t\t0x11A39 == code || // Mc ZANABAZAR SQUARE SIGN VISARGA\r\n\t\t(0x11A57 <= code && code <= 0x11A58) || // Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU\r\n\t\t0x11A97 == code || // Mc SOYOMBO SIGN VISARGA\r\n\t\t0x11C2F == code || // Mc BHAIKSUKI VOWEL SIGN AA\r\n\t\t0x11C3E == code || // Mc BHAIKSUKI SIGN VISARGA\r\n\t\t0x11CA9 == code || // Mc MARCHEN SUBJOINED LETTER YA\r\n\t\t0x11CB1 == code || // Mc MARCHEN VOWEL SIGN I\r\n\t\t0x11CB4 == code || // Mc MARCHEN VOWEL SIGN O\r\n\t\t(0x16F51 <= code && code <= 0x16F7E) || // Mc [46] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN NG\r\n\t\t0x1D166 == code || // Mc MUSICAL SYMBOL COMBINING SPRECHGESANG STEM\r\n\t\t0x1D16D == code // Mc MUSICAL SYMBOL COMBINING AUGMENTATION DOT\r\n\t\t){\r\n\t\t\treturn SpacingMark;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\tif(\r\n\t\t(0x1100 <= code && code <= 0x115F) || // Lo [96] HANGUL CHOSEONG KIYEOK..HANGUL CHOSEONG FILLER\r\n\t\t(0xA960 <= code && code <= 0xA97C) // Lo [29] HANGUL CHOSEONG TIKEUT-MIEUM..HANGUL CHOSEONG SSANGYEORINHIEUH\r\n\t\t){\r\n\t\t\treturn L;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t(0x1160 <= code && code <= 0x11A7) || // Lo [72] HANGUL JUNGSEONG FILLER..HANGUL JUNGSEONG O-YAE\r\n\t\t(0xD7B0 <= code && code <= 0xD7C6) // Lo [23] HANGUL JUNGSEONG O-YEO..HANGUL JUNGSEONG ARAEA-E\r\n\t\t){\r\n\t\t\treturn V;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\tif(\r\n\t\t(0x11A8 <= code && code <= 0x11FF) || // Lo [88] HANGUL JONGSEONG KIYEOK..HANGUL JONGSEONG SSANGNIEUN\r\n\t\t(0xD7CB <= code && code <= 0xD7FB) // Lo [49] HANGUL JONGSEONG NIEUN-RIEUL..HANGUL JONGSEONG PHIEUPH-THIEUTH\r\n\t\t){\r\n\t\t\treturn T;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t0xAC00 == code || // Lo HANGUL SYLLABLE GA\r\n\t\t0xAC1C == code || // Lo HANGUL SYLLABLE GAE\r\n\t\t0xAC38 == code || // Lo HANGUL SYLLABLE GYA\r\n\t\t0xAC54 == code || // Lo HANGUL SYLLABLE GYAE\r\n\t\t0xAC70 == code || // Lo HANGUL SYLLABLE GEO\r\n\t\t0xAC8C == code || // Lo HANGUL SYLLABLE GE\r\n\t\t0xACA8 == code || // Lo HANGUL SYLLABLE GYEO\r\n\t\t0xACC4 == code || // Lo HANGUL SYLLABLE GYE\r\n\t\t0xACE0 == code || // Lo HANGUL SYLLABLE GO\r\n\t\t0xACFC == code || // Lo HANGUL SYLLABLE GWA\r\n\t\t0xAD18 == code || // Lo HANGUL SYLLABLE GWAE\r\n\t\t0xAD34 == code || // Lo HANGUL SYLLABLE GOE\r\n\t\t0xAD50 == code || // Lo HANGUL SYLLABLE GYO\r\n\t\t0xAD6C == code || // Lo HANGUL SYLLABLE GU\r\n\t\t0xAD88 == code || // Lo HANGUL SYLLABLE GWEO\r\n\t\t0xADA4 == code || // Lo HANGUL SYLLABLE GWE\r\n\t\t0xADC0 == code || // Lo HANGUL SYLLABLE GWI\r\n\t\t0xADDC == code || // Lo HANGUL SYLLABLE GYU\r\n\t\t0xADF8 == code || // Lo HANGUL SYLLABLE GEU\r\n\t\t0xAE14 == code || // Lo HANGUL SYLLABLE GYI\r\n\t\t0xAE30 == code || // Lo HANGUL SYLLABLE GI\r\n\t\t0xAE4C == code || // Lo HANGUL SYLLABLE GGA\r\n\t\t0xAE68 == code || // Lo HANGUL SYLLABLE GGAE\r\n\t\t0xAE84 == code || // Lo HANGUL SYLLABLE GGYA\r\n\t\t0xAEA0 == code || // Lo HANGUL SYLLABLE GGYAE\r\n\t\t0xAEBC == code || // Lo HANGUL SYLLABLE GGEO\r\n\t\t0xAED8 == code || // Lo HANGUL SYLLABLE GGE\r\n\t\t0xAEF4 == code || // Lo HANGUL SYLLABLE GGYEO\r\n\t\t0xAF10 == code || // Lo HANGUL SYLLABLE GGYE\r\n\t\t0xAF2C == code || // Lo HANGUL SYLLABLE GGO\r\n\t\t0xAF48 == code || // Lo HANGUL SYLLABLE GGWA\r\n\t\t0xAF64 == code || // Lo HANGUL SYLLABLE GGWAE\r\n\t\t0xAF80 == code || // Lo HANGUL SYLLABLE GGOE\r\n\t\t0xAF9C == code || // Lo HANGUL SYLLABLE GGYO\r\n\t\t0xAFB8 == code || // Lo HANGUL SYLLABLE GGU\r\n\t\t0xAFD4 == code || // Lo HANGUL SYLLABLE GGWEO\r\n\t\t0xAFF0 == code || // Lo HANGUL SYLLABLE GGWE\r\n\t\t0xB00C == code || // Lo HANGUL SYLLABLE GGWI\r\n\t\t0xB028 == code || // Lo HANGUL SYLLABLE GGYU\r\n\t\t0xB044 == code || // Lo HANGUL SYLLABLE GGEU\r\n\t\t0xB060 == code || // Lo HANGUL SYLLABLE GGYI\r\n\t\t0xB07C == code || // Lo HANGUL SYLLABLE GGI\r\n\t\t0xB098 == code || // Lo HANGUL SYLLABLE NA\r\n\t\t0xB0B4 == code || // Lo HANGUL SYLLABLE NAE\r\n\t\t0xB0D0 == code || // Lo HANGUL SYLLABLE NYA\r\n\t\t0xB0EC == code || // Lo HANGUL SYLLABLE NYAE\r\n\t\t0xB108 == code || // Lo HANGUL SYLLABLE NEO\r\n\t\t0xB124 == code || // Lo HANGUL SYLLABLE NE\r\n\t\t0xB140 == code || // Lo HANGUL SYLLABLE NYEO\r\n\t\t0xB15C == code || // Lo HANGUL SYLLABLE NYE\r\n\t\t0xB178 == code || // Lo HANGUL SYLLABLE NO\r\n\t\t0xB194 == code || // Lo HANGUL SYLLABLE NWA\r\n\t\t0xB1B0 == code || // Lo HANGUL SYLLABLE NWAE\r\n\t\t0xB1CC == code || // Lo HANGUL SYLLABLE NOE\r\n\t\t0xB1E8 == code || // Lo HANGUL SYLLABLE NYO\r\n\t\t0xB204 == code || // Lo HANGUL SYLLABLE NU\r\n\t\t0xB220 == code || // Lo HANGUL SYLLABLE NWEO\r\n\t\t0xB23C == code || // Lo HANGUL SYLLABLE NWE\r\n\t\t0xB258 == code || // Lo HANGUL SYLLABLE NWI\r\n\t\t0xB274 == code || // Lo HANGUL SYLLABLE NYU\r\n\t\t0xB290 == code || // Lo HANGUL SYLLABLE NEU\r\n\t\t0xB2AC == code || // Lo HANGUL SYLLABLE NYI\r\n\t\t0xB2C8 == code || // Lo HANGUL SYLLABLE NI\r\n\t\t0xB2E4 == code || // Lo HANGUL SYLLABLE DA\r\n\t\t0xB300 == code || // Lo HANGUL SYLLABLE DAE\r\n\t\t0xB31C == code || // Lo HANGUL SYLLABLE DYA\r\n\t\t0xB338 == code || // Lo HANGUL SYLLABLE DYAE\r\n\t\t0xB354 == code || // Lo HANGUL SYLLABLE DEO\r\n\t\t0xB370 == code || // Lo HANGUL SYLLABLE DE\r\n\t\t0xB38C == code || // Lo HANGUL SYLLABLE DYEO\r\n\t\t0xB3A8 == code || // Lo HANGUL SYLLABLE DYE\r\n\t\t0xB3C4 == code || // Lo HANGUL SYLLABLE DO\r\n\t\t0xB3E0 == code || // Lo HANGUL SYLLABLE DWA\r\n\t\t0xB3FC == code || // Lo HANGUL SYLLABLE DWAE\r\n\t\t0xB418 == code || // Lo HANGUL SYLLABLE DOE\r\n\t\t0xB434 == code || // Lo HANGUL SYLLABLE DYO\r\n\t\t0xB450 == code || // Lo HANGUL SYLLABLE DU\r\n\t\t0xB46C == code || // Lo HANGUL SYLLABLE DWEO\r\n\t\t0xB488 == code || // Lo HANGUL SYLLABLE DWE\r\n\t\t0xB4A4 == code || // Lo HANGUL SYLLABLE DWI\r\n\t\t0xB4C0 == code || // Lo HANGUL SYLLABLE DYU\r\n\t\t0xB4DC == code || // Lo HANGUL SYLLABLE DEU\r\n\t\t0xB4F8 == code || // Lo HANGUL SYLLABLE DYI\r\n\t\t0xB514 == code || // Lo HANGUL SYLLABLE DI\r\n\t\t0xB530 == code || // Lo HANGUL SYLLABLE DDA\r\n\t\t0xB54C == code || // Lo HANGUL SYLLABLE DDAE\r\n\t\t0xB568 == code || // Lo HANGUL SYLLABLE DDYA\r\n\t\t0xB584 == code || // Lo HANGUL SYLLABLE DDYAE\r\n\t\t0xB5A0 == code || // Lo HANGUL SYLLABLE DDEO\r\n\t\t0xB5BC == code || // Lo HANGUL SYLLABLE DDE\r\n\t\t0xB5D8 == code || // Lo HANGUL SYLLABLE DDYEO\r\n\t\t0xB5F4 == code || // Lo HANGUL SYLLABLE DDYE\r\n\t\t0xB610 == code || // Lo HANGUL SYLLABLE DDO\r\n\t\t0xB62C == code || // Lo HANGUL SYLLABLE DDWA\r\n\t\t0xB648 == code || // Lo HANGUL SYLLABLE DDWAE\r\n\t\t0xB664 == code || // Lo HANGUL SYLLABLE DDOE\r\n\t\t0xB680 == code || // Lo HANGUL SYLLABLE DDYO\r\n\t\t0xB69C == code || // Lo HANGUL SYLLABLE DDU\r\n\t\t0xB6B8 == code || // Lo HANGUL SYLLABLE DDWEO\r\n\t\t0xB6D4 == code || // Lo HANGUL SYLLABLE DDWE\r\n\t\t0xB6F0 == code || // Lo HANGUL SYLLABLE DDWI\r\n\t\t0xB70C == code || // Lo HANGUL SYLLABLE DDYU\r\n\t\t0xB728 == code || // Lo HANGUL SYLLABLE DDEU\r\n\t\t0xB744 == code || // Lo HANGUL SYLLABLE DDYI\r\n\t\t0xB760 == code || // Lo HANGUL SYLLABLE DDI\r\n\t\t0xB77C == code || // Lo HANGUL SYLLABLE RA\r\n\t\t0xB798 == code || // Lo HANGUL SYLLABLE RAE\r\n\t\t0xB7B4 == code || // Lo HANGUL SYLLABLE RYA\r\n\t\t0xB7D0 == code || // Lo HANGUL SYLLABLE RYAE\r\n\t\t0xB7EC == code || // Lo HANGUL SYLLABLE REO\r\n\t\t0xB808 == code || // Lo HANGUL SYLLABLE RE\r\n\t\t0xB824 == code || // Lo HANGUL SYLLABLE RYEO\r\n\t\t0xB840 == code || // Lo HANGUL SYLLABLE RYE\r\n\t\t0xB85C == code || // Lo HANGUL SYLLABLE RO\r\n\t\t0xB878 == code || // Lo HANGUL SYLLABLE RWA\r\n\t\t0xB894 == code || // Lo HANGUL SYLLABLE RWAE\r\n\t\t0xB8B0 == code || // Lo HANGUL SYLLABLE ROE\r\n\t\t0xB8CC == code || // Lo HANGUL SYLLABLE RYO\r\n\t\t0xB8E8 == code || // Lo HANGUL SYLLABLE RU\r\n\t\t0xB904 == code || // Lo HANGUL SYLLABLE RWEO\r\n\t\t0xB920 == code || // Lo HANGUL SYLLABLE RWE\r\n\t\t0xB93C == code || // Lo HANGUL SYLLABLE RWI\r\n\t\t0xB958 == code || // Lo HANGUL SYLLABLE RYU\r\n\t\t0xB974 == code || // Lo HANGUL SYLLABLE REU\r\n\t\t0xB990 == code || // Lo HANGUL SYLLABLE RYI\r\n\t\t0xB9AC == code || // Lo HANGUL SYLLABLE RI\r\n\t\t0xB9C8 == code || // Lo HANGUL SYLLABLE MA\r\n\t\t0xB9E4 == code || // Lo HANGUL SYLLABLE MAE\r\n\t\t0xBA00 == code || // Lo HANGUL SYLLABLE MYA\r\n\t\t0xBA1C == code || // Lo HANGUL SYLLABLE MYAE\r\n\t\t0xBA38 == code || // Lo HANGUL SYLLABLE MEO\r\n\t\t0xBA54 == code || // Lo HANGUL SYLLABLE ME\r\n\t\t0xBA70 == code || // Lo HANGUL SYLLABLE MYEO\r\n\t\t0xBA8C == code || // Lo HANGUL SYLLABLE MYE\r\n\t\t0xBAA8 == code || // Lo HANGUL SYLLABLE MO\r\n\t\t0xBAC4 == code || // Lo HANGUL SYLLABLE MWA\r\n\t\t0xBAE0 == code || // Lo HANGUL SYLLABLE MWAE\r\n\t\t0xBAFC == code || // Lo HANGUL SYLLABLE MOE\r\n\t\t0xBB18 == code || // Lo HANGUL SYLLABLE MYO\r\n\t\t0xBB34 == code || // Lo HANGUL SYLLABLE MU\r\n\t\t0xBB50 == code || // Lo HANGUL SYLLABLE MWEO\r\n\t\t0xBB6C == code || // Lo HANGUL SYLLABLE MWE\r\n\t\t0xBB88 == code || // Lo HANGUL SYLLABLE MWI\r\n\t\t0xBBA4 == code || // Lo HANGUL SYLLABLE MYU\r\n\t\t0xBBC0 == code || // Lo HANGUL SYLLABLE MEU\r\n\t\t0xBBDC == code || // Lo HANGUL SYLLABLE MYI\r\n\t\t0xBBF8 == code || // Lo HANGUL SYLLABLE MI\r\n\t\t0xBC14 == code || // Lo HANGUL SYLLABLE BA\r\n\t\t0xBC30 == code || // Lo HANGUL SYLLABLE BAE\r\n\t\t0xBC4C == code || // Lo HANGUL SYLLABLE BYA\r\n\t\t0xBC68 == code || // Lo HANGUL SYLLABLE BYAE\r\n\t\t0xBC84 == code || // Lo HANGUL SYLLABLE BEO\r\n\t\t0xBCA0 == code || // Lo HANGUL SYLLABLE BE\r\n\t\t0xBCBC == code || // Lo HANGUL SYLLABLE BYEO\r\n\t\t0xBCD8 == code || // Lo HANGUL SYLLABLE BYE\r\n\t\t0xBCF4 == code || // Lo HANGUL SYLLABLE BO\r\n\t\t0xBD10 == code || // Lo HANGUL SYLLABLE BWA\r\n\t\t0xBD2C == code || // Lo HANGUL SYLLABLE BWAE\r\n\t\t0xBD48 == code || // Lo HANGUL SYLLABLE BOE\r\n\t\t0xBD64 == code || // Lo HANGUL SYLLABLE BYO\r\n\t\t0xBD80 == code || // Lo HANGUL SYLLABLE BU\r\n\t\t0xBD9C == code || // Lo HANGUL SYLLABLE BWEO\r\n\t\t0xBDB8 == code || // Lo HANGUL SYLLABLE BWE\r\n\t\t0xBDD4 == code || // Lo HANGUL SYLLABLE BWI\r\n\t\t0xBDF0 == code || // Lo HANGUL SYLLABLE BYU\r\n\t\t0xBE0C == code || // Lo HANGUL SYLLABLE BEU\r\n\t\t0xBE28 == code || // Lo HANGUL SYLLABLE BYI\r\n\t\t0xBE44 == code || // Lo HANGUL SYLLABLE BI\r\n\t\t0xBE60 == code || // Lo HANGUL SYLLABLE BBA\r\n\t\t0xBE7C == code || // Lo HANGUL SYLLABLE BBAE\r\n\t\t0xBE98 == code || // Lo HANGUL SYLLABLE BBYA\r\n\t\t0xBEB4 == code || // Lo HANGUL SYLLABLE BBYAE\r\n\t\t0xBED0 == code || // Lo HANGUL SYLLABLE BBEO\r\n\t\t0xBEEC == code || // Lo HANGUL SYLLABLE BBE\r\n\t\t0xBF08 == code || // Lo HANGUL SYLLABLE BBYEO\r\n\t\t0xBF24 == code || // Lo HANGUL SYLLABLE BBYE\r\n\t\t0xBF40 == code || // Lo HANGUL SYLLABLE BBO\r\n\t\t0xBF5C == code || // Lo HANGUL SYLLABLE BBWA\r\n\t\t0xBF78 == code || // Lo HANGUL SYLLABLE BBWAE\r\n\t\t0xBF94 == code || // Lo HANGUL SYLLABLE BBOE\r\n\t\t0xBFB0 == code || // Lo HANGUL SYLLABLE BBYO\r\n\t\t0xBFCC == code || // Lo HANGUL SYLLABLE BBU\r\n\t\t0xBFE8 == code || // Lo HANGUL SYLLABLE BBWEO\r\n\t\t0xC004 == code || // Lo HANGUL SYLLABLE BBWE\r\n\t\t0xC020 == code || // Lo HANGUL SYLLABLE BBWI\r\n\t\t0xC03C == code || // Lo HANGUL SYLLABLE BBYU\r\n\t\t0xC058 == code || // Lo HANGUL SYLLABLE BBEU\r\n\t\t0xC074 == code || // Lo HANGUL SYLLABLE BBYI\r\n\t\t0xC090 == code || // Lo HANGUL SYLLABLE BBI\r\n\t\t0xC0AC == code || // Lo HANGUL SYLLABLE SA\r\n\t\t0xC0C8 == code || // Lo HANGUL SYLLABLE SAE\r\n\t\t0xC0E4 == code || // Lo HANGUL SYLLABLE SYA\r\n\t\t0xC100 == code || // Lo HANGUL SYLLABLE SYAE\r\n\t\t0xC11C == code || // Lo HANGUL SYLLABLE SEO\r\n\t\t0xC138 == code || // Lo HANGUL SYLLABLE SE\r\n\t\t0xC154 == code || // Lo HANGUL SYLLABLE SYEO\r\n\t\t0xC170 == code || // Lo HANGUL SYLLABLE SYE\r\n\t\t0xC18C == code || // Lo HANGUL SYLLABLE SO\r\n\t\t0xC1A8 == code || // Lo HANGUL SYLLABLE SWA\r\n\t\t0xC1C4 == code || // Lo HANGUL SYLLABLE SWAE\r\n\t\t0xC1E0 == code || // Lo HANGUL SYLLABLE SOE\r\n\t\t0xC1FC == code || // Lo HANGUL SYLLABLE SYO\r\n\t\t0xC218 == code || // Lo HANGUL SYLLABLE SU\r\n\t\t0xC234 == code || // Lo HANGUL SYLLABLE SWEO\r\n\t\t0xC250 == code || // Lo HANGUL SYLLABLE SWE\r\n\t\t0xC26C == code || // Lo HANGUL SYLLABLE SWI\r\n\t\t0xC288 == code || // Lo HANGUL SYLLABLE SYU\r\n\t\t0xC2A4 == code || // Lo HANGUL SYLLABLE SEU\r\n\t\t0xC2C0 == code || // Lo HANGUL SYLLABLE SYI\r\n\t\t0xC2DC == code || // Lo HANGUL SYLLABLE SI\r\n\t\t0xC2F8 == code || // Lo HANGUL SYLLABLE SSA\r\n\t\t0xC314 == code || // Lo HANGUL SYLLABLE SSAE\r\n\t\t0xC330 == code || // Lo HANGUL SYLLABLE SSYA\r\n\t\t0xC34C == code || // Lo HANGUL SYLLABLE SSYAE\r\n\t\t0xC368 == code || // Lo HANGUL SYLLABLE SSEO\r\n\t\t0xC384 == code || // Lo HANGUL SYLLABLE SSE\r\n\t\t0xC3A0 == code || // Lo HANGUL SYLLABLE SSYEO\r\n\t\t0xC3BC == code || // Lo HANGUL SYLLABLE SSYE\r\n\t\t0xC3D8 == code || // Lo HANGUL SYLLABLE SSO\r\n\t\t0xC3F4 == code || // Lo HANGUL SYLLABLE SSWA\r\n\t\t0xC410 == code || // Lo HANGUL SYLLABLE SSWAE\r\n\t\t0xC42C == code || // Lo HANGUL SYLLABLE SSOE\r\n\t\t0xC448 == code || // Lo HANGUL SYLLABLE SSYO\r\n\t\t0xC464 == code || // Lo HANGUL SYLLABLE SSU\r\n\t\t0xC480 == code || // Lo HANGUL SYLLABLE SSWEO\r\n\t\t0xC49C == code || // Lo HANGUL SYLLABLE SSWE\r\n\t\t0xC4B8 == code || // Lo HANGUL SYLLABLE SSWI\r\n\t\t0xC4D4 == code || // Lo HANGUL SYLLABLE SSYU\r\n\t\t0xC4F0 == code || // Lo HANGUL SYLLABLE SSEU\r\n\t\t0xC50C == code || // Lo HANGUL SYLLABLE SSYI\r\n\t\t0xC528 == code || // Lo HANGUL SYLLABLE SSI\r\n\t\t0xC544 == code || // Lo HANGUL SYLLABLE A\r\n\t\t0xC560 == code || // Lo HANGUL SYLLABLE AE\r\n\t\t0xC57C == code || // Lo HANGUL SYLLABLE YA\r\n\t\t0xC598 == code || // Lo HANGUL SYLLABLE YAE\r\n\t\t0xC5B4 == code || // Lo HANGUL SYLLABLE EO\r\n\t\t0xC5D0 == code || // Lo HANGUL SYLLABLE E\r\n\t\t0xC5EC == code || // Lo HANGUL SYLLABLE YEO\r\n\t\t0xC608 == code || // Lo HANGUL SYLLABLE YE\r\n\t\t0xC624 == code || // Lo HANGUL SYLLABLE O\r\n\t\t0xC640 == code || // Lo HANGUL SYLLABLE WA\r\n\t\t0xC65C == code || // Lo HANGUL SYLLABLE WAE\r\n\t\t0xC678 == code || // Lo HANGUL SYLLABLE OE\r\n\t\t0xC694 == code || // Lo HANGUL SYLLABLE YO\r\n\t\t0xC6B0 == code || // Lo HANGUL SYLLABLE U\r\n\t\t0xC6CC == code || // Lo HANGUL SYLLABLE WEO\r\n\t\t0xC6E8 == code || // Lo HANGUL SYLLABLE WE\r\n\t\t0xC704 == code || // Lo HANGUL SYLLABLE WI\r\n\t\t0xC720 == code || // Lo HANGUL SYLLABLE YU\r\n\t\t0xC73C == code || // Lo HANGUL SYLLABLE EU\r\n\t\t0xC758 == code || // Lo HANGUL SYLLABLE YI\r\n\t\t0xC774 == code || // Lo HANGUL SYLLABLE I\r\n\t\t0xC790 == code || // Lo HANGUL SYLLABLE JA\r\n\t\t0xC7AC == code || // Lo HANGUL SYLLABLE JAE\r\n\t\t0xC7C8 == code || // Lo HANGUL SYLLABLE JYA\r\n\t\t0xC7E4 == code || // Lo HANGUL SYLLABLE JYAE\r\n\t\t0xC800 == code || // Lo HANGUL SYLLABLE JEO\r\n\t\t0xC81C == code || // Lo HANGUL SYLLABLE JE\r\n\t\t0xC838 == code || // Lo HANGUL SYLLABLE JYEO\r\n\t\t0xC854 == code || // Lo HANGUL SYLLABLE JYE\r\n\t\t0xC870 == code || // Lo HANGUL SYLLABLE JO\r\n\t\t0xC88C == code || // Lo HANGUL SYLLABLE JWA\r\n\t\t0xC8A8 == code || // Lo HANGUL SYLLABLE JWAE\r\n\t\t0xC8C4 == code || // Lo HANGUL SYLLABLE JOE\r\n\t\t0xC8E0 == code || // Lo HANGUL SYLLABLE JYO\r\n\t\t0xC8FC == code || // Lo HANGUL SYLLABLE JU\r\n\t\t0xC918 == code || // Lo HANGUL SYLLABLE JWEO\r\n\t\t0xC934 == code || // Lo HANGUL SYLLABLE JWE\r\n\t\t0xC950 == code || // Lo HANGUL SYLLABLE JWI\r\n\t\t0xC96C == code || // Lo HANGUL SYLLABLE JYU\r\n\t\t0xC988 == code || // Lo HANGUL SYLLABLE JEU\r\n\t\t0xC9A4 == code || // Lo HANGUL SYLLABLE JYI\r\n\t\t0xC9C0 == code || // Lo HANGUL SYLLABLE JI\r\n\t\t0xC9DC == code || // Lo HANGUL SYLLABLE JJA\r\n\t\t0xC9F8 == code || // Lo HANGUL SYLLABLE JJAE\r\n\t\t0xCA14 == code || // Lo HANGUL SYLLABLE JJYA\r\n\t\t0xCA30 == code || // Lo HANGUL SYLLABLE JJYAE\r\n\t\t0xCA4C == code || // Lo HANGUL SYLLABLE JJEO\r\n\t\t0xCA68 == code || // Lo HANGUL SYLLABLE JJE\r\n\t\t0xCA84 == code || // Lo HANGUL SYLLABLE JJYEO\r\n\t\t0xCAA0 == code || // Lo HANGUL SYLLABLE JJYE\r\n\t\t0xCABC == code || // Lo HANGUL SYLLABLE JJO\r\n\t\t0xCAD8 == code || // Lo HANGUL SYLLABLE JJWA\r\n\t\t0xCAF4 == code || // Lo HANGUL SYLLABLE JJWAE\r\n\t\t0xCB10 == code || // Lo HANGUL SYLLABLE JJOE\r\n\t\t0xCB2C == code || // Lo HANGUL SYLLABLE JJYO\r\n\t\t0xCB48 == code || // Lo HANGUL SYLLABLE JJU\r\n\t\t0xCB64 == code || // Lo HANGUL SYLLABLE JJWEO\r\n\t\t0xCB80 == code || // Lo HANGUL SYLLABLE JJWE\r\n\t\t0xCB9C == code || // Lo HANGUL SYLLABLE JJWI\r\n\t\t0xCBB8 == code || // Lo HANGUL SYLLABLE JJYU\r\n\t\t0xCBD4 == code || // Lo HANGUL SYLLABLE JJEU\r\n\t\t0xCBF0 == code || // Lo HANGUL SYLLABLE JJYI\r\n\t\t0xCC0C == code || // Lo HANGUL SYLLABLE JJI\r\n\t\t0xCC28 == code || // Lo HANGUL SYLLABLE CA\r\n\t\t0xCC44 == code || // Lo HANGUL SYLLABLE CAE\r\n\t\t0xCC60 == code || // Lo HANGUL SYLLABLE CYA\r\n\t\t0xCC7C == code || // Lo HANGUL SYLLABLE CYAE\r\n\t\t0xCC98 == code || // Lo HANGUL SYLLABLE CEO\r\n\t\t0xCCB4 == code || // Lo HANGUL SYLLABLE CE\r\n\t\t0xCCD0 == code || // Lo HANGUL SYLLABLE CYEO\r\n\t\t0xCCEC == code || // Lo HANGUL SYLLABLE CYE\r\n\t\t0xCD08 == code || // Lo HANGUL SYLLABLE CO\r\n\t\t0xCD24 == code || // Lo HANGUL SYLLABLE CWA\r\n\t\t0xCD40 == code || // Lo HANGUL SYLLABLE CWAE\r\n\t\t0xCD5C == code || // Lo HANGUL SYLLABLE COE\r\n\t\t0xCD78 == code || // Lo HANGUL SYLLABLE CYO\r\n\t\t0xCD94 == code || // Lo HANGUL SYLLABLE CU\r\n\t\t0xCDB0 == code || // Lo HANGUL SYLLABLE CWEO\r\n\t\t0xCDCC == code || // Lo HANGUL SYLLABLE CWE\r\n\t\t0xCDE8 == code || // Lo HANGUL SYLLABLE CWI\r\n\t\t0xCE04 == code || // Lo HANGUL SYLLABLE CYU\r\n\t\t0xCE20 == code || // Lo HANGUL SYLLABLE CEU\r\n\t\t0xCE3C == code || // Lo HANGUL SYLLABLE CYI\r\n\t\t0xCE58 == code || // Lo HANGUL SYLLABLE CI\r\n\t\t0xCE74 == code || // Lo HANGUL SYLLABLE KA\r\n\t\t0xCE90 == code || // Lo HANGUL SYLLABLE KAE\r\n\t\t0xCEAC == code || // Lo HANGUL SYLLABLE KYA\r\n\t\t0xCEC8 == code || // Lo HANGUL SYLLABLE KYAE\r\n\t\t0xCEE4 == code || // Lo HANGUL SYLLABLE KEO\r\n\t\t0xCF00 == code || // Lo HANGUL SYLLABLE KE\r\n\t\t0xCF1C == code || // Lo HANGUL SYLLABLE KYEO\r\n\t\t0xCF38 == code || // Lo HANGUL SYLLABLE KYE\r\n\t\t0xCF54 == code || // Lo HANGUL SYLLABLE KO\r\n\t\t0xCF70 == code || // Lo HANGUL SYLLABLE KWA\r\n\t\t0xCF8C == code || // Lo HANGUL SYLLABLE KWAE\r\n\t\t0xCFA8 == code || // Lo HANGUL SYLLABLE KOE\r\n\t\t0xCFC4 == code || // Lo HANGUL SYLLABLE KYO\r\n\t\t0xCFE0 == code || // Lo HANGUL SYLLABLE KU\r\n\t\t0xCFFC == code || // Lo HANGUL SYLLABLE KWEO\r\n\t\t0xD018 == code || // Lo HANGUL SYLLABLE KWE\r\n\t\t0xD034 == code || // Lo HANGUL SYLLABLE KWI\r\n\t\t0xD050 == code || // Lo HANGUL SYLLABLE KYU\r\n\t\t0xD06C == code || // Lo HANGUL SYLLABLE KEU\r\n\t\t0xD088 == code || // Lo HANGUL SYLLABLE KYI\r\n\t\t0xD0A4 == code || // Lo HANGUL SYLLABLE KI\r\n\t\t0xD0C0 == code || // Lo HANGUL SYLLABLE TA\r\n\t\t0xD0DC == code || // Lo HANGUL SYLLABLE TAE\r\n\t\t0xD0F8 == code || // Lo HANGUL SYLLABLE TYA\r\n\t\t0xD114 == code || // Lo HANGUL SYLLABLE TYAE\r\n\t\t0xD130 == code || // Lo HANGUL SYLLABLE TEO\r\n\t\t0xD14C == code || // Lo HANGUL SYLLABLE TE\r\n\t\t0xD168 == code || // Lo HANGUL SYLLABLE TYEO\r\n\t\t0xD184 == code || // Lo HANGUL SYLLABLE TYE\r\n\t\t0xD1A0 == code || // Lo HANGUL SYLLABLE TO\r\n\t\t0xD1BC == code || // Lo HANGUL SYLLABLE TWA\r\n\t\t0xD1D8 == code || // Lo HANGUL SYLLABLE TWAE\r\n\t\t0xD1F4 == code || // Lo HANGUL SYLLABLE TOE\r\n\t\t0xD210 == code || // Lo HANGUL SYLLABLE TYO\r\n\t\t0xD22C == code || // Lo HANGUL SYLLABLE TU\r\n\t\t0xD248 == code || // Lo HANGUL SYLLABLE TWEO\r\n\t\t0xD264 == code || // Lo HANGUL SYLLABLE TWE\r\n\t\t0xD280 == code || // Lo HANGUL SYLLABLE TWI\r\n\t\t0xD29C == code || // Lo HANGUL SYLLABLE TYU\r\n\t\t0xD2B8 == code || // Lo HANGUL SYLLABLE TEU\r\n\t\t0xD2D4 == code || // Lo HANGUL SYLLABLE TYI\r\n\t\t0xD2F0 == code || // Lo HANGUL SYLLABLE TI\r\n\t\t0xD30C == code || // Lo HANGUL SYLLABLE PA\r\n\t\t0xD328 == code || // Lo HANGUL SYLLABLE PAE\r\n\t\t0xD344 == code || // Lo HANGUL SYLLABLE PYA\r\n\t\t0xD360 == code || // Lo HANGUL SYLLABLE PYAE\r\n\t\t0xD37C == code || // Lo HANGUL SYLLABLE PEO\r\n\t\t0xD398 == code || // Lo HANGUL SYLLABLE PE\r\n\t\t0xD3B4 == code || // Lo HANGUL SYLLABLE PYEO\r\n\t\t0xD3D0 == code || // Lo HANGUL SYLLABLE PYE\r\n\t\t0xD3EC == code || // Lo HANGUL SYLLABLE PO\r\n\t\t0xD408 == code || // Lo HANGUL SYLLABLE PWA\r\n\t\t0xD424 == code || // Lo HANGUL SYLLABLE PWAE\r\n\t\t0xD440 == code || // Lo HANGUL SYLLABLE POE\r\n\t\t0xD45C == code || // Lo HANGUL SYLLABLE PYO\r\n\t\t0xD478 == code || // Lo HANGUL SYLLABLE PU\r\n\t\t0xD494 == code || // Lo HANGUL SYLLABLE PWEO\r\n\t\t0xD4B0 == code || // Lo HANGUL SYLLABLE PWE\r\n\t\t0xD4CC == code || // Lo HANGUL SYLLABLE PWI\r\n\t\t0xD4E8 == code || // Lo HANGUL SYLLABLE PYU\r\n\t\t0xD504 == code || // Lo HANGUL SYLLABLE PEU\r\n\t\t0xD520 == code || // Lo HANGUL SYLLABLE PYI\r\n\t\t0xD53C == code || // Lo HANGUL SYLLABLE PI\r\n\t\t0xD558 == code || // Lo HANGUL SYLLABLE HA\r\n\t\t0xD574 == code || // Lo HANGUL SYLLABLE HAE\r\n\t\t0xD590 == code || // Lo HANGUL SYLLABLE HYA\r\n\t\t0xD5AC == code || // Lo HANGUL SYLLABLE HYAE\r\n\t\t0xD5C8 == code || // Lo HANGUL SYLLABLE HEO\r\n\t\t0xD5E4 == code || // Lo HANGUL SYLLABLE HE\r\n\t\t0xD600 == code || // Lo HANGUL SYLLABLE HYEO\r\n\t\t0xD61C == code || // Lo HANGUL SYLLABLE HYE\r\n\t\t0xD638 == code || // Lo HANGUL SYLLABLE HO\r\n\t\t0xD654 == code || // Lo HANGUL SYLLABLE HWA\r\n\t\t0xD670 == code || // Lo HANGUL SYLLABLE HWAE\r\n\t\t0xD68C == code || // Lo HANGUL SYLLABLE HOE\r\n\t\t0xD6A8 == code || // Lo HANGUL SYLLABLE HYO\r\n\t\t0xD6C4 == code || // Lo HANGUL SYLLABLE HU\r\n\t\t0xD6E0 == code || // Lo HANGUL SYLLABLE HWEO\r\n\t\t0xD6FC == code || // Lo HANGUL SYLLABLE HWE\r\n\t\t0xD718 == code || // Lo HANGUL SYLLABLE HWI\r\n\t\t0xD734 == code || // Lo HANGUL SYLLABLE HYU\r\n\t\t0xD750 == code || // Lo HANGUL SYLLABLE HEU\r\n\t\t0xD76C == code || // Lo HANGUL SYLLABLE HYI\r\n\t\t0xD788 == code // Lo HANGUL SYLLABLE HI\r\n\t\t){\r\n\t\t\treturn LV;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t(0xAC01 <= code && code <= 0xAC1B) || // Lo [27] HANGUL SYLLABLE GAG..HANGUL SYLLABLE GAH\r\n\t\t(0xAC1D <= code && code <= 0xAC37) || // Lo [27] HANGUL SYLLABLE GAEG..HANGUL SYLLABLE GAEH\r\n\t\t(0xAC39 <= code && code <= 0xAC53) || // Lo [27] HANGUL SYLLABLE GYAG..HANGUL SYLLABLE GYAH\r\n\t\t(0xAC55 <= code && code <= 0xAC6F) || // Lo [27] HANGUL SYLLABLE GYAEG..HANGUL SYLLABLE GYAEH\r\n\t\t(0xAC71 <= code && code <= 0xAC8B) || // Lo [27] HANGUL SYLLABLE GEOG..HANGUL SYLLABLE GEOH\r\n\t\t(0xAC8D <= code && code <= 0xACA7) || // Lo [27] HANGUL SYLLABLE GEG..HANGUL SYLLABLE GEH\r\n\t\t(0xACA9 <= code && code <= 0xACC3) || // Lo [27] HANGUL SYLLABLE GYEOG..HANGUL SYLLABLE GYEOH\r\n\t\t(0xACC5 <= code && code <= 0xACDF) || // Lo [27] HANGUL SYLLABLE GYEG..HANGUL SYLLABLE GYEH\r\n\t\t(0xACE1 <= code && code <= 0xACFB) || // Lo [27] HANGUL SYLLABLE GOG..HANGUL SYLLABLE GOH\r\n\t\t(0xACFD <= code && code <= 0xAD17) || // Lo [27] HANGUL SYLLABLE GWAG..HANGUL SYLLABLE GWAH\r\n\t\t(0xAD19 <= code && code <= 0xAD33) || // Lo [27] HANGUL SYLLABLE GWAEG..HANGUL SYLLABLE GWAEH\r\n\t\t(0xAD35 <= code && code <= 0xAD4F) || // Lo [27] HANGUL SYLLABLE GOEG..HANGUL SYLLABLE GOEH\r\n\t\t(0xAD51 <= code && code <= 0xAD6B) || // Lo [27] HANGUL SYLLABLE GYOG..HANGUL SYLLABLE GYOH\r\n\t\t(0xAD6D <= code && code <= 0xAD87) || // Lo [27] HANGUL SYLLABLE GUG..HANGUL SYLLABLE GUH\r\n\t\t(0xAD89 <= code && code <= 0xADA3) || // Lo [27] HANGUL SYLLABLE GWEOG..HANGUL SYLLABLE GWEOH\r\n\t\t(0xADA5 <= code && code <= 0xADBF) || // Lo [27] HANGUL SYLLABLE GWEG..HANGUL SYLLABLE GWEH\r\n\t\t(0xADC1 <= code && code <= 0xADDB) || // Lo [27] HANGUL SYLLABLE GWIG..HANGUL SYLLABLE GWIH\r\n\t\t(0xADDD <= code && code <= 0xADF7) || // Lo [27] HANGUL SYLLABLE GYUG..HANGUL SYLLABLE GYUH\r\n\t\t(0xADF9 <= code && code <= 0xAE13) || // Lo [27] HANGUL SYLLABLE GEUG..HANGUL SYLLABLE GEUH\r\n\t\t(0xAE15 <= code && code <= 0xAE2F) || // Lo [27] HANGUL SYLLABLE GYIG..HANGUL SYLLABLE GYIH\r\n\t\t(0xAE31 <= code && code <= 0xAE4B) || // Lo [27] HANGUL SYLLABLE GIG..HANGUL SYLLABLE GIH\r\n\t\t(0xAE4D <= code && code <= 0xAE67) || // Lo [27] HANGUL SYLLABLE GGAG..HANGUL SYLLABLE GGAH\r\n\t\t(0xAE69 <= code && code <= 0xAE83) || // Lo [27] HANGUL SYLLABLE GGAEG..HANGUL SYLLABLE GGAEH\r\n\t\t(0xAE85 <= code && code <= 0xAE9F) || // Lo [27] HANGUL SYLLABLE GGYAG..HANGUL SYLLABLE GGYAH\r\n\t\t(0xAEA1 <= code && code <= 0xAEBB) || // Lo [27] HANGUL SYLLABLE GGYAEG..HANGUL SYLLABLE GGYAEH\r\n\t\t(0xAEBD <= code && code <= 0xAED7) || // Lo [27] HANGUL SYLLABLE GGEOG..HANGUL SYLLABLE GGEOH\r\n\t\t(0xAED9 <= code && code <= 0xAEF3) || // Lo [27] HANGUL SYLLABLE GGEG..HANGUL SYLLABLE GGEH\r\n\t\t(0xAEF5 <= code && code <= 0xAF0F) || // Lo [27] HANGUL SYLLABLE GGYEOG..HANGUL SYLLABLE GGYEOH\r\n\t\t(0xAF11 <= code && code <= 0xAF2B) || // Lo [27] HANGUL SYLLABLE GGYEG..HANGUL SYLLABLE GGYEH\r\n\t\t(0xAF2D <= code && code <= 0xAF47) || // Lo [27] HANGUL SYLLABLE GGOG..HANGUL SYLLABLE GGOH\r\n\t\t(0xAF49 <= code && code <= 0xAF63) || // Lo [27] HANGUL SYLLABLE GGWAG..HANGUL SYLLABLE GGWAH\r\n\t\t(0xAF65 <= code && code <= 0xAF7F) || // Lo [27] HANGUL SYLLABLE GGWAEG..HANGUL SYLLABLE GGWAEH\r\n\t\t(0xAF81 <= code && code <= 0xAF9B) || // Lo [27] HANGUL SYLLABLE GGOEG..HANGUL SYLLABLE GGOEH\r\n\t\t(0xAF9D <= code && code <= 0xAFB7) || // Lo [27] HANGUL SYLLABLE GGYOG..HANGUL SYLLABLE GGYOH\r\n\t\t(0xAFB9 <= code && code <= 0xAFD3) || // Lo [27] HANGUL SYLLABLE GGUG..HANGUL SYLLABLE GGUH\r\n\t\t(0xAFD5 <= code && code <= 0xAFEF) || // Lo [27] HANGUL SYLLABLE GGWEOG..HANGUL SYLLABLE GGWEOH\r\n\t\t(0xAFF1 <= code && code <= 0xB00B) || // Lo [27] HANGUL SYLLABLE GGWEG..HANGUL SYLLABLE GGWEH\r\n\t\t(0xB00D <= code && code <= 0xB027) || // Lo [27] HANGUL SYLLABLE GGWIG..HANGUL SYLLABLE GGWIH\r\n\t\t(0xB029 <= code && code <= 0xB043) || // Lo [27] HANGUL SYLLABLE GGYUG..HANGUL SYLLABLE GGYUH\r\n\t\t(0xB045 <= code && code <= 0xB05F) || // Lo [27] HANGUL SYLLABLE GGEUG..HANGUL SYLLABLE GGEUH\r\n\t\t(0xB061 <= code && code <= 0xB07B) || // Lo [27] HANGUL SYLLABLE GGYIG..HANGUL SYLLABLE GGYIH\r\n\t\t(0xB07D <= code && code <= 0xB097) || // Lo [27] HANGUL SYLLABLE GGIG..HANGUL SYLLABLE GGIH\r\n\t\t(0xB099 <= code && code <= 0xB0B3) || // Lo [27] HANGUL SYLLABLE NAG..HANGUL SYLLABLE NAH\r\n\t\t(0xB0B5 <= code && code <= 0xB0CF) || // Lo [27] HANGUL SYLLABLE NAEG..HANGUL SYLLABLE NAEH\r\n\t\t(0xB0D1 <= code && code <= 0xB0EB) || // Lo [27] HANGUL SYLLABLE NYAG..HANGUL SYLLABLE NYAH\r\n\t\t(0xB0ED <= code && code <= 0xB107) || // Lo [27] HANGUL SYLLABLE NYAEG..HANGUL SYLLABLE NYAEH\r\n\t\t(0xB109 <= code && code <= 0xB123) || // Lo [27] HANGUL SYLLABLE NEOG..HANGUL SYLLABLE NEOH\r\n\t\t(0xB125 <= code && code <= 0xB13F) || // Lo [27] HANGUL SYLLABLE NEG..HANGUL SYLLABLE NEH\r\n\t\t(0xB141 <= code && code <= 0xB15B) || // Lo [27] HANGUL SYLLABLE NYEOG..HANGUL SYLLABLE NYEOH\r\n\t\t(0xB15D <= code && code <= 0xB177) || // Lo [27] HANGUL SYLLABLE NYEG..HANGUL SYLLABLE NYEH\r\n\t\t(0xB179 <= code && code <= 0xB193) || // Lo [27] HANGUL SYLLABLE NOG..HANGUL SYLLABLE NOH\r\n\t\t(0xB195 <= code && code <= 0xB1AF) || // Lo [27] HANGUL SYLLABLE NWAG..HANGUL SYLLABLE NWAH\r\n\t\t(0xB1B1 <= code && code <= 0xB1CB) || // Lo [27] HANGUL SYLLABLE NWAEG..HANGUL SYLLABLE NWAEH\r\n\t\t(0xB1CD <= code && code <= 0xB1E7) || // Lo [27] HANGUL SYLLABLE NOEG..HANGUL SYLLABLE NOEH\r\n\t\t(0xB1E9 <= code && code <= 0xB203) || // Lo [27] HANGUL SYLLABLE NYOG..HANGUL SYLLABLE NYOH\r\n\t\t(0xB205 <= code && code <= 0xB21F) || // Lo [27] HANGUL SYLLABLE NUG..HANGUL SYLLABLE NUH\r\n\t\t(0xB221 <= code && code <= 0xB23B) || // Lo [27] HANGUL SYLLABLE NWEOG..HANGUL SYLLABLE NWEOH\r\n\t\t(0xB23D <= code && code <= 0xB257) || // Lo [27] HANGUL SYLLABLE NWEG..HANGUL SYLLABLE NWEH\r\n\t\t(0xB259 <= code && code <= 0xB273) || // Lo [27] HANGUL SYLLABLE NWIG..HANGUL SYLLABLE NWIH\r\n\t\t(0xB275 <= code && code <= 0xB28F) || // Lo [27] HANGUL SYLLABLE NYUG..HANGUL SYLLABLE NYUH\r\n\t\t(0xB291 <= code && code <= 0xB2AB) || // Lo [27] HANGUL SYLLABLE NEUG..HANGUL SYLLABLE NEUH\r\n\t\t(0xB2AD <= code && code <= 0xB2C7) || // Lo [27] HANGUL SYLLABLE NYIG..HANGUL SYLLABLE NYIH\r\n\t\t(0xB2C9 <= code && code <= 0xB2E3) || // Lo [27] HANGUL SYLLABLE NIG..HANGUL SYLLABLE NIH\r\n\t\t(0xB2E5 <= code && code <= 0xB2FF) || // Lo [27] HANGUL SYLLABLE DAG..HANGUL SYLLABLE DAH\r\n\t\t(0xB301 <= code && code <= 0xB31B) || // Lo [27] HANGUL SYLLABLE DAEG..HANGUL SYLLABLE DAEH\r\n\t\t(0xB31D <= code && code <= 0xB337) || // Lo [27] HANGUL SYLLABLE DYAG..HANGUL SYLLABLE DYAH\r\n\t\t(0xB339 <= code && code <= 0xB353) || // Lo [27] HANGUL SYLLABLE DYAEG..HANGUL SYLLABLE DYAEH\r\n\t\t(0xB355 <= code && code <= 0xB36F) || // Lo [27] HANGUL SYLLABLE DEOG..HANGUL SYLLABLE DEOH\r\n\t\t(0xB371 <= code && code <= 0xB38B) || // Lo [27] HANGUL SYLLABLE DEG..HANGUL SYLLABLE DEH\r\n\t\t(0xB38D <= code && code <= 0xB3A7) || // Lo [27] HANGUL SYLLABLE DYEOG..HANGUL SYLLABLE DYEOH\r\n\t\t(0xB3A9 <= code && code <= 0xB3C3) || // Lo [27] HANGUL SYLLABLE DYEG..HANGUL SYLLABLE DYEH\r\n\t\t(0xB3C5 <= code && code <= 0xB3DF) || // Lo [27] HANGUL SYLLABLE DOG..HANGUL SYLLABLE DOH\r\n\t\t(0xB3E1 <= code && code <= 0xB3FB) || // Lo [27] HANGUL SYLLABLE DWAG..HANGUL SYLLABLE DWAH\r\n\t\t(0xB3FD <= code && code <= 0xB417) || // Lo [27] HANGUL SYLLABLE DWAEG..HANGUL SYLLABLE DWAEH\r\n\t\t(0xB419 <= code && code <= 0xB433) || // Lo [27] HANGUL SYLLABLE DOEG..HANGUL SYLLABLE DOEH\r\n\t\t(0xB435 <= code && code <= 0xB44F) || // Lo [27] HANGUL SYLLABLE DYOG..HANGUL SYLLABLE DYOH\r\n\t\t(0xB451 <= code && code <= 0xB46B) || // Lo [27] HANGUL SYLLABLE DUG..HANGUL SYLLABLE DUH\r\n\t\t(0xB46D <= code && code <= 0xB487) || // Lo [27] HANGUL SYLLABLE DWEOG..HANGUL SYLLABLE DWEOH\r\n\t\t(0xB489 <= code && code <= 0xB4A3) || // Lo [27] HANGUL SYLLABLE DWEG..HANGUL SYLLABLE DWEH\r\n\t\t(0xB4A5 <= code && code <= 0xB4BF) || // Lo [27] HANGUL SYLLABLE DWIG..HANGUL SYLLABLE DWIH\r\n\t\t(0xB4C1 <= code && code <= 0xB4DB) || // Lo [27] HANGUL SYLLABLE DYUG..HANGUL SYLLABLE DYUH\r\n\t\t(0xB4DD <= code && code <= 0xB4F7) || // Lo [27] HANGUL SYLLABLE DEUG..HANGUL SYLLABLE DEUH\r\n\t\t(0xB4F9 <= code && code <= 0xB513) || // Lo [27] HANGUL SYLLABLE DYIG..HANGUL SYLLABLE DYIH\r\n\t\t(0xB515 <= code && code <= 0xB52F) || // Lo [27] HANGUL SYLLABLE DIG..HANGUL SYLLABLE DIH\r\n\t\t(0xB531 <= code && code <= 0xB54B) || // Lo [27] HANGUL SYLLABLE DDAG..HANGUL SYLLABLE DDAH\r\n\t\t(0xB54D <= code && code <= 0xB567) || // Lo [27] HANGUL SYLLABLE DDAEG..HANGUL SYLLABLE DDAEH\r\n\t\t(0xB569 <= code && code <= 0xB583) || // Lo [27] HANGUL SYLLABLE DDYAG..HANGUL SYLLABLE DDYAH\r\n\t\t(0xB585 <= code && code <= 0xB59F) || // Lo [27] HANGUL SYLLABLE DDYAEG..HANGUL SYLLABLE DDYAEH\r\n\t\t(0xB5A1 <= code && code <= 0xB5BB) || // Lo [27] HANGUL SYLLABLE DDEOG..HANGUL SYLLABLE DDEOH\r\n\t\t(0xB5BD <= code && code <= 0xB5D7) || // Lo [27] HANGUL SYLLABLE DDEG..HANGUL SYLLABLE DDEH\r\n\t\t(0xB5D9 <= code && code <= 0xB5F3) || // Lo [27] HANGUL SYLLABLE DDYEOG..HANGUL SYLLABLE DDYEOH\r\n\t\t(0xB5F5 <= code && code <= 0xB60F) || // Lo [27] HANGUL SYLLABLE DDYEG..HANGUL SYLLABLE DDYEH\r\n\t\t(0xB611 <= code && code <= 0xB62B) || // Lo [27] HANGUL SYLLABLE DDOG..HANGUL SYLLABLE DDOH\r\n\t\t(0xB62D <= code && code <= 0xB647) || // Lo [27] HANGUL SYLLABLE DDWAG..HANGUL SYLLABLE DDWAH\r\n\t\t(0xB649 <= code && code <= 0xB663) || // Lo [27] HANGUL SYLLABLE DDWAEG..HANGUL SYLLABLE DDWAEH\r\n\t\t(0xB665 <= code && code <= 0xB67F) || // Lo [27] HANGUL SYLLABLE DDOEG..HANGUL SYLLABLE DDOEH\r\n\t\t(0xB681 <= code && code <= 0xB69B) || // Lo [27] HANGUL SYLLABLE DDYOG..HANGUL SYLLABLE DDYOH\r\n\t\t(0xB69D <= code && code <= 0xB6B7) || // Lo [27] HANGUL SYLLABLE DDUG..HANGUL SYLLABLE DDUH\r\n\t\t(0xB6B9 <= code && code <= 0xB6D3) || // Lo [27] HANGUL SYLLABLE DDWEOG..HANGUL SYLLABLE DDWEOH\r\n\t\t(0xB6D5 <= code && code <= 0xB6EF) || // Lo [27] HANGUL SYLLABLE DDWEG..HANGUL SYLLABLE DDWEH\r\n\t\t(0xB6F1 <= code && code <= 0xB70B) || // Lo [27] HANGUL SYLLABLE DDWIG..HANGUL SYLLABLE DDWIH\r\n\t\t(0xB70D <= code && code <= 0xB727) || // Lo [27] HANGUL SYLLABLE DDYUG..HANGUL SYLLABLE DDYUH\r\n\t\t(0xB729 <= code && code <= 0xB743) || // Lo [27] HANGUL SYLLABLE DDEUG..HANGUL SYLLABLE DDEUH\r\n\t\t(0xB745 <= code && code <= 0xB75F) || // Lo [27] HANGUL SYLLABLE DDYIG..HANGUL SYLLABLE DDYIH\r\n\t\t(0xB761 <= code && code <= 0xB77B) || // Lo [27] HANGUL SYLLABLE DDIG..HANGUL SYLLABLE DDIH\r\n\t\t(0xB77D <= code && code <= 0xB797) || // Lo [27] HANGUL SYLLABLE RAG..HANGUL SYLLABLE RAH\r\n\t\t(0xB799 <= code && code <= 0xB7B3) || // Lo [27] HANGUL SYLLABLE RAEG..HANGUL SYLLABLE RAEH\r\n\t\t(0xB7B5 <= code && code <= 0xB7CF) || // Lo [27] HANGUL SYLLABLE RYAG..HANGUL SYLLABLE RYAH\r\n\t\t(0xB7D1 <= code && code <= 0xB7EB) || // Lo [27] HANGUL SYLLABLE RYAEG..HANGUL SYLLABLE RYAEH\r\n\t\t(0xB7ED <= code && code <= 0xB807) || // Lo [27] HANGUL SYLLABLE REOG..HANGUL SYLLABLE REOH\r\n\t\t(0xB809 <= code && code <= 0xB823) || // Lo [27] HANGUL SYLLABLE REG..HANGUL SYLLABLE REH\r\n\t\t(0xB825 <= code && code <= 0xB83F) || // Lo [27] HANGUL SYLLABLE RYEOG..HANGUL SYLLABLE RYEOH\r\n\t\t(0xB841 <= code && code <= 0xB85B) || // Lo [27] HANGUL SYLLABLE RYEG..HANGUL SYLLABLE RYEH\r\n\t\t(0xB85D <= code && code <= 0xB877) || // Lo [27] HANGUL SYLLABLE ROG..HANGUL SYLLABLE ROH\r\n\t\t(0xB879 <= code && code <= 0xB893) || // Lo [27] HANGUL SYLLABLE RWAG..HANGUL SYLLABLE RWAH\r\n\t\t(0xB895 <= code && code <= 0xB8AF) || // Lo [27] HANGUL SYLLABLE RWAEG..HANGUL SYLLABLE RWAEH\r\n\t\t(0xB8B1 <= code && code <= 0xB8CB) || // Lo [27] HANGUL SYLLABLE ROEG..HANGUL SYLLABLE ROEH\r\n\t\t(0xB8CD <= code && code <= 0xB8E7) || // Lo [27] HANGUL SYLLABLE RYOG..HANGUL SYLLABLE RYOH\r\n\t\t(0xB8E9 <= code && code <= 0xB903) || // Lo [27] HANGUL SYLLABLE RUG..HANGUL SYLLABLE RUH\r\n\t\t(0xB905 <= code && code <= 0xB91F) || // Lo [27] HANGUL SYLLABLE RWEOG..HANGUL SYLLABLE RWEOH\r\n\t\t(0xB921 <= code && code <= 0xB93B) || // Lo [27] HANGUL SYLLABLE RWEG..HANGUL SYLLABLE RWEH\r\n\t\t(0xB93D <= code && code <= 0xB957) || // Lo [27] HANGUL SYLLABLE RWIG..HANGUL SYLLABLE RWIH\r\n\t\t(0xB959 <= code && code <= 0xB973) || // Lo [27] HANGUL SYLLABLE RYUG..HANGUL SYLLABLE RYUH\r\n\t\t(0xB975 <= code && code <= 0xB98F) || // Lo [27] HANGUL SYLLABLE REUG..HANGUL SYLLABLE REUH\r\n\t\t(0xB991 <= code && code <= 0xB9AB) || // Lo [27] HANGUL SYLLABLE RYIG..HANGUL SYLLABLE RYIH\r\n\t\t(0xB9AD <= code && code <= 0xB9C7) || // Lo [27] HANGUL SYLLABLE RIG..HANGUL SYLLABLE RIH\r\n\t\t(0xB9C9 <= code && code <= 0xB9E3) || // Lo [27] HANGUL SYLLABLE MAG..HANGUL SYLLABLE MAH\r\n\t\t(0xB9E5 <= code && code <= 0xB9FF) || // Lo [27] HANGUL SYLLABLE MAEG..HANGUL SYLLABLE MAEH\r\n\t\t(0xBA01 <= code && code <= 0xBA1B) || // Lo [27] HANGUL SYLLABLE MYAG..HANGUL SYLLABLE MYAH\r\n\t\t(0xBA1D <= code && code <= 0xBA37) || // Lo [27] HANGUL SYLLABLE MYAEG..HANGUL SYLLABLE MYAEH\r\n\t\t(0xBA39 <= code && code <= 0xBA53) || // Lo [27] HANGUL SYLLABLE MEOG..HANGUL SYLLABLE MEOH\r\n\t\t(0xBA55 <= code && code <= 0xBA6F) || // Lo [27] HANGUL SYLLABLE MEG..HANGUL SYLLABLE MEH\r\n\t\t(0xBA71 <= code && code <= 0xBA8B) || // Lo [27] HANGUL SYLLABLE MYEOG..HANGUL SYLLABLE MYEOH\r\n\t\t(0xBA8D <= code && code <= 0xBAA7) || // Lo [27] HANGUL SYLLABLE MYEG..HANGUL SYLLABLE MYEH\r\n\t\t(0xBAA9 <= code && code <= 0xBAC3) || // Lo [27] HANGUL SYLLABLE MOG..HANGUL SYLLABLE MOH\r\n\t\t(0xBAC5 <= code && code <= 0xBADF) || // Lo [27] HANGUL SYLLABLE MWAG..HANGUL SYLLABLE MWAH\r\n\t\t(0xBAE1 <= code && code <= 0xBAFB) || // Lo [27] HANGUL SYLLABLE MWAEG..HANGUL SYLLABLE MWAEH\r\n\t\t(0xBAFD <= code && code <= 0xBB17) || // Lo [27] HANGUL SYLLABLE MOEG..HANGUL SYLLABLE MOEH\r\n\t\t(0xBB19 <= code && code <= 0xBB33) || // Lo [27] HANGUL SYLLABLE MYOG..HANGUL SYLLABLE MYOH\r\n\t\t(0xBB35 <= code && code <= 0xBB4F) || // Lo [27] HANGUL SYLLABLE MUG..HANGUL SYLLABLE MUH\r\n\t\t(0xBB51 <= code && code <= 0xBB6B) || // Lo [27] HANGUL SYLLABLE MWEOG..HANGUL SYLLABLE MWEOH\r\n\t\t(0xBB6D <= code && code <= 0xBB87) || // Lo [27] HANGUL SYLLABLE MWEG..HANGUL SYLLABLE MWEH\r\n\t\t(0xBB89 <= code && code <= 0xBBA3) || // Lo [27] HANGUL SYLLABLE MWIG..HANGUL SYLLABLE MWIH\r\n\t\t(0xBBA5 <= code && code <= 0xBBBF) || // Lo [27] HANGUL SYLLABLE MYUG..HANGUL SYLLABLE MYUH\r\n\t\t(0xBBC1 <= code && code <= 0xBBDB) || // Lo [27] HANGUL SYLLABLE MEUG..HANGUL SYLLABLE MEUH\r\n\t\t(0xBBDD <= code && code <= 0xBBF7) || // Lo [27] HANGUL SYLLABLE MYIG..HANGUL SYLLABLE MYIH\r\n\t\t(0xBBF9 <= code && code <= 0xBC13) || // Lo [27] HANGUL SYLLABLE MIG..HANGUL SYLLABLE MIH\r\n\t\t(0xBC15 <= code && code <= 0xBC2F) || // Lo [27] HANGUL SYLLABLE BAG..HANGUL SYLLABLE BAH\r\n\t\t(0xBC31 <= code && code <= 0xBC4B) || // Lo [27] HANGUL SYLLABLE BAEG..HANGUL SYLLABLE BAEH\r\n\t\t(0xBC4D <= code && code <= 0xBC67) || // Lo [27] HANGUL SYLLABLE BYAG..HANGUL SYLLABLE BYAH\r\n\t\t(0xBC69 <= code && code <= 0xBC83) || // Lo [27] HANGUL SYLLABLE BYAEG..HANGUL SYLLABLE BYAEH\r\n\t\t(0xBC85 <= code && code <= 0xBC9F) || // Lo [27] HANGUL SYLLABLE BEOG..HANGUL SYLLABLE BEOH\r\n\t\t(0xBCA1 <= code && code <= 0xBCBB) || // Lo [27] HANGUL SYLLABLE BEG..HANGUL SYLLABLE BEH\r\n\t\t(0xBCBD <= code && code <= 0xBCD7) || // Lo [27] HANGUL SYLLABLE BYEOG..HANGUL SYLLABLE BYEOH\r\n\t\t(0xBCD9 <= code && code <= 0xBCF3) || // Lo [27] HANGUL SYLLABLE BYEG..HANGUL SYLLABLE BYEH\r\n\t\t(0xBCF5 <= code && code <= 0xBD0F) || // Lo [27] HANGUL SYLLABLE BOG..HANGUL SYLLABLE BOH\r\n\t\t(0xBD11 <= code && code <= 0xBD2B) || // Lo [27] HANGUL SYLLABLE BWAG..HANGUL SYLLABLE BWAH\r\n\t\t(0xBD2D <= code && code <= 0xBD47) || // Lo [27] HANGUL SYLLABLE BWAEG..HANGUL SYLLABLE BWAEH\r\n\t\t(0xBD49 <= code && code <= 0xBD63) || // Lo [27] HANGUL SYLLABLE BOEG..HANGUL SYLLABLE BOEH\r\n\t\t(0xBD65 <= code && code <= 0xBD7F) || // Lo [27] HANGUL SYLLABLE BYOG..HANGUL SYLLABLE BYOH\r\n\t\t(0xBD81 <= code && code <= 0xBD9B) || // Lo [27] HANGUL SYLLABLE BUG..HANGUL SYLLABLE BUH\r\n\t\t(0xBD9D <= code && code <= 0xBDB7) || // Lo [27] HANGUL SYLLABLE BWEOG..HANGUL SYLLABLE BWEOH\r\n\t\t(0xBDB9 <= code && code <= 0xBDD3) || // Lo [27] HANGUL SYLLABLE BWEG..HANGUL SYLLABLE BWEH\r\n\t\t(0xBDD5 <= code && code <= 0xBDEF) || // Lo [27] HANGUL SYLLABLE BWIG..HANGUL SYLLABLE BWIH\r\n\t\t(0xBDF1 <= code && code <= 0xBE0B) || // Lo [27] HANGUL SYLLABLE BYUG..HANGUL SYLLABLE BYUH\r\n\t\t(0xBE0D <= code && code <= 0xBE27) || // Lo [27] HANGUL SYLLABLE BEUG..HANGUL SYLLABLE BEUH\r\n\t\t(0xBE29 <= code && code <= 0xBE43) || // Lo [27] HANGUL SYLLABLE BYIG..HANGUL SYLLABLE BYIH\r\n\t\t(0xBE45 <= code && code <= 0xBE5F) || // Lo [27] HANGUL SYLLABLE BIG..HANGUL SYLLABLE BIH\r\n\t\t(0xBE61 <= code && code <= 0xBE7B) || // Lo [27] HANGUL SYLLABLE BBAG..HANGUL SYLLABLE BBAH\r\n\t\t(0xBE7D <= code && code <= 0xBE97) || // Lo [27] HANGUL SYLLABLE BBAEG..HANGUL SYLLABLE BBAEH\r\n\t\t(0xBE99 <= code && code <= 0xBEB3) || // Lo [27] HANGUL SYLLABLE BBYAG..HANGUL SYLLABLE BBYAH\r\n\t\t(0xBEB5 <= code && code <= 0xBECF) || // Lo [27] HANGUL SYLLABLE BBYAEG..HANGUL SYLLABLE BBYAEH\r\n\t\t(0xBED1 <= code && code <= 0xBEEB) || // Lo [27] HANGUL SYLLABLE BBEOG..HANGUL SYLLABLE BBEOH\r\n\t\t(0xBEED <= code && code <= 0xBF07) || // Lo [27] HANGUL SYLLABLE BBEG..HANGUL SYLLABLE BBEH\r\n\t\t(0xBF09 <= code && code <= 0xBF23) || // Lo [27] HANGUL SYLLABLE BBYEOG..HANGUL SYLLABLE BBYEOH\r\n\t\t(0xBF25 <= code && code <= 0xBF3F) || // Lo [27] HANGUL SYLLABLE BBYEG..HANGUL SYLLABLE BBYEH\r\n\t\t(0xBF41 <= code && code <= 0xBF5B) || // Lo [27] HANGUL SYLLABLE BBOG..HANGUL SYLLABLE BBOH\r\n\t\t(0xBF5D <= code && code <= 0xBF77) || // Lo [27] HANGUL SYLLABLE BBWAG..HANGUL SYLLABLE BBWAH\r\n\t\t(0xBF79 <= code && code <= 0xBF93) || // Lo [27] HANGUL SYLLABLE BBWAEG..HANGUL SYLLABLE BBWAEH\r\n\t\t(0xBF95 <= code && code <= 0xBFAF) || // Lo [27] HANGUL SYLLABLE BBOEG..HANGUL SYLLABLE BBOEH\r\n\t\t(0xBFB1 <= code && code <= 0xBFCB) || // Lo [27] HANGUL SYLLABLE BBYOG..HANGUL SYLLABLE BBYOH\r\n\t\t(0xBFCD <= code && code <= 0xBFE7) || // Lo [27] HANGUL SYLLABLE BBUG..HANGUL SYLLABLE BBUH\r\n\t\t(0xBFE9 <= code && code <= 0xC003) || // Lo [27] HANGUL SYLLABLE BBWEOG..HANGUL SYLLABLE BBWEOH\r\n\t\t(0xC005 <= code && code <= 0xC01F) || // Lo [27] HANGUL SYLLABLE BBWEG..HANGUL SYLLABLE BBWEH\r\n\t\t(0xC021 <= code && code <= 0xC03B) || // Lo [27] HANGUL SYLLABLE BBWIG..HANGUL SYLLABLE BBWIH\r\n\t\t(0xC03D <= code && code <= 0xC057) || // Lo [27] HANGUL SYLLABLE BBYUG..HANGUL SYLLABLE BBYUH\r\n\t\t(0xC059 <= code && code <= 0xC073) || // Lo [27] HANGUL SYLLABLE BBEUG..HANGUL SYLLABLE BBEUH\r\n\t\t(0xC075 <= code && code <= 0xC08F) || // Lo [27] HANGUL SYLLABLE BBYIG..HANGUL SYLLABLE BBYIH\r\n\t\t(0xC091 <= code && code <= 0xC0AB) || // Lo [27] HANGUL SYLLABLE BBIG..HANGUL SYLLABLE BBIH\r\n\t\t(0xC0AD <= code && code <= 0xC0C7) || // Lo [27] HANGUL SYLLABLE SAG..HANGUL SYLLABLE SAH\r\n\t\t(0xC0C9 <= code && code <= 0xC0E3) || // Lo [27] HANGUL SYLLABLE SAEG..HANGUL SYLLABLE SAEH\r\n\t\t(0xC0E5 <= code && code <= 0xC0FF) || // Lo [27] HANGUL SYLLABLE SYAG..HANGUL SYLLABLE SYAH\r\n\t\t(0xC101 <= code && code <= 0xC11B) || // Lo [27] HANGUL SYLLABLE SYAEG..HANGUL SYLLABLE SYAEH\r\n\t\t(0xC11D <= code && code <= 0xC137) || // Lo [27] HANGUL SYLLABLE SEOG..HANGUL SYLLABLE SEOH\r\n\t\t(0xC139 <= code && code <= 0xC153) || // Lo [27] HANGUL SYLLABLE SEG..HANGUL SYLLABLE SEH\r\n\t\t(0xC155 <= code && code <= 0xC16F) || // Lo [27] HANGUL SYLLABLE SYEOG..HANGUL SYLLABLE SYEOH\r\n\t\t(0xC171 <= code && code <= 0xC18B) || // Lo [27] HANGUL SYLLABLE SYEG..HANGUL SYLLABLE SYEH\r\n\t\t(0xC18D <= code && code <= 0xC1A7) || // Lo [27] HANGUL SYLLABLE SOG..HANGUL SYLLABLE SOH\r\n\t\t(0xC1A9 <= code && code <= 0xC1C3) || // Lo [27] HANGUL SYLLABLE SWAG..HANGUL SYLLABLE SWAH\r\n\t\t(0xC1C5 <= code && code <= 0xC1DF) || // Lo [27] HANGUL SYLLABLE SWAEG..HANGUL SYLLABLE SWAEH\r\n\t\t(0xC1E1 <= code && code <= 0xC1FB) || // Lo [27] HANGUL SYLLABLE SOEG..HANGUL SYLLABLE SOEH\r\n\t\t(0xC1FD <= code && code <= 0xC217) || // Lo [27] HANGUL SYLLABLE SYOG..HANGUL SYLLABLE SYOH\r\n\t\t(0xC219 <= code && code <= 0xC233) || // Lo [27] HANGUL SYLLABLE SUG..HANGUL SYLLABLE SUH\r\n\t\t(0xC235 <= code && code <= 0xC24F) || // Lo [27] HANGUL SYLLABLE SWEOG..HANGUL SYLLABLE SWEOH\r\n\t\t(0xC251 <= code && code <= 0xC26B) || // Lo [27] HANGUL SYLLABLE SWEG..HANGUL SYLLABLE SWEH\r\n\t\t(0xC26D <= code && code <= 0xC287) || // Lo [27] HANGUL SYLLABLE SWIG..HANGUL SYLLABLE SWIH\r\n\t\t(0xC289 <= code && code <= 0xC2A3) || // Lo [27] HANGUL SYLLABLE SYUG..HANGUL SYLLABLE SYUH\r\n\t\t(0xC2A5 <= code && code <= 0xC2BF) || // Lo [27] HANGUL SYLLABLE SEUG..HANGUL SYLLABLE SEUH\r\n\t\t(0xC2C1 <= code && code <= 0xC2DB) || // Lo [27] HANGUL SYLLABLE SYIG..HANGUL SYLLABLE SYIH\r\n\t\t(0xC2DD <= code && code <= 0xC2F7) || // Lo [27] HANGUL SYLLABLE SIG..HANGUL SYLLABLE SIH\r\n\t\t(0xC2F9 <= code && code <= 0xC313) || // Lo [27] HANGUL SYLLABLE SSAG..HANGUL SYLLABLE SSAH\r\n\t\t(0xC315 <= code && code <= 0xC32F) || // Lo [27] HANGUL SYLLABLE SSAEG..HANGUL SYLLABLE SSAEH\r\n\t\t(0xC331 <= code && code <= 0xC34B) || // Lo [27] HANGUL SYLLABLE SSYAG..HANGUL SYLLABLE SSYAH\r\n\t\t(0xC34D <= code && code <= 0xC367) || // Lo [27] HANGUL SYLLABLE SSYAEG..HANGUL SYLLABLE SSYAEH\r\n\t\t(0xC369 <= code && code <= 0xC383) || // Lo [27] HANGUL SYLLABLE SSEOG..HANGUL SYLLABLE SSEOH\r\n\t\t(0xC385 <= code && code <= 0xC39F) || // Lo [27] HANGUL SYLLABLE SSEG..HANGUL SYLLABLE SSEH\r\n\t\t(0xC3A1 <= code && code <= 0xC3BB) || // Lo [27] HANGUL SYLLABLE SSYEOG..HANGUL SYLLABLE SSYEOH\r\n\t\t(0xC3BD <= code && code <= 0xC3D7) || // Lo [27] HANGUL SYLLABLE SSYEG..HANGUL SYLLABLE SSYEH\r\n\t\t(0xC3D9 <= code && code <= 0xC3F3) || // Lo [27] HANGUL SYLLABLE SSOG..HANGUL SYLLABLE SSOH\r\n\t\t(0xC3F5 <= code && code <= 0xC40F) || // Lo [27] HANGUL SYLLABLE SSWAG..HANGUL SYLLABLE SSWAH\r\n\t\t(0xC411 <= code && code <= 0xC42B) || // Lo [27] HANGUL SYLLABLE SSWAEG..HANGUL SYLLABLE SSWAEH\r\n\t\t(0xC42D <= code && code <= 0xC447) || // Lo [27] HANGUL SYLLABLE SSOEG..HANGUL SYLLABLE SSOEH\r\n\t\t(0xC449 <= code && code <= 0xC463) || // Lo [27] HANGUL SYLLABLE SSYOG..HANGUL SYLLABLE SSYOH\r\n\t\t(0xC465 <= code && code <= 0xC47F) || // Lo [27] HANGUL SYLLABLE SSUG..HANGUL SYLLABLE SSUH\r\n\t\t(0xC481 <= code && code <= 0xC49B) || // Lo [27] HANGUL SYLLABLE SSWEOG..HANGUL SYLLABLE SSWEOH\r\n\t\t(0xC49D <= code && code <= 0xC4B7) || // Lo [27] HANGUL SYLLABLE SSWEG..HANGUL SYLLABLE SSWEH\r\n\t\t(0xC4B9 <= code && code <= 0xC4D3) || // Lo [27] HANGUL SYLLABLE SSWIG..HANGUL SYLLABLE SSWIH\r\n\t\t(0xC4D5 <= code && code <= 0xC4EF) || // Lo [27] HANGUL SYLLABLE SSYUG..HANGUL SYLLABLE SSYUH\r\n\t\t(0xC4F1 <= code && code <= 0xC50B) || // Lo [27] HANGUL SYLLABLE SSEUG..HANGUL SYLLABLE SSEUH\r\n\t\t(0xC50D <= code && code <= 0xC527) || // Lo [27] HANGUL SYLLABLE SSYIG..HANGUL SYLLABLE SSYIH\r\n\t\t(0xC529 <= code && code <= 0xC543) || // Lo [27] HANGUL SYLLABLE SSIG..HANGUL SYLLABLE SSIH\r\n\t\t(0xC545 <= code && code <= 0xC55F) || // Lo [27] HANGUL SYLLABLE AG..HANGUL SYLLABLE AH\r\n\t\t(0xC561 <= code && code <= 0xC57B) || // Lo [27] HANGUL SYLLABLE AEG..HANGUL SYLLABLE AEH\r\n\t\t(0xC57D <= code && code <= 0xC597) || // Lo [27] HANGUL SYLLABLE YAG..HANGUL SYLLABLE YAH\r\n\t\t(0xC599 <= code && code <= 0xC5B3) || // Lo [27] HANGUL SYLLABLE YAEG..HANGUL SYLLABLE YAEH\r\n\t\t(0xC5B5 <= code && code <= 0xC5CF) || // Lo [27] HANGUL SYLLABLE EOG..HANGUL SYLLABLE EOH\r\n\t\t(0xC5D1 <= code && code <= 0xC5EB) || // Lo [27] HANGUL SYLLABLE EG..HANGUL SYLLABLE EH\r\n\t\t(0xC5ED <= code && code <= 0xC607) || // Lo [27] HANGUL SYLLABLE YEOG..HANGUL SYLLABLE YEOH\r\n\t\t(0xC609 <= code && code <= 0xC623) || // Lo [27] HANGUL SYLLABLE YEG..HANGUL SYLLABLE YEH\r\n\t\t(0xC625 <= code && code <= 0xC63F) || // Lo [27] HANGUL SYLLABLE OG..HANGUL SYLLABLE OH\r\n\t\t(0xC641 <= code && code <= 0xC65B) || // Lo [27] HANGUL SYLLABLE WAG..HANGUL SYLLABLE WAH\r\n\t\t(0xC65D <= code && code <= 0xC677) || // Lo [27] HANGUL SYLLABLE WAEG..HANGUL SYLLABLE WAEH\r\n\t\t(0xC679 <= code && code <= 0xC693) || // Lo [27] HANGUL SYLLABLE OEG..HANGUL SYLLABLE OEH\r\n\t\t(0xC695 <= code && code <= 0xC6AF) || // Lo [27] HANGUL SYLLABLE YOG..HANGUL SYLLABLE YOH\r\n\t\t(0xC6B1 <= code && code <= 0xC6CB) || // Lo [27] HANGUL SYLLABLE UG..HANGUL SYLLABLE UH\r\n\t\t(0xC6CD <= code && code <= 0xC6E7) || // Lo [27] HANGUL SYLLABLE WEOG..HANGUL SYLLABLE WEOH\r\n\t\t(0xC6E9 <= code && code <= 0xC703) || // Lo [27] HANGUL SYLLABLE WEG..HANGUL SYLLABLE WEH\r\n\t\t(0xC705 <= code && code <= 0xC71F) || // Lo [27] HANGUL SYLLABLE WIG..HANGUL SYLLABLE WIH\r\n\t\t(0xC721 <= code && code <= 0xC73B) || // Lo [27] HANGUL SYLLABLE YUG..HANGUL SYLLABLE YUH\r\n\t\t(0xC73D <= code && code <= 0xC757) || // Lo [27] HANGUL SYLLABLE EUG..HANGUL SYLLABLE EUH\r\n\t\t(0xC759 <= code && code <= 0xC773) || // Lo [27] HANGUL SYLLABLE YIG..HANGUL SYLLABLE YIH\r\n\t\t(0xC775 <= code && code <= 0xC78F) || // Lo [27] HANGUL SYLLABLE IG..HANGUL SYLLABLE IH\r\n\t\t(0xC791 <= code && code <= 0xC7AB) || // Lo [27] HANGUL SYLLABLE JAG..HANGUL SYLLABLE JAH\r\n\t\t(0xC7AD <= code && code <= 0xC7C7) || // Lo [27] HANGUL SYLLABLE JAEG..HANGUL SYLLABLE JAEH\r\n\t\t(0xC7C9 <= code && code <= 0xC7E3) || // Lo [27] HANGUL SYLLABLE JYAG..HANGUL SYLLABLE JYAH\r\n\t\t(0xC7E5 <= code && code <= 0xC7FF) || // Lo [27] HANGUL SYLLABLE JYAEG..HANGUL SYLLABLE JYAEH\r\n\t\t(0xC801 <= code && code <= 0xC81B) || // Lo [27] HANGUL SYLLABLE JEOG..HANGUL SYLLABLE JEOH\r\n\t\t(0xC81D <= code && code <= 0xC837) || // Lo [27] HANGUL SYLLABLE JEG..HANGUL SYLLABLE JEH\r\n\t\t(0xC839 <= code && code <= 0xC853) || // Lo [27] HANGUL SYLLABLE JYEOG..HANGUL SYLLABLE JYEOH\r\n\t\t(0xC855 <= code && code <= 0xC86F) || // Lo [27] HANGUL SYLLABLE JYEG..HANGUL SYLLABLE JYEH\r\n\t\t(0xC871 <= code && code <= 0xC88B) || // Lo [27] HANGUL SYLLABLE JOG..HANGUL SYLLABLE JOH\r\n\t\t(0xC88D <= code && code <= 0xC8A7) || // Lo [27] HANGUL SYLLABLE JWAG..HANGUL SYLLABLE JWAH\r\n\t\t(0xC8A9 <= code && code <= 0xC8C3) || // Lo [27] HANGUL SYLLABLE JWAEG..HANGUL SYLLABLE JWAEH\r\n\t\t(0xC8C5 <= code && code <= 0xC8DF) || // Lo [27] HANGUL SYLLABLE JOEG..HANGUL SYLLABLE JOEH\r\n\t\t(0xC8E1 <= code && code <= 0xC8FB) || // Lo [27] HANGUL SYLLABLE JYOG..HANGUL SYLLABLE JYOH\r\n\t\t(0xC8FD <= code && code <= 0xC917) || // Lo [27] HANGUL SYLLABLE JUG..HANGUL SYLLABLE JUH\r\n\t\t(0xC919 <= code && code <= 0xC933) || // Lo [27] HANGUL SYLLABLE JWEOG..HANGUL SYLLABLE JWEOH\r\n\t\t(0xC935 <= code && code <= 0xC94F) || // Lo [27] HANGUL SYLLABLE JWEG..HANGUL SYLLABLE JWEH\r\n\t\t(0xC951 <= code && code <= 0xC96B) || // Lo [27] HANGUL SYLLABLE JWIG..HANGUL SYLLABLE JWIH\r\n\t\t(0xC96D <= code && code <= 0xC987) || // Lo [27] HANGUL SYLLABLE JYUG..HANGUL SYLLABLE JYUH\r\n\t\t(0xC989 <= code && code <= 0xC9A3) || // Lo [27] HANGUL SYLLABLE JEUG..HANGUL SYLLABLE JEUH\r\n\t\t(0xC9A5 <= code && code <= 0xC9BF) || // Lo [27] HANGUL SYLLABLE JYIG..HANGUL SYLLABLE JYIH\r\n\t\t(0xC9C1 <= code && code <= 0xC9DB) || // Lo [27] HANGUL SYLLABLE JIG..HANGUL SYLLABLE JIH\r\n\t\t(0xC9DD <= code && code <= 0xC9F7) || // Lo [27] HANGUL SYLLABLE JJAG..HANGUL SYLLABLE JJAH\r\n\t\t(0xC9F9 <= code && code <= 0xCA13) || // Lo [27] HANGUL SYLLABLE JJAEG..HANGUL SYLLABLE JJAEH\r\n\t\t(0xCA15 <= code && code <= 0xCA2F) || // Lo [27] HANGUL SYLLABLE JJYAG..HANGUL SYLLABLE JJYAH\r\n\t\t(0xCA31 <= code && code <= 0xCA4B) || // Lo [27] HANGUL SYLLABLE JJYAEG..HANGUL SYLLABLE JJYAEH\r\n\t\t(0xCA4D <= code && code <= 0xCA67) || // Lo [27] HANGUL SYLLABLE JJEOG..HANGUL SYLLABLE JJEOH\r\n\t\t(0xCA69 <= code && code <= 0xCA83) || // Lo [27] HANGUL SYLLABLE JJEG..HANGUL SYLLABLE JJEH\r\n\t\t(0xCA85 <= code && code <= 0xCA9F) || // Lo [27] HANGUL SYLLABLE JJYEOG..HANGUL SYLLABLE JJYEOH\r\n\t\t(0xCAA1 <= code && code <= 0xCABB) || // Lo [27] HANGUL SYLLABLE JJYEG..HANGUL SYLLABLE JJYEH\r\n\t\t(0xCABD <= code && code <= 0xCAD7) || // Lo [27] HANGUL SYLLABLE JJOG..HANGUL SYLLABLE JJOH\r\n\t\t(0xCAD9 <= code && code <= 0xCAF3) || // Lo [27] HANGUL SYLLABLE JJWAG..HANGUL SYLLABLE JJWAH\r\n\t\t(0xCAF5 <= code && code <= 0xCB0F) || // Lo [27] HANGUL SYLLABLE JJWAEG..HANGUL SYLLABLE JJWAEH\r\n\t\t(0xCB11 <= code && code <= 0xCB2B) || // Lo [27] HANGUL SYLLABLE JJOEG..HANGUL SYLLABLE JJOEH\r\n\t\t(0xCB2D <= code && code <= 0xCB47) || // Lo [27] HANGUL SYLLABLE JJYOG..HANGUL SYLLABLE JJYOH\r\n\t\t(0xCB49 <= code && code <= 0xCB63) || // Lo [27] HANGUL SYLLABLE JJUG..HANGUL SYLLABLE JJUH\r\n\t\t(0xCB65 <= code && code <= 0xCB7F) || // Lo [27] HANGUL SYLLABLE JJWEOG..HANGUL SYLLABLE JJWEOH\r\n\t\t(0xCB81 <= code && code <= 0xCB9B) || // Lo [27] HANGUL SYLLABLE JJWEG..HANGUL SYLLABLE JJWEH\r\n\t\t(0xCB9D <= code && code <= 0xCBB7) || // Lo [27] HANGUL SYLLABLE JJWIG..HANGUL SYLLABLE JJWIH\r\n\t\t(0xCBB9 <= code && code <= 0xCBD3) || // Lo [27] HANGUL SYLLABLE JJYUG..HANGUL SYLLABLE JJYUH\r\n\t\t(0xCBD5 <= code && code <= 0xCBEF) || // Lo [27] HANGUL SYLLABLE JJEUG..HANGUL SYLLABLE JJEUH\r\n\t\t(0xCBF1 <= code && code <= 0xCC0B) || // Lo [27] HANGUL SYLLABLE JJYIG..HANGUL SYLLABLE JJYIH\r\n\t\t(0xCC0D <= code && code <= 0xCC27) || // Lo [27] HANGUL SYLLABLE JJIG..HANGUL SYLLABLE JJIH\r\n\t\t(0xCC29 <= code && code <= 0xCC43) || // Lo [27] HANGUL SYLLABLE CAG..HANGUL SYLLABLE CAH\r\n\t\t(0xCC45 <= code && code <= 0xCC5F) || // Lo [27] HANGUL SYLLABLE CAEG..HANGUL SYLLABLE CAEH\r\n\t\t(0xCC61 <= code && code <= 0xCC7B) || // Lo [27] HANGUL SYLLABLE CYAG..HANGUL SYLLABLE CYAH\r\n\t\t(0xCC7D <= code && code <= 0xCC97) || // Lo [27] HANGUL SYLLABLE CYAEG..HANGUL SYLLABLE CYAEH\r\n\t\t(0xCC99 <= code && code <= 0xCCB3) || // Lo [27] HANGUL SYLLABLE CEOG..HANGUL SYLLABLE CEOH\r\n\t\t(0xCCB5 <= code && code <= 0xCCCF) || // Lo [27] HANGUL SYLLABLE CEG..HANGUL SYLLABLE CEH\r\n\t\t(0xCCD1 <= code && code <= 0xCCEB) || // Lo [27] HANGUL SYLLABLE CYEOG..HANGUL SYLLABLE CYEOH\r\n\t\t(0xCCED <= code && code <= 0xCD07) || // Lo [27] HANGUL SYLLABLE CYEG..HANGUL SYLLABLE CYEH\r\n\t\t(0xCD09 <= code && code <= 0xCD23) || // Lo [27] HANGUL SYLLABLE COG..HANGUL SYLLABLE COH\r\n\t\t(0xCD25 <= code && code <= 0xCD3F) || // Lo [27] HANGUL SYLLABLE CWAG..HANGUL SYLLABLE CWAH\r\n\t\t(0xCD41 <= code && code <= 0xCD5B) || // Lo [27] HANGUL SYLLABLE CWAEG..HANGUL SYLLABLE CWAEH\r\n\t\t(0xCD5D <= code && code <= 0xCD77) || // Lo [27] HANGUL SYLLABLE COEG..HANGUL SYLLABLE COEH\r\n\t\t(0xCD79 <= code && code <= 0xCD93) || // Lo [27] HANGUL SYLLABLE CYOG..HANGUL SYLLABLE CYOH\r\n\t\t(0xCD95 <= code && code <= 0xCDAF) || // Lo [27] HANGUL SYLLABLE CUG..HANGUL SYLLABLE CUH\r\n\t\t(0xCDB1 <= code && code <= 0xCDCB) || // Lo [27] HANGUL SYLLABLE CWEOG..HANGUL SYLLABLE CWEOH\r\n\t\t(0xCDCD <= code && code <= 0xCDE7) || // Lo [27] HANGUL SYLLABLE CWEG..HANGUL SYLLABLE CWEH\r\n\t\t(0xCDE9 <= code && code <= 0xCE03) || // Lo [27] HANGUL SYLLABLE CWIG..HANGUL SYLLABLE CWIH\r\n\t\t(0xCE05 <= code && code <= 0xCE1F) || // Lo [27] HANGUL SYLLABLE CYUG..HANGUL SYLLABLE CYUH\r\n\t\t(0xCE21 <= code && code <= 0xCE3B) || // Lo [27] HANGUL SYLLABLE CEUG..HANGUL SYLLABLE CEUH\r\n\t\t(0xCE3D <= code && code <= 0xCE57) || // Lo [27] HANGUL SYLLABLE CYIG..HANGUL SYLLABLE CYIH\r\n\t\t(0xCE59 <= code && code <= 0xCE73) || // Lo [27] HANGUL SYLLABLE CIG..HANGUL SYLLABLE CIH\r\n\t\t(0xCE75 <= code && code <= 0xCE8F) || // Lo [27] HANGUL SYLLABLE KAG..HANGUL SYLLABLE KAH\r\n\t\t(0xCE91 <= code && code <= 0xCEAB) || // Lo [27] HANGUL SYLLABLE KAEG..HANGUL SYLLABLE KAEH\r\n\t\t(0xCEAD <= code && code <= 0xCEC7) || // Lo [27] HANGUL SYLLABLE KYAG..HANGUL SYLLABLE KYAH\r\n\t\t(0xCEC9 <= code && code <= 0xCEE3) || // Lo [27] HANGUL SYLLABLE KYAEG..HANGUL SYLLABLE KYAEH\r\n\t\t(0xCEE5 <= code && code <= 0xCEFF) || // Lo [27] HANGUL SYLLABLE KEOG..HANGUL SYLLABLE KEOH\r\n\t\t(0xCF01 <= code && code <= 0xCF1B) || // Lo [27] HANGUL SYLLABLE KEG..HANGUL SYLLABLE KEH\r\n\t\t(0xCF1D <= code && code <= 0xCF37) || // Lo [27] HANGUL SYLLABLE KYEOG..HANGUL SYLLABLE KYEOH\r\n\t\t(0xCF39 <= code && code <= 0xCF53) || // Lo [27] HANGUL SYLLABLE KYEG..HANGUL SYLLABLE KYEH\r\n\t\t(0xCF55 <= code && code <= 0xCF6F) || // Lo [27] HANGUL SYLLABLE KOG..HANGUL SYLLABLE KOH\r\n\t\t(0xCF71 <= code && code <= 0xCF8B) || // Lo [27] HANGUL SYLLABLE KWAG..HANGUL SYLLABLE KWAH\r\n\t\t(0xCF8D <= code && code <= 0xCFA7) || // Lo [27] HANGUL SYLLABLE KWAEG..HANGUL SYLLABLE KWAEH\r\n\t\t(0xCFA9 <= code && code <= 0xCFC3) || // Lo [27] HANGUL SYLLABLE KOEG..HANGUL SYLLABLE KOEH\r\n\t\t(0xCFC5 <= code && code <= 0xCFDF) || // Lo [27] HANGUL SYLLABLE KYOG..HANGUL SYLLABLE KYOH\r\n\t\t(0xCFE1 <= code && code <= 0xCFFB) || // Lo [27] HANGUL SYLLABLE KUG..HANGUL SYLLABLE KUH\r\n\t\t(0xCFFD <= code && code <= 0xD017) || // Lo [27] HANGUL SYLLABLE KWEOG..HANGUL SYLLABLE KWEOH\r\n\t\t(0xD019 <= code && code <= 0xD033) || // Lo [27] HANGUL SYLLABLE KWEG..HANGUL SYLLABLE KWEH\r\n\t\t(0xD035 <= code && code <= 0xD04F) || // Lo [27] HANGUL SYLLABLE KWIG..HANGUL SYLLABLE KWIH\r\n\t\t(0xD051 <= code && code <= 0xD06B) || // Lo [27] HANGUL SYLLABLE KYUG..HANGUL SYLLABLE KYUH\r\n\t\t(0xD06D <= code && code <= 0xD087) || // Lo [27] HANGUL SYLLABLE KEUG..HANGUL SYLLABLE KEUH\r\n\t\t(0xD089 <= code && code <= 0xD0A3) || // Lo [27] HANGUL SYLLABLE KYIG..HANGUL SYLLABLE KYIH\r\n\t\t(0xD0A5 <= code && code <= 0xD0BF) || // Lo [27] HANGUL SYLLABLE KIG..HANGUL SYLLABLE KIH\r\n\t\t(0xD0C1 <= code && code <= 0xD0DB) || // Lo [27] HANGUL SYLLABLE TAG..HANGUL SYLLABLE TAH\r\n\t\t(0xD0DD <= code && code <= 0xD0F7) || // Lo [27] HANGUL SYLLABLE TAEG..HANGUL SYLLABLE TAEH\r\n\t\t(0xD0F9 <= code && code <= 0xD113) || // Lo [27] HANGUL SYLLABLE TYAG..HANGUL SYLLABLE TYAH\r\n\t\t(0xD115 <= code && code <= 0xD12F) || // Lo [27] HANGUL SYLLABLE TYAEG..HANGUL SYLLABLE TYAEH\r\n\t\t(0xD131 <= code && code <= 0xD14B) || // Lo [27] HANGUL SYLLABLE TEOG..HANGUL SYLLABLE TEOH\r\n\t\t(0xD14D <= code && code <= 0xD167) || // Lo [27] HANGUL SYLLABLE TEG..HANGUL SYLLABLE TEH\r\n\t\t(0xD169 <= code && code <= 0xD183) || // Lo [27] HANGUL SYLLABLE TYEOG..HANGUL SYLLABLE TYEOH\r\n\t\t(0xD185 <= code && code <= 0xD19F) || // Lo [27] HANGUL SYLLABLE TYEG..HANGUL SYLLABLE TYEH\r\n\t\t(0xD1A1 <= code && code <= 0xD1BB) || // Lo [27] HANGUL SYLLABLE TOG..HANGUL SYLLABLE TOH\r\n\t\t(0xD1BD <= code && code <= 0xD1D7) || // Lo [27] HANGUL SYLLABLE TWAG..HANGUL SYLLABLE TWAH\r\n\t\t(0xD1D9 <= code && code <= 0xD1F3) || // Lo [27] HANGUL SYLLABLE TWAEG..HANGUL SYLLABLE TWAEH\r\n\t\t(0xD1F5 <= code && code <= 0xD20F) || // Lo [27] HANGUL SYLLABLE TOEG..HANGUL SYLLABLE TOEH\r\n\t\t(0xD211 <= code && code <= 0xD22B) || // Lo [27] HANGUL SYLLABLE TYOG..HANGUL SYLLABLE TYOH\r\n\t\t(0xD22D <= code && code <= 0xD247) || // Lo [27] HANGUL SYLLABLE TUG..HANGUL SYLLABLE TUH\r\n\t\t(0xD249 <= code && code <= 0xD263) || // Lo [27] HANGUL SYLLABLE TWEOG..HANGUL SYLLABLE TWEOH\r\n\t\t(0xD265 <= code && code <= 0xD27F) || // Lo [27] HANGUL SYLLABLE TWEG..HANGUL SYLLABLE TWEH\r\n\t\t(0xD281 <= code && code <= 0xD29B) || // Lo [27] HANGUL SYLLABLE TWIG..HANGUL SYLLABLE TWIH\r\n\t\t(0xD29D <= code && code <= 0xD2B7) || // Lo [27] HANGUL SYLLABLE TYUG..HANGUL SYLLABLE TYUH\r\n\t\t(0xD2B9 <= code && code <= 0xD2D3) || // Lo [27] HANGUL SYLLABLE TEUG..HANGUL SYLLABLE TEUH\r\n\t\t(0xD2D5 <= code && code <= 0xD2EF) || // Lo [27] HANGUL SYLLABLE TYIG..HANGUL SYLLABLE TYIH\r\n\t\t(0xD2F1 <= code && code <= 0xD30B) || // Lo [27] HANGUL SYLLABLE TIG..HANGUL SYLLABLE TIH\r\n\t\t(0xD30D <= code && code <= 0xD327) || // Lo [27] HANGUL SYLLABLE PAG..HANGUL SYLLABLE PAH\r\n\t\t(0xD329 <= code && code <= 0xD343) || // Lo [27] HANGUL SYLLABLE PAEG..HANGUL SYLLABLE PAEH\r\n\t\t(0xD345 <= code && code <= 0xD35F) || // Lo [27] HANGUL SYLLABLE PYAG..HANGUL SYLLABLE PYAH\r\n\t\t(0xD361 <= code && code <= 0xD37B) || // Lo [27] HANGUL SYLLABLE PYAEG..HANGUL SYLLABLE PYAEH\r\n\t\t(0xD37D <= code && code <= 0xD397) || // Lo [27] HANGUL SYLLABLE PEOG..HANGUL SYLLABLE PEOH\r\n\t\t(0xD399 <= code && code <= 0xD3B3) || // Lo [27] HANGUL SYLLABLE PEG..HANGUL SYLLABLE PEH\r\n\t\t(0xD3B5 <= code && code <= 0xD3CF) || // Lo [27] HANGUL SYLLABLE PYEOG..HANGUL SYLLABLE PYEOH\r\n\t\t(0xD3D1 <= code && code <= 0xD3EB) || // Lo [27] HANGUL SYLLABLE PYEG..HANGUL SYLLABLE PYEH\r\n\t\t(0xD3ED <= code && code <= 0xD407) || // Lo [27] HANGUL SYLLABLE POG..HANGUL SYLLABLE POH\r\n\t\t(0xD409 <= code && code <= 0xD423) || // Lo [27] HANGUL SYLLABLE PWAG..HANGUL SYLLABLE PWAH\r\n\t\t(0xD425 <= code && code <= 0xD43F) || // Lo [27] HANGUL SYLLABLE PWAEG..HANGUL SYLLABLE PWAEH\r\n\t\t(0xD441 <= code && code <= 0xD45B) || // Lo [27] HANGUL SYLLABLE POEG..HANGUL SYLLABLE POEH\r\n\t\t(0xD45D <= code && code <= 0xD477) || // Lo [27] HANGUL SYLLABLE PYOG..HANGUL SYLLABLE PYOH\r\n\t\t(0xD479 <= code && code <= 0xD493) || // Lo [27] HANGUL SYLLABLE PUG..HANGUL SYLLABLE PUH\r\n\t\t(0xD495 <= code && code <= 0xD4AF) || // Lo [27] HANGUL SYLLABLE PWEOG..HANGUL SYLLABLE PWEOH\r\n\t\t(0xD4B1 <= code && code <= 0xD4CB) || // Lo [27] HANGUL SYLLABLE PWEG..HANGUL SYLLABLE PWEH\r\n\t\t(0xD4CD <= code && code <= 0xD4E7) || // Lo [27] HANGUL SYLLABLE PWIG..HANGUL SYLLABLE PWIH\r\n\t\t(0xD4E9 <= code && code <= 0xD503) || // Lo [27] HANGUL SYLLABLE PYUG..HANGUL SYLLABLE PYUH\r\n\t\t(0xD505 <= code && code <= 0xD51F) || // Lo [27] HANGUL SYLLABLE PEUG..HANGUL SYLLABLE PEUH\r\n\t\t(0xD521 <= code && code <= 0xD53B) || // Lo [27] HANGUL SYLLABLE PYIG..HANGUL SYLLABLE PYIH\r\n\t\t(0xD53D <= code && code <= 0xD557) || // Lo [27] HANGUL SYLLABLE PIG..HANGUL SYLLABLE PIH\r\n\t\t(0xD559 <= code && code <= 0xD573) || // Lo [27] HANGUL SYLLABLE HAG..HANGUL SYLLABLE HAH\r\n\t\t(0xD575 <= code && code <= 0xD58F) || // Lo [27] HANGUL SYLLABLE HAEG..HANGUL SYLLABLE HAEH\r\n\t\t(0xD591 <= code && code <= 0xD5AB) || // Lo [27] HANGUL SYLLABLE HYAG..HANGUL SYLLABLE HYAH\r\n\t\t(0xD5AD <= code && code <= 0xD5C7) || // Lo [27] HANGUL SYLLABLE HYAEG..HANGUL SYLLABLE HYAEH\r\n\t\t(0xD5C9 <= code && code <= 0xD5E3) || // Lo [27] HANGUL SYLLABLE HEOG..HANGUL SYLLABLE HEOH\r\n\t\t(0xD5E5 <= code && code <= 0xD5FF) || // Lo [27] HANGUL SYLLABLE HEG..HANGUL SYLLABLE HEH\r\n\t\t(0xD601 <= code && code <= 0xD61B) || // Lo [27] HANGUL SYLLABLE HYEOG..HANGUL SYLLABLE HYEOH\r\n\t\t(0xD61D <= code && code <= 0xD637) || // Lo [27] HANGUL SYLLABLE HYEG..HANGUL SYLLABLE HYEH\r\n\t\t(0xD639 <= code && code <= 0xD653) || // Lo [27] HANGUL SYLLABLE HOG..HANGUL SYLLABLE HOH\r\n\t\t(0xD655 <= code && code <= 0xD66F) || // Lo [27] HANGUL SYLLABLE HWAG..HANGUL SYLLABLE HWAH\r\n\t\t(0xD671 <= code && code <= 0xD68B) || // Lo [27] HANGUL SYLLABLE HWAEG..HANGUL SYLLABLE HWAEH\r\n\t\t(0xD68D <= code && code <= 0xD6A7) || // Lo [27] HANGUL SYLLABLE HOEG..HANGUL SYLLABLE HOEH\r\n\t\t(0xD6A9 <= code && code <= 0xD6C3) || // Lo [27] HANGUL SYLLABLE HYOG..HANGUL SYLLABLE HYOH\r\n\t\t(0xD6C5 <= code && code <= 0xD6DF) || // Lo [27] HANGUL SYLLABLE HUG..HANGUL SYLLABLE HUH\r\n\t\t(0xD6E1 <= code && code <= 0xD6FB) || // Lo [27] HANGUL SYLLABLE HWEOG..HANGUL SYLLABLE HWEOH\r\n\t\t(0xD6FD <= code && code <= 0xD717) || // Lo [27] HANGUL SYLLABLE HWEG..HANGUL SYLLABLE HWEH\r\n\t\t(0xD719 <= code && code <= 0xD733) || // Lo [27] HANGUL SYLLABLE HWIG..HANGUL SYLLABLE HWIH\r\n\t\t(0xD735 <= code && code <= 0xD74F) || // Lo [27] HANGUL SYLLABLE HYUG..HANGUL SYLLABLE HYUH\r\n\t\t(0xD751 <= code && code <= 0xD76B) || // Lo [27] HANGUL SYLLABLE HEUG..HANGUL SYLLABLE HEUH\r\n\t\t(0xD76D <= code && code <= 0xD787) || // Lo [27] HANGUL SYLLABLE HYIG..HANGUL SYLLABLE HYIH\r\n\t\t(0xD789 <= code && code <= 0xD7A3) // Lo [27] HANGUL SYLLABLE HIG..HANGUL SYLLABLE HIH\r\n\t\t){\r\n\t\t\treturn LVT;\r\n\t\t}\r\n\t\t\r\n\t\tif(\r\n\t\t0x261D == code || // So WHITE UP POINTING INDEX\r\n\t\t0x26F9 == code || // So PERSON WITH BALL\r\n\t\t(0x270A <= code && code <= 0x270D) || // So [4] RAISED FIST..WRITING HAND\r\n\t\t0x1F385 == code || // So FATHER CHRISTMAS\r\n\t\t(0x1F3C2 <= code && code <= 0x1F3C4) || // So [3] SNOWBOARDER..SURFER\r\n\t\t0x1F3C7 == code || // So HORSE RACING\r\n\t\t(0x1F3CA <= code && code <= 0x1F3CC) || // So [3] SWIMMER..GOLFER\r\n\t\t(0x1F442 <= code && code <= 0x1F443) || // So [2] EAR..NOSE\r\n\t\t(0x1F446 <= code && code <= 0x1F450) || // So [11] WHITE UP POINTING BACKHAND INDEX..OPEN HANDS SIGN\r\n\t\t0x1F46E == code || // So POLICE OFFICER\r\n\t\t(0x1F470 <= code && code <= 0x1F478) || // So [9] BRIDE WITH VEIL..PRINCESS\r\n\t\t0x1F47C == code || // So BABY ANGEL\r\n\t\t(0x1F481 <= code && code <= 0x1F483) || // So [3] INFORMATION DESK PERSON..DANCER\r\n\t\t(0x1F485 <= code && code <= 0x1F487) || // So [3] NAIL POLISH..HAIRCUT\r\n\t\t0x1F4AA == code || // So FLEXED BICEPS\r\n\t\t(0x1F574 <= code && code <= 0x1F575) || // So [2] MAN IN BUSINESS SUIT LEVITATING..SLEUTH OR SPY\r\n\t\t0x1F57A == code || // So MAN DANCING\r\n\t\t0x1F590 == code || // So RAISED HAND WITH FINGERS SPLAYED\r\n\t\t(0x1F595 <= code && code <= 0x1F596) || // So [2] REVERSED HAND WITH MIDDLE FINGER EXTENDED..RAISED HAND WITH PART BETWEEN MIDDLE AND RING FINGERS\r\n\t\t(0x1F645 <= code && code <= 0x1F647) || // So [3] FACE WITH NO GOOD GESTURE..PERSON BOWING DEEPLY\r\n\t\t(0x1F64B <= code && code <= 0x1F64F) || // So [5] HAPPY PERSON RAISING ONE HAND..PERSON WITH FOLDED HANDS\r\n\t\t0x1F6A3 == code || // So ROWBOAT\r\n\t\t(0x1F6B4 <= code && code <= 0x1F6B6) || // So [3] BICYCLIST..PEDESTRIAN\r\n\t\t0x1F6C0 == code || // So BATH\r\n\t\t0x1F6CC == code || // So SLEEPING ACCOMMODATION\r\n\t\t(0x1F918 <= code && code <= 0x1F91C) || // So [5] SIGN OF THE HORNS..RIGHT-FACING FIST\r\n\t\t(0x1F91E <= code && code <= 0x1F91F) || // So [2] HAND WITH INDEX AND MIDDLE FINGERS CROSSED..I LOVE YOU HAND SIGN\r\n\t\t0x1F926 == code || // So FACE PALM\r\n\t\t(0x1F930 <= code && code <= 0x1F939) || // So [10] PREGNANT WOMAN..JUGGLING\r\n\t\t(0x1F93D <= code && code <= 0x1F93E) || // So [2] WATER POLO..HANDBALL\r\n\t\t(0x1F9D1 <= code && code <= 0x1F9DD) // So [13] ADULT..ELF\r\n\t\t){\r\n\t\t\treturn E_Base;\r\n\t\t}\r\n\r\n\t\tif(\r\n\t\t(0x1F3FB <= code && code <= 0x1F3FF) // Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6\r\n\t\t){\r\n\t\t\treturn E_Modifier;\r\n\t\t}\r\n\r\n\t\tif(\r\n\t\t0x200D == code // Cf ZERO WIDTH JOINER\r\n\t\t){\r\n\t\t\treturn ZWJ;\r\n\t\t}\r\n\r\n\t\tif(\r\n\t\t0x2640 == code || // So FEMALE SIGN\r\n\t\t0x2642 == code || // So MALE SIGN\r\n\t\t(0x2695 <= code && code <= 0x2696) || // So [2] STAFF OF AESCULAPIUS..SCALES\r\n\t\t0x2708 == code || // So AIRPLANE\r\n\t\t0x2764 == code || // So HEAVY BLACK HEART\r\n\t\t0x1F308 == code || // So RAINBOW\r\n\t\t0x1F33E == code || // So EAR OF RICE\r\n\t\t0x1F373 == code || // So COOKING\r\n\t\t0x1F393 == code || // So GRADUATION CAP\r\n\t\t0x1F3A4 == code || // So MICROPHONE\r\n\t\t0x1F3A8 == code || // So ARTIST PALETTE\r\n\t\t0x1F3EB == code || // So SCHOOL\r\n\t\t0x1F3ED == code || // So FACTORY\r\n\t\t0x1F48B == code || // So KISS MARK\r\n\t\t(0x1F4BB <= code && code <= 0x1F4BC) || // So [2] PERSONAL COMPUTER..BRIEFCASE\r\n\t\t0x1F527 == code || // So WRENCH\r\n\t\t0x1F52C == code || // So MICROSCOPE\r\n\t\t0x1F5E8 == code || // So LEFT SPEECH BUBBLE\r\n\t\t0x1F680 == code || // So ROCKET\r\n\t\t0x1F692 == code // So FIRE ENGINE\r\n\t\t){\r\n\t\t\treturn Glue_After_Zwj;\r\n\t\t}\r\n\r\n\t\tif(\r\n\t\t(0x1F466 <= code && code <= 0x1F469) // So [4] BOY..WOMAN\r\n\t\t){\r\n\t\t\treturn E_Base_GAZ;\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\t//all unlisted characters have a grapheme break property of \"Other\"\r\n\t\treturn Other;\r\n\t}\r\n\treturn this;\r\n}\r\n\r\nif (typeof module != 'undefined' && module.exports) {\r\n module.exports = GraphemeSplitter;\r\n}\r\n", "/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\n", "/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nvar ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');\n\nfunction emptyFunction() {}\nfunction emptyFunctionWithReset() {}\nemptyFunctionWithReset.resetWarningCache = emptyFunction;\n\nmodule.exports = function() {\n function shim(props, propName, componentName, location, propFullName, secret) {\n if (secret === ReactPropTypesSecret) {\n // It is still safe when called from React.\n return;\n }\n var err = new Error(\n 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +\n 'Use PropTypes.checkPropTypes() to call them. ' +\n 'Read more at http://fb.me/use-check-prop-types'\n );\n err.name = 'Invariant Violation';\n throw err;\n };\n shim.isRequired = shim;\n function getShim() {\n return shim;\n };\n // Important!\n // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`.\n var ReactPropTypes = {\n array: shim,\n bool: shim,\n func: shim,\n number: shim,\n object: shim,\n string: shim,\n symbol: shim,\n\n any: shim,\n arrayOf: getShim,\n element: shim,\n elementType: shim,\n instanceOf: getShim,\n node: shim,\n objectOf: getShim,\n oneOf: getShim,\n oneOfType: getShim,\n shape: getShim,\n exact: getShim,\n\n checkPropTypes: emptyFunctionWithReset,\n resetWarningCache: emptyFunction\n };\n\n ReactPropTypes.PropTypes = ReactPropTypes;\n\n return ReactPropTypes;\n};\n", "/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nif (process.env.NODE_ENV !== 'production') {\n var ReactIs = require('react-is');\n\n // By explicitly using `prop-types` you are opting into new development behavior.\n // http://fb.me/prop-types-in-prod\n var throwOnDirectAccess = true;\n module.exports = require('./factoryWithTypeCheckers')(ReactIs.isElement, throwOnDirectAccess);\n} else {\n // By explicitly using `prop-types` you are opting into new production behavior.\n // http://fb.me/prop-types-in-prod\n module.exports = require('./factoryWithThrowingShims')();\n}\n", "'use strict';\n\n// do not edit .js files directly - edit src/index.jst\n\n\n\nmodule.exports = function equal(a, b) {\n if (a === b) return true;\n\n if (a && b && typeof a == 'object' && typeof b == 'object') {\n if (a.constructor !== b.constructor) return false;\n\n var length, i, keys;\n if (Array.isArray(a)) {\n length = a.length;\n if (length != b.length) return false;\n for (i = length; i-- !== 0;)\n if (!equal(a[i], b[i])) return false;\n return true;\n }\n\n\n\n if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;\n if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();\n if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();\n\n keys = Object.keys(a);\n length = keys.length;\n if (length !== Object.keys(b).length) return false;\n\n for (i = length; i-- !== 0;)\n if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;\n\n for (i = length; i-- !== 0;) {\n var key = keys[i];\n\n if (!equal(a[key], b[key])) return false;\n }\n\n return true;\n }\n\n // true if both NaN, false otherwise\n return a!==a && b!==b;\n};\n", "'use strict';\n\nvar Sister;\n\n/**\n* @link https://github.com/gajus/sister for the canonical source repository\n* @license https://github.com/gajus/sister/blob/master/LICENSE BSD 3-Clause\n*/\nSister = function () {\n var sister = {},\n events = {};\n\n /**\n * @name handler\n * @function\n * @param {Object} data Event data.\n */\n\n /**\n * @param {String} name Event name.\n * @param {handler} handler\n * @return {listener}\n */\n sister.on = function (name, handler) {\n var listener = {name: name, handler: handler};\n events[name] = events[name] || [];\n events[name].unshift(listener);\n return listener;\n };\n\n /**\n * @param {listener}\n */\n sister.off = function (listener) {\n var index = events[listener.name].indexOf(listener);\n\n if (index !== -1) {\n events[listener.name].splice(index, 1);\n }\n };\n\n /**\n * @param {String} name Event name.\n * @param {Object} data Event data.\n */\n sister.trigger = function (name, data) {\n var listeners = events[name],\n i;\n\n if (listeners) {\n i = listeners.length;\n while (i--) {\n listeners[i].handler(data);\n }\n }\n };\n\n return sister;\n};\n\nmodule.exports = Sister;\n", "\nmodule.exports = function load (src, opts, cb) {\n var head = document.head || document.getElementsByTagName('head')[0]\n var script = document.createElement('script')\n\n if (typeof opts === 'function') {\n cb = opts\n opts = {}\n }\n\n opts = opts || {}\n cb = cb || function() {}\n\n script.type = opts.type || 'text/javascript'\n script.charset = opts.charset || 'utf8';\n script.async = 'async' in opts ? !!opts.async : true\n script.src = src\n\n if (opts.attrs) {\n setAttributes(script, opts.attrs)\n }\n\n if (opts.text) {\n script.text = '' + opts.text\n }\n\n var onend = 'onload' in script ? stdOnEnd : ieOnEnd\n onend(script, cb)\n\n // some good legacy browsers (firefox) fail the 'in' detection above\n // so as a fallback we always set onload\n // old IE will ignore this and new IE will set onload\n if (!script.onload) {\n stdOnEnd(script, cb);\n }\n\n head.appendChild(script)\n}\n\nfunction setAttributes(script, attrs) {\n for (var attr in attrs) {\n script.setAttribute(attr, attrs[attr]);\n }\n}\n\nfunction stdOnEnd (script, cb) {\n script.onload = function () {\n this.onerror = this.onload = null\n cb(null, script)\n }\n script.onerror = function () {\n // this.onload = null here is necessary\n // because even IE9 works not like others\n this.onerror = this.onload = null\n cb(new Error('Failed to load ' + this.src), script)\n }\n}\n\nfunction ieOnEnd (script, cb) {\n script.onreadystatechange = function () {\n if (this.readyState != 'complete' && this.readyState != 'loaded') return\n this.onreadystatechange = null\n cb(null, script) // there is no way to catch loading errors in IE8\n }\n}\n", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _loadScript = require('load-script');\n\nvar _loadScript2 = _interopRequireDefault(_loadScript);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nexports.default = function (emitter) {\n /**\n * A promise that is resolved when window.onYouTubeIframeAPIReady is called.\n * The promise is resolved with a reference to window.YT object.\n */\n var iframeAPIReady = new Promise(function (resolve) {\n if (window.YT && window.YT.Player && window.YT.Player instanceof Function) {\n resolve(window.YT);\n\n return;\n } else {\n var protocol = window.location.protocol === 'http:' ? 'http:' : 'https:';\n\n (0, _loadScript2.default)(protocol + '//www.youtube.com/iframe_api', function (error) {\n if (error) {\n emitter.trigger('error', error);\n }\n });\n }\n\n var previous = window.onYouTubeIframeAPIReady;\n\n // The API will call this function when page has finished downloading\n // the JavaScript for the player API.\n window.onYouTubeIframeAPIReady = function () {\n if (previous) {\n previous();\n }\n\n resolve(window.YT);\n };\n });\n\n return iframeAPIReady;\n};\n\nmodule.exports = exports['default'];", "/**\n * Helpers.\n */\n\nvar s = 1000;\nvar m = s * 60;\nvar h = m * 60;\nvar d = h * 24;\nvar y = d * 365.25;\n\n/**\n * Parse or format the given `val`.\n *\n * Options:\n *\n * - `long` verbose formatting [false]\n *\n * @param {String|Number} val\n * @param {Object} [options]\n * @throws {Error} throw an error if val is not a non-empty string or a number\n * @return {String|Number}\n * @api public\n */\n\nmodule.exports = function(val, options) {\n options = options || {};\n var type = typeof val;\n if (type === 'string' && val.length > 0) {\n return parse(val);\n } else if (type === 'number' && isNaN(val) === false) {\n return options.long ? fmtLong(val) : fmtShort(val);\n }\n throw new Error(\n 'val is not a non-empty string or a valid number. val=' +\n JSON.stringify(val)\n );\n};\n\n/**\n * Parse the given `str` and return milliseconds.\n *\n * @param {String} str\n * @return {Number}\n * @api private\n */\n\nfunction parse(str) {\n str = String(str);\n if (str.length > 100) {\n return;\n }\n var match = /^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(\n str\n );\n if (!match) {\n return;\n }\n var n = parseFloat(match[1]);\n var type = (match[2] || 'ms').toLowerCase();\n switch (type) {\n case 'years':\n case 'year':\n case 'yrs':\n case 'yr':\n case 'y':\n return n * y;\n case 'days':\n case 'day':\n case 'd':\n return n * d;\n case 'hours':\n case 'hour':\n case 'hrs':\n case 'hr':\n case 'h':\n return n * h;\n case 'minutes':\n case 'minute':\n case 'mins':\n case 'min':\n case 'm':\n return n * m;\n case 'seconds':\n case 'second':\n case 'secs':\n case 'sec':\n case 's':\n return n * s;\n case 'milliseconds':\n case 'millisecond':\n case 'msecs':\n case 'msec':\n case 'ms':\n return n;\n default:\n return undefined;\n }\n}\n\n/**\n * Short format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n */\n\nfunction fmtShort(ms) {\n if (ms >= d) {\n return Math.round(ms / d) + 'd';\n }\n if (ms >= h) {\n return Math.round(ms / h) + 'h';\n }\n if (ms >= m) {\n return Math.round(ms / m) + 'm';\n }\n if (ms >= s) {\n return Math.round(ms / s) + 's';\n }\n return ms + 'ms';\n}\n\n/**\n * Long format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n */\n\nfunction fmtLong(ms) {\n return plural(ms, d, 'day') ||\n plural(ms, h, 'hour') ||\n plural(ms, m, 'minute') ||\n plural(ms, s, 'second') ||\n ms + ' ms';\n}\n\n/**\n * Pluralization helper.\n */\n\nfunction plural(ms, n, name) {\n if (ms < n) {\n return;\n }\n if (ms < n * 1.5) {\n return Math.floor(ms / n) + ' ' + name;\n }\n return Math.ceil(ms / n) + ' ' + name + 's';\n}\n", "\n/**\n * This is the common logic for both the Node.js and web browser\n * implementations of `debug()`.\n *\n * Expose `debug()` as the module.\n */\n\nexports = module.exports = createDebug.debug = createDebug['default'] = createDebug;\nexports.coerce = coerce;\nexports.disable = disable;\nexports.enable = enable;\nexports.enabled = enabled;\nexports.humanize = require('ms');\n\n/**\n * The currently active debug mode names, and names to skip.\n */\n\nexports.names = [];\nexports.skips = [];\n\n/**\n * Map of special \"%n\" handling functions, for the debug \"format\" argument.\n *\n * Valid key names are a single, lower or upper-case letter, i.e. \"n\" and \"N\".\n */\n\nexports.formatters = {};\n\n/**\n * Previous log timestamp.\n */\n\nvar prevTime;\n\n/**\n * Select a color.\n * @param {String} namespace\n * @return {Number}\n * @api private\n */\n\nfunction selectColor(namespace) {\n var hash = 0, i;\n\n for (i in namespace) {\n hash = ((hash << 5) - hash) + namespace.charCodeAt(i);\n hash |= 0; // Convert to 32bit integer\n }\n\n return exports.colors[Math.abs(hash) % exports.colors.length];\n}\n\n/**\n * Create a debugger with the given `namespace`.\n *\n * @param {String} namespace\n * @return {Function}\n * @api public\n */\n\nfunction createDebug(namespace) {\n\n function debug() {\n // disabled?\n if (!debug.enabled) return;\n\n var self = debug;\n\n // set `diff` timestamp\n var curr = +new Date();\n var ms = curr - (prevTime || curr);\n self.diff = ms;\n self.prev = prevTime;\n self.curr = curr;\n prevTime = curr;\n\n // turn the `arguments` into a proper Array\n var args = new Array(arguments.length);\n for (var i = 0; i < args.length; i++) {\n args[i] = arguments[i];\n }\n\n args[0] = exports.coerce(args[0]);\n\n if ('string' !== typeof args[0]) {\n // anything else let's inspect with %O\n args.unshift('%O');\n }\n\n // apply any `formatters` transformations\n var index = 0;\n args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {\n // if we encounter an escaped % then don't increase the array index\n if (match === '%%') return match;\n index++;\n var formatter = exports.formatters[format];\n if ('function' === typeof formatter) {\n var val = args[index];\n match = formatter.call(self, val);\n\n // now we need to remove `args[index]` since it's inlined in the `format`\n args.splice(index, 1);\n index--;\n }\n return match;\n });\n\n // apply env-specific formatting (colors, etc.)\n exports.formatArgs.call(self, args);\n\n var logFn = debug.log || exports.log || console.log.bind(console);\n logFn.apply(self, args);\n }\n\n debug.namespace = namespace;\n debug.enabled = exports.enabled(namespace);\n debug.useColors = exports.useColors();\n debug.color = selectColor(namespace);\n\n // env-specific initialization logic for debug instances\n if ('function' === typeof exports.init) {\n exports.init(debug);\n }\n\n return debug;\n}\n\n/**\n * Enables a debug mode by namespaces. This can include modes\n * separated by a colon and wildcards.\n *\n * @param {String} namespaces\n * @api public\n */\n\nfunction enable(namespaces) {\n exports.save(namespaces);\n\n exports.names = [];\n exports.skips = [];\n\n var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\\s,]+/);\n var len = split.length;\n\n for (var i = 0; i < len; i++) {\n if (!split[i]) continue; // ignore empty strings\n namespaces = split[i].replace(/\\*/g, '.*?');\n if (namespaces[0] === '-') {\n exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));\n } else {\n exports.names.push(new RegExp('^' + namespaces + '$'));\n }\n }\n}\n\n/**\n * Disable debug output.\n *\n * @api public\n */\n\nfunction disable() {\n exports.enable('');\n}\n\n/**\n * Returns true if the given mode name is enabled, false otherwise.\n *\n * @param {String} name\n * @return {Boolean}\n * @api public\n */\n\nfunction enabled(name) {\n var i, len;\n for (i = 0, len = exports.skips.length; i < len; i++) {\n if (exports.skips[i].test(name)) {\n return false;\n }\n }\n for (i = 0, len = exports.names.length; i < len; i++) {\n if (exports.names[i].test(name)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Coerce `val`.\n *\n * @param {Mixed} val\n * @return {Mixed}\n * @api private\n */\n\nfunction coerce(val) {\n if (val instanceof Error) return val.stack || val.message;\n return val;\n}\n", "/**\n * This is the web browser implementation of `debug()`.\n *\n * Expose `debug()` as the module.\n */\n\nexports = module.exports = require('./debug');\nexports.log = log;\nexports.formatArgs = formatArgs;\nexports.save = save;\nexports.load = load;\nexports.useColors = useColors;\nexports.storage = 'undefined' != typeof chrome\n && 'undefined' != typeof chrome.storage\n ? chrome.storage.local\n : localstorage();\n\n/**\n * Colors.\n */\n\nexports.colors = [\n 'lightseagreen',\n 'forestgreen',\n 'goldenrod',\n 'dodgerblue',\n 'darkorchid',\n 'crimson'\n];\n\n/**\n * Currently only WebKit-based Web Inspectors, Firefox >= v31,\n * and the Firebug extension (any Firefox version) are known\n * to support \"%c\" CSS customizations.\n *\n * TODO: add a `localStorage` variable to explicitly enable/disable colors\n */\n\nfunction useColors() {\n // NB: In an Electron preload script, document will be defined but not fully\n // initialized. Since we know we're in Chrome, we'll just detect this case\n // explicitly\n if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {\n return true;\n }\n\n // is webkit? http://stackoverflow.com/a/16459606/376773\n // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632\n return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||\n // is firebug? http://stackoverflow.com/a/398120/376773\n (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||\n // is firefox >= v31?\n // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\\/(\\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||\n // double check webkit in userAgent just in case we are in a worker\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\\/(\\d+)/));\n}\n\n/**\n * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.\n */\n\nexports.formatters.j = function(v) {\n try {\n return JSON.stringify(v);\n } catch (err) {\n return '[UnexpectedJSONParseError]: ' + err.message;\n }\n};\n\n\n/**\n * Colorize log arguments if enabled.\n *\n * @api public\n */\n\nfunction formatArgs(args) {\n var useColors = this.useColors;\n\n args[0] = (useColors ? '%c' : '')\n + this.namespace\n + (useColors ? ' %c' : ' ')\n + args[0]\n + (useColors ? '%c ' : ' ')\n + '+' + exports.humanize(this.diff);\n\n if (!useColors) return;\n\n var c = 'color: ' + this.color;\n args.splice(1, 0, c, 'color: inherit')\n\n // the final \"%c\" is somewhat tricky, because there could be other\n // arguments passed either before or after the %c, so we need to\n // figure out the correct index to insert the CSS into\n var index = 0;\n var lastC = 0;\n args[0].replace(/%[a-zA-Z%]/g, function(match) {\n if ('%%' === match) return;\n index++;\n if ('%c' === match) {\n // we only are interested in the *last* %c\n // (the user may have provided their own)\n lastC = index;\n }\n });\n\n args.splice(lastC, 0, c);\n}\n\n/**\n * Invokes `console.log()` when available.\n * No-op when `console.log` is not a \"function\".\n *\n * @api public\n */\n\nfunction log() {\n // this hackery is required for IE8/9, where\n // the `console.log` function doesn't have 'apply'\n return 'object' === typeof console\n && console.log\n && Function.prototype.apply.call(console.log, console, arguments);\n}\n\n/**\n * Save `namespaces`.\n *\n * @param {String} namespaces\n * @api private\n */\n\nfunction save(namespaces) {\n try {\n if (null == namespaces) {\n exports.storage.removeItem('debug');\n } else {\n exports.storage.debug = namespaces;\n }\n } catch(e) {}\n}\n\n/**\n * Load `namespaces`.\n *\n * @return {String} returns the previously persisted debug modes\n * @api private\n */\n\nfunction load() {\n var r;\n try {\n r = exports.storage.debug;\n } catch(e) {}\n\n // If debug isn't set in LS, and we're in Electron, try to load $DEBUG\n if (!r && typeof process !== 'undefined' && 'env' in process) {\n r = process.env.DEBUG;\n }\n\n return r;\n}\n\n/**\n * Enable namespaces listed in `localStorage.debug` initially.\n */\n\nexports.enable(load());\n\n/**\n * Localstorage attempts to return the localstorage.\n *\n * This is necessary because safari throws\n * when a user disables cookies/localstorage\n * and you attempt to access it.\n *\n * @return {LocalStorage}\n * @api private\n */\n\nfunction localstorage() {\n try {\n return window.localStorage;\n } catch (e) {}\n}\n", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\n\n/**\n * @see https://developers.google.com/youtube/iframe_api_reference#Functions\n */\nexports.default = ['cueVideoById', 'loadVideoById', 'cueVideoByUrl', 'loadVideoByUrl', 'playVideo', 'pauseVideo', 'stopVideo', 'getVideoLoadedFraction', 'cuePlaylist', 'loadPlaylist', 'nextVideo', 'previousVideo', 'playVideoAt', 'setShuffle', 'setLoop', 'getPlaylist', 'getPlaylistIndex', 'setOption', 'mute', 'unMute', 'isMuted', 'setVolume', 'getVolume', 'seekTo', 'getPlayerState', 'getPlaybackRate', 'setPlaybackRate', 'getAvailablePlaybackRates', 'getPlaybackQuality', 'setPlaybackQuality', 'getAvailableQualityLevels', 'getCurrentTime', 'getDuration', 'removeEventListener', 'getVideoUrl', 'getVideoEmbedCode', 'getOptions', 'getOption', 'addEventListener', 'destroy', 'setSize', 'getIframe'];\nmodule.exports = exports['default'];", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\n\n/**\n * @see https://developers.google.com/youtube/iframe_api_reference#Events\n * `volumeChange` is not officially supported but seems to work\n * it emits an object: `{volume: 82.6923076923077, muted: false}`\n */\nexports.default = ['ready', 'stateChange', 'playbackQualityChange', 'playbackRateChange', 'error', 'apiChange', 'volumeChange'];\nmodule.exports = exports['default'];", "\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = {\n BUFFERING: 3,\n ENDED: 0,\n PAUSED: 2,\n PLAYING: 1,\n UNSTARTED: -1,\n VIDEO_CUED: 5\n};\nmodule.exports = exports[\"default\"];", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _PlayerStates = require('./constants/PlayerStates');\n\nvar _PlayerStates2 = _interopRequireDefault(_PlayerStates);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nexports.default = {\n pauseVideo: {\n acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PAUSED],\n stateChangeRequired: false\n },\n playVideo: {\n acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PLAYING],\n stateChangeRequired: false\n },\n seekTo: {\n acceptableStates: [_PlayerStates2.default.ENDED, _PlayerStates2.default.PLAYING, _PlayerStates2.default.PAUSED],\n stateChangeRequired: true,\n\n // TRICKY: `seekTo` may not cause a state change if no buffering is\n // required.\n timeout: 3000\n }\n};\nmodule.exports = exports['default'];", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _debug = require('debug');\n\nvar _debug2 = _interopRequireDefault(_debug);\n\nvar _functionNames = require('./functionNames');\n\nvar _functionNames2 = _interopRequireDefault(_functionNames);\n\nvar _eventNames = require('./eventNames');\n\nvar _eventNames2 = _interopRequireDefault(_eventNames);\n\nvar _FunctionStateMap = require('./FunctionStateMap');\n\nvar _FunctionStateMap2 = _interopRequireDefault(_FunctionStateMap);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n/* eslint-disable promise/prefer-await-to-then */\n\nvar debug = (0, _debug2.default)('youtube-player');\n\nvar YouTubePlayer = {};\n\n/**\n * Construct an object that defines an event handler for all of the YouTube\n * player events. Proxy captured events through an event emitter.\n *\n * @todo Capture event parameters.\n * @see https://developers.google.com/youtube/iframe_api_reference#Events\n */\nYouTubePlayer.proxyEvents = function (emitter) {\n var events = {};\n\n var _loop = function _loop(eventName) {\n var onEventName = 'on' + eventName.slice(0, 1).toUpperCase() + eventName.slice(1);\n\n events[onEventName] = function (event) {\n debug('event \"%s\"', onEventName, event);\n\n emitter.trigger(eventName, event);\n };\n };\n\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = _eventNames2.default[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n var eventName = _step.value;\n\n _loop(eventName);\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n return events;\n};\n\n/**\n * Delays player API method execution until player state is ready.\n *\n * @todo Proxy all of the methods using Object.keys.\n * @todo See TRICKY below.\n * @param playerAPIReady Promise that resolves when player is ready.\n * @param strictState A flag designating whether or not to wait for\n * an acceptable state when calling supported functions.\n * @returns {Object}\n */\nYouTubePlayer.promisifyPlayer = function (playerAPIReady) {\n var strictState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n\n var functions = {};\n\n var _loop2 = function _loop2(functionName) {\n if (strictState && _FunctionStateMap2.default[functionName]) {\n functions[functionName] = function () {\n for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {\n args[_key] = arguments[_key];\n }\n\n return playerAPIReady.then(function (player) {\n var stateInfo = _FunctionStateMap2.default[functionName];\n var playerState = player.getPlayerState();\n\n // eslint-disable-next-line no-warning-comments\n // TODO: Just spread the args into the function once Babel is fixed:\n // https://github.com/babel/babel/issues/4270\n //\n // eslint-disable-next-line prefer-spread\n var value = player[functionName].apply(player, args);\n\n // TRICKY: For functions like `seekTo`, a change in state must be\n // triggered given that the resulting state could match the initial\n // state.\n if (stateInfo.stateChangeRequired ||\n\n // eslint-disable-next-line no-extra-parens\n Array.isArray(stateInfo.acceptableStates) && stateInfo.acceptableStates.indexOf(playerState) === -1) {\n return new Promise(function (resolve) {\n var onPlayerStateChange = function onPlayerStateChange() {\n var playerStateAfterChange = player.getPlayerState();\n\n var timeout = void 0;\n\n if (typeof stateInfo.timeout === 'number') {\n timeout = setTimeout(function () {\n player.removeEventListener('onStateChange', onPlayerStateChange);\n\n resolve();\n }, stateInfo.timeout);\n }\n\n if (Array.isArray(stateInfo.acceptableStates) && stateInfo.acceptableStates.indexOf(playerStateAfterChange) !== -1) {\n player.removeEventListener('onStateChange', onPlayerStateChange);\n\n clearTimeout(timeout);\n\n resolve();\n }\n };\n\n player.addEventListener('onStateChange', onPlayerStateChange);\n }).then(function () {\n return value;\n });\n }\n\n return value;\n });\n };\n } else {\n functions[functionName] = function () {\n for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n args[_key2] = arguments[_key2];\n }\n\n return playerAPIReady.then(function (player) {\n // eslint-disable-next-line no-warning-comments\n // TODO: Just spread the args into the function once Babel is fixed:\n // https://github.com/babel/babel/issues/4270\n //\n // eslint-disable-next-line prefer-spread\n return player[functionName].apply(player, args);\n });\n };\n }\n };\n\n var _iteratorNormalCompletion2 = true;\n var _didIteratorError2 = false;\n var _iteratorError2 = undefined;\n\n try {\n for (var _iterator2 = _functionNames2.default[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {\n var functionName = _step2.value;\n\n _loop2(functionName);\n }\n } catch (err) {\n _didIteratorError2 = true;\n _iteratorError2 = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion2 && _iterator2.return) {\n _iterator2.return();\n }\n } finally {\n if (_didIteratorError2) {\n throw _iteratorError2;\n }\n }\n }\n\n return functions;\n};\n\nexports.default = YouTubePlayer;\nmodule.exports = exports['default'];", "'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _typeof = typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; };\n\nvar _sister = require('sister');\n\nvar _sister2 = _interopRequireDefault(_sister);\n\nvar _loadYouTubeIframeApi = require('./loadYouTubeIframeApi');\n\nvar _loadYouTubeIframeApi2 = _interopRequireDefault(_loadYouTubeIframeApi);\n\nvar _YouTubePlayer = require('./YouTubePlayer');\n\nvar _YouTubePlayer2 = _interopRequireDefault(_YouTubePlayer);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n/**\n * @typedef YT.Player\n * @see https://developers.google.com/youtube/iframe_api_reference\n * */\n\n/**\n * @see https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player\n */\nvar youtubeIframeAPI = void 0;\n\n/**\n * A factory function used to produce an instance of YT.Player and queue function calls and proxy events of the resulting object.\n *\n * @param maybeElementId Either An existing YT.Player instance,\n * the DOM element or the id of the HTML element where the API will insert an