pixel-react 1.15.11 → 1.15.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -75
- package/lib/ThirdPartyPackages/JanusGateway.js +5 -2
- package/lib/ThirdPartyPackages/JanusGateway.js.map +1 -1
- package/lib/_virtual/index10.js +2 -2
- package/lib/_virtual/index11.js +2 -2
- package/lib/_virtual/index12.js +2 -2
- package/lib/_virtual/index9.js +2 -2
- package/lib/assets/icons/spinner.svg.js +1 -1
- package/lib/assets/icons/spinner.svg.js.map +1 -1
- package/lib/components/Editor/VariableDropdown.js +2 -2
- package/lib/index.cjs +7209 -4574
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +1 -1
- package/lib/node_modules/@date-fns/tz/date/index.js +19 -1
- package/lib/node_modules/@date-fns/tz/date/index.js.map +1 -1
- package/lib/node_modules/@date-fns/tz/date/mini.js +6 -34
- package/lib/node_modules/@date-fns/tz/date/mini.js.map +1 -1
- package/lib/node_modules/@date-fns/tz/tzOffset/index.js +5 -6
- package/lib/node_modules/@date-fns/tz/tzOffset/index.js.map +1 -1
- package/lib/node_modules/janus-gateway/npm/dist/janus.es.js +3241 -0
- package/lib/node_modules/janus-gateway/npm/dist/janus.es.js.map +1 -0
- package/lib/node_modules/js-beautify/js/src/css/index.js +1 -1
- package/lib/node_modules/js-beautify/js/src/css/options.js +1 -1
- package/lib/node_modules/js-beautify/js/src/html/beautifier.js +1 -1
- package/lib/node_modules/js-beautify/js/src/html/index.js +1 -1
- package/lib/node_modules/js-beautify/js/src/html/options.js +1 -1
- package/lib/node_modules/js-beautify/js/src/html/tokenizer.js +1 -1
- package/lib/node_modules/js-beautify/js/src/javascript/beautifier.js +1 -1
- package/lib/node_modules/js-beautify/js/src/javascript/index.js +1 -1
- package/lib/node_modules/js-beautify/js/src/javascript/options.js +1 -1
- package/lib/node_modules/js-beautify/js/src/javascript/tokenizer.js +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouType.js +82 -115
- package/lib/node_modules/libphonenumber-js/es6/AsYouType.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js +58 -68
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternMatcher.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js +23 -36
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.PatternParser.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js +9 -13
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.complete.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js +187 -226
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js +27 -21
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeFormatter.util.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeParser.js +117 -150
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeParser.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeState.js +38 -57
- package/lib/node_modules/libphonenumber-js/es6/AsYouTypeState.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/ParseError.js +109 -70
- package/lib/node_modules/libphonenumber-js/es6/ParseError.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/PhoneNumber.js +57 -72
- package/lib/node_modules/libphonenumber-js/es6/PhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/constants.js +9 -15
- package/lib/node_modules/libphonenumber-js/es6/constants.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/format.js +93 -67
- package/lib/node_modules/libphonenumber-js/es6/format.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js +1 -0
- package/lib/node_modules/libphonenumber-js/es6/formatIncompletePhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/getCountries.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/RFC3966.js +1 -0
- package/lib/node_modules/libphonenumber-js/es6/helpers/RFC3966.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/applyInternationalSeparatorStyle.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js +10 -49
- package/lib/node_modules/libphonenumber-js/es6/helpers/checkNumberLength.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js +32 -20
- package/lib/node_modules/libphonenumber-js/es6/helpers/extension/createExtensionPattern.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js +4 -5
- package/lib/node_modules/libphonenumber-js/es6/helpers/extension/extractExtension.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js +18 -25
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCode.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js +11 -12
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractCountryCallingCodeFromInternationalNumberWithoutPlusSign.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js +11 -13
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractFormattedPhoneNumberFromPossibleRfc3966NumberUri.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js +11 -21
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractNationalNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js +11 -12
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractNationalNumberFromPossiblyIncompleteNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js +12 -15
- package/lib/node_modules/libphonenumber-js/es6/helpers/extractPhoneContext.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/formatNationalNumberUsingFormat.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js +4 -4
- package/lib/node_modules/libphonenumber-js/es6/helpers/getCountryByCallingCode.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js +35 -59
- package/lib/node_modules/libphonenumber-js/es6/helpers/getCountryByNationalNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js +2 -2
- package/lib/node_modules/libphonenumber-js/es6/helpers/getIddPrefix.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/getNumberType.js +36 -38
- package/lib/node_modules/libphonenumber-js/es6/helpers/getNumberType.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js +1 -0
- package/lib/node_modules/libphonenumber-js/es6/helpers/getPossibleCountriesForNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/isObject.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js +10 -14
- package/lib/node_modules/libphonenumber-js/es6/helpers/isViablePhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js +4 -4
- package/lib/node_modules/libphonenumber-js/es6/helpers/matchesEntirely.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/parseDigits.js +27 -22
- package/lib/node_modules/libphonenumber-js/es6/helpers/parseDigits.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js +6 -6
- package/lib/node_modules/libphonenumber-js/es6/helpers/stripIddPrefix.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/isPossible.js +7 -6
- package/lib/node_modules/libphonenumber-js/es6/isPossible.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/isValid.js +5 -6
- package/lib/node_modules/libphonenumber-js/es6/isValid.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/isValidPhoneNumber.js +26 -44
- package/lib/node_modules/libphonenumber-js/es6/isValidPhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/metadata.js +69 -120
- package/lib/node_modules/libphonenumber-js/es6/metadata.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/normalizeArguments.js +68 -87
- package/lib/node_modules/libphonenumber-js/es6/normalizeArguments.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/parse.js +40 -58
- package/lib/node_modules/libphonenumber-js/es6/parse.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js +36 -32
- package/lib/node_modules/libphonenumber-js/es6/parseIncompletePhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/parsePhoneNumber.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js +26 -44
- package/lib/node_modules/libphonenumber-js/es6/parsePhoneNumberWithError_.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/parsePhoneNumber_.js +28 -46
- package/lib/node_modules/libphonenumber-js/es6/parsePhoneNumber_.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/es6/tools/semver-compare.js.map +1 -1
- package/lib/node_modules/libphonenumber-js/metadata.min.json.js +18 -18
- package/lib/node_modules/libphonenumber-js/metadata.min.json.js.map +1 -1
- package/lib/node_modules/prop-types/index.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/DayPicker.js +64 -88
- package/lib/node_modules/react-day-picker/dist/esm/DayPicker.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/UI.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/classes/CalendarDay.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/classes/CalendarMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/classes/CalendarWeek.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/classes/DateLib.js +6 -71
- package/lib/node_modules/react-day-picker/dist/esm/classes/DateLib.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Button.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/CaptionLabel.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Chevron.js +14 -18
- package/lib/node_modules/react-day-picker/dist/esm/components/Chevron.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Day.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/DayButton.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Dropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/DropdownNav.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Footer.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Month.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/MonthCaption.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/MonthGrid.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Months.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/MonthsDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Nav.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/NextMonthButton.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Option.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/PreviousMonthButton.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Root.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Select.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Week.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/WeekNumber.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/WeekNumberHeader.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Weekday.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Weekdays.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/Weeks.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/components/YearsDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatCaption.js +2 -3
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatCaption.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatDay.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatMonthDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatWeekNumber.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatWeekNumberHeader.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatWeekdayName.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/formatYearDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/formatters/index.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/calculateFocusTarget.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/createGetModifiers.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/endOfBroadcastWeek.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getBroadcastWeeksInMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getClassNamesForModifiers.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getComponents.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDataAttributes.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDates.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDays.js +4 -4
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDays.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDefaultClassNames.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getDisplayMonths.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getFocusableDate.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getFormatters.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getInitialMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getMonthOptions.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getMonths.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getNavMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getNextFocus.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getNextMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getPreviousMonth.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getStyleForModifiers.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getWeekdays.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getWeeks.js +2 -2
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getWeeks.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getYearOptions.js +11 -9
- package/lib/node_modules/react-day-picker/dist/esm/helpers/getYearOptions.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/startOfBroadcastWeek.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/helpers/useControlledValue.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/index.js +2 -2
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelDayButton.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelGrid.js +2 -3
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelGrid.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelGridcell.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelMonthDropdown.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelMonthDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelNav.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelNext.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelNext.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelPrevious.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelPrevious.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelWeekNumber.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelWeekNumber.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelWeekNumberHeader.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelWeekNumberHeader.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelWeekday.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelYearDropdown.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/labels/labelYearDropdown.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/selection/useMulti.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/selection/useRange.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/selection/useSingle.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useAnimation.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useAnimation.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useCalendar.js +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useCalendar.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useDayPicker.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useFocus.js +0 -6
- package/lib/node_modules/react-day-picker/dist/esm/useFocus.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/useSelection.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/addToRange.js +1 -6
- package/lib/node_modules/react-day-picker/dist/esm/utils/addToRange.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/dateMatchModifiers.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/rangeContainsDayOfWeek.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/rangeContainsModifiers.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/rangeIncludesDate.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/rangeOverlaps.js.map +1 -1
- package/lib/node_modules/react-day-picker/dist/esm/utils/typeguards.js.map +1 -1
- package/lib/node_modules/react-hook-form/dist/index.esm.js +44 -67
- package/lib/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
- package/lib/node_modules/react-virtuoso/dist/index.js +1200 -1246
- package/lib/node_modules/react-virtuoso/dist/index.js.map +1 -1
- package/lib/node_modules/ua-parser-js/src/ua-parser.js +71 -85
- package/lib/node_modules/ua-parser-js/src/ua-parser.js.map +1 -1
- package/lib/styles.css +1 -1
- package/lib/styles.css.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +106 -106
- package/lib/node_modules/@date-fns/tz/tzName/index.js +0 -40
- package/lib/node_modules/@date-fns/tz/tzName/index.js.map +0 -1
- package/lib/node_modules/react-day-picker/node_modules/date-fns/eachYearOfInterval.js +0 -64
- package/lib/node_modules/react-day-picker/node_modules/date-fns/eachYearOfInterval.js.map +0 -1
|
@@ -0,0 +1,3241 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2016 Meetecho
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
7
|
+
a copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
to deal in the Software without restriction, including without limitation
|
|
9
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included
|
|
14
|
+
in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
17
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line no-unused-vars
|
|
26
|
+
var Janus = function (factory) {
|
|
27
|
+
if (typeof define === 'function' && define.amd) {
|
|
28
|
+
define(factory);
|
|
29
|
+
} else if (typeof module === 'object' && module.exports) {
|
|
30
|
+
module.exports = factory();
|
|
31
|
+
} else if (typeof window === 'object') {
|
|
32
|
+
return factory();
|
|
33
|
+
}
|
|
34
|
+
}(function () {
|
|
35
|
+
// List of sessions
|
|
36
|
+
Janus.sessions = new Map();
|
|
37
|
+
Janus.isExtensionEnabled = function () {
|
|
38
|
+
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
|
39
|
+
// No need for the extension, getDisplayMedia is supported
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (window.navigator.userAgent.match('Chrome')) {
|
|
43
|
+
let chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
|
|
44
|
+
let maxver = 33;
|
|
45
|
+
if (window.navigator.userAgent.match('Linux')) maxver = 35; // "known" crash in chrome 34 and 35 on linux
|
|
46
|
+
if (chromever >= 26 && chromever <= maxver) {
|
|
47
|
+
// Older versions of Chrome don't support this extension-based approach, so lie
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return Janus.extension.isInstalled();
|
|
51
|
+
} else {
|
|
52
|
+
// Firefox and others, no need for the extension (but this doesn't mean it will work)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var defaultExtension = {
|
|
57
|
+
// Screensharing Chrome Extension ID
|
|
58
|
+
extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj',
|
|
59
|
+
isInstalled: function () {
|
|
60
|
+
return document.querySelector('#janus-extension-installed') !== null;
|
|
61
|
+
},
|
|
62
|
+
getScreen: function (callback) {
|
|
63
|
+
let pending = window.setTimeout(function () {
|
|
64
|
+
let error = new Error('NavigatorUserMediaError');
|
|
65
|
+
error.name = 'The required Chrome extension is not installed: click <a href="#">here</a> to install it. (NOTE: this will need you to refresh the page)';
|
|
66
|
+
return callback(error);
|
|
67
|
+
}, 1000);
|
|
68
|
+
this.cache[pending] = callback;
|
|
69
|
+
window.postMessage({
|
|
70
|
+
type: 'janusGetScreen',
|
|
71
|
+
id: pending
|
|
72
|
+
}, '*');
|
|
73
|
+
},
|
|
74
|
+
init: function () {
|
|
75
|
+
let cache = {};
|
|
76
|
+
this.cache = cache;
|
|
77
|
+
// Wait for events from the Chrome Extension
|
|
78
|
+
window.addEventListener('message', function (event) {
|
|
79
|
+
if (event.origin != window.location.origin) return;
|
|
80
|
+
if (event.data.type == 'janusGotScreen' && cache[event.data.id]) {
|
|
81
|
+
let callback = cache[event.data.id];
|
|
82
|
+
delete cache[event.data.id];
|
|
83
|
+
if (event.data.sourceId === '') {
|
|
84
|
+
// user canceled
|
|
85
|
+
let error = new Error('NavigatorUserMediaError');
|
|
86
|
+
error.name = 'You cancelled the request for permission, giving up...';
|
|
87
|
+
callback(error);
|
|
88
|
+
} else {
|
|
89
|
+
callback(null, event.data.sourceId);
|
|
90
|
+
}
|
|
91
|
+
} else if (event.data.type == 'janusGetScreenPending') {
|
|
92
|
+
window.clearTimeout(event.data.id);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
Janus.useDefaultDependencies = function (deps) {
|
|
98
|
+
let f = deps && deps.fetch || fetch;
|
|
99
|
+
let p = deps && deps.Promise || Promise;
|
|
100
|
+
let socketCls = deps && deps.WebSocket || WebSocket;
|
|
101
|
+
return {
|
|
102
|
+
newWebSocket: function (server, proto) {
|
|
103
|
+
return new socketCls(server, proto);
|
|
104
|
+
},
|
|
105
|
+
extension: deps && deps.extension || defaultExtension,
|
|
106
|
+
isArray: function (arr) {
|
|
107
|
+
return Array.isArray(arr);
|
|
108
|
+
},
|
|
109
|
+
webRTCAdapter: deps && deps.adapter || adapter,
|
|
110
|
+
httpAPICall: function (url, options) {
|
|
111
|
+
let fetchOptions = {
|
|
112
|
+
method: options.verb,
|
|
113
|
+
headers: {
|
|
114
|
+
'Accept': 'application/json, text/plain, */*'
|
|
115
|
+
},
|
|
116
|
+
cache: 'no-cache'
|
|
117
|
+
};
|
|
118
|
+
if (options.verb === "POST") {
|
|
119
|
+
fetchOptions.headers['Content-Type'] = 'application/json';
|
|
120
|
+
}
|
|
121
|
+
if (typeof options.withCredentials !== 'undefined') {
|
|
122
|
+
fetchOptions.credentials = options.withCredentials === true ? 'include' : options.withCredentials ? options.withCredentials : 'omit';
|
|
123
|
+
}
|
|
124
|
+
if (options.body) {
|
|
125
|
+
fetchOptions.body = JSON.stringify(options.body);
|
|
126
|
+
}
|
|
127
|
+
let fetching = f(url, fetchOptions).catch(function (error) {
|
|
128
|
+
return p.reject({
|
|
129
|
+
message: 'Probably a network error, is the server down?',
|
|
130
|
+
error: error
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* fetch() does not natively support timeouts.
|
|
136
|
+
* Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first.
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
if (options.timeout) {
|
|
140
|
+
// eslint-disable-next-line no-unused-vars
|
|
141
|
+
let timeout = new p(function (resolve, reject) {
|
|
142
|
+
let timerId = setTimeout(function () {
|
|
143
|
+
clearTimeout(timerId);
|
|
144
|
+
return reject({
|
|
145
|
+
message: 'Request timed out',
|
|
146
|
+
timeout: options.timeout
|
|
147
|
+
});
|
|
148
|
+
}, options.timeout);
|
|
149
|
+
});
|
|
150
|
+
fetching = p.race([fetching, timeout]);
|
|
151
|
+
}
|
|
152
|
+
fetching.then(function (response) {
|
|
153
|
+
if (response.ok) {
|
|
154
|
+
if (typeof options.success === typeof Janus.noop) {
|
|
155
|
+
return response.json().then(function (parsed) {
|
|
156
|
+
try {
|
|
157
|
+
options.success(parsed);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
Janus.error('Unhandled httpAPICall success callback error', error);
|
|
160
|
+
}
|
|
161
|
+
}, function (error) {
|
|
162
|
+
return p.reject({
|
|
163
|
+
message: 'Failed to parse response body',
|
|
164
|
+
error: error,
|
|
165
|
+
response: response
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
return p.reject({
|
|
171
|
+
message: 'API call failed',
|
|
172
|
+
response: response
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}).catch(function (error) {
|
|
176
|
+
if (typeof options.error === typeof Janus.noop) {
|
|
177
|
+
options.error(error.message || '<< internal error >>', error);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return fetching;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
Janus.useOldDependencies = function (deps) {
|
|
185
|
+
let jq = deps && deps.jQuery || jQuery;
|
|
186
|
+
let socketCls = deps && deps.WebSocket || WebSocket;
|
|
187
|
+
return {
|
|
188
|
+
newWebSocket: function (server, proto) {
|
|
189
|
+
return new socketCls(server, proto);
|
|
190
|
+
},
|
|
191
|
+
isArray: function (arr) {
|
|
192
|
+
return jq.isArray(arr);
|
|
193
|
+
},
|
|
194
|
+
extension: deps && deps.extension || defaultExtension,
|
|
195
|
+
webRTCAdapter: deps && deps.adapter || adapter,
|
|
196
|
+
httpAPICall: function (url, options) {
|
|
197
|
+
let payload = typeof options.body !== 'undefined' ? {
|
|
198
|
+
contentType: 'application/json',
|
|
199
|
+
data: JSON.stringify(options.body)
|
|
200
|
+
} : {};
|
|
201
|
+
let credentials = typeof options.withCredentials !== 'undefined' ? {
|
|
202
|
+
xhrFields: {
|
|
203
|
+
withCredentials: options.withCredentials
|
|
204
|
+
}
|
|
205
|
+
} : {};
|
|
206
|
+
return jq.ajax(jq.extend(payload, credentials, {
|
|
207
|
+
url: url,
|
|
208
|
+
type: options.verb,
|
|
209
|
+
cache: false,
|
|
210
|
+
dataType: 'json',
|
|
211
|
+
async: options.async,
|
|
212
|
+
timeout: options.timeout,
|
|
213
|
+
success: function (result) {
|
|
214
|
+
if (typeof options.success === typeof Janus.noop) {
|
|
215
|
+
options.success(result);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
// eslint-disable-next-line no-unused-vars
|
|
219
|
+
error: function (xhr, status, err) {
|
|
220
|
+
if (typeof options.error === typeof Janus.noop) {
|
|
221
|
+
options.error(status, err);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Helper function to convert a deprecated media object to a tracks array
|
|
230
|
+
Janus.mediaToTracks = function (media) {
|
|
231
|
+
let tracks = [];
|
|
232
|
+
if (!media) {
|
|
233
|
+
// Default is bidirectional audio and video, using default devices
|
|
234
|
+
tracks.push({
|
|
235
|
+
type: 'audio',
|
|
236
|
+
capture: true,
|
|
237
|
+
recv: true
|
|
238
|
+
});
|
|
239
|
+
tracks.push({
|
|
240
|
+
type: 'video',
|
|
241
|
+
capture: true,
|
|
242
|
+
recv: true
|
|
243
|
+
});
|
|
244
|
+
} else {
|
|
245
|
+
if (!media.keepAudio && media.audio !== false && (typeof media.audio === 'undefined' || media.audio || media.audioSend || media.audioRecv || media.addAudio || media.replaceAudio || media.removeAudio)) {
|
|
246
|
+
// We may need an audio track
|
|
247
|
+
let track = {
|
|
248
|
+
type: 'audio'
|
|
249
|
+
};
|
|
250
|
+
if (media.removeAudio) {
|
|
251
|
+
track.remove = true;
|
|
252
|
+
} else {
|
|
253
|
+
if (media.addAudio) track.add = true;else if (media.replaceAudio) track.replace = true;
|
|
254
|
+
// Check if we need to capture an audio device
|
|
255
|
+
if (media.audioSend !== false) track.capture = media.audio || true;
|
|
256
|
+
// Check if we need to receive audio
|
|
257
|
+
if (media.audioRecv !== false) track.recv = true;
|
|
258
|
+
}
|
|
259
|
+
// Add an audio track if needed
|
|
260
|
+
if (track.remove || track.capture || track.recv) tracks.push(track);
|
|
261
|
+
}
|
|
262
|
+
if (!media.keepVideo && media.video !== false && (typeof media.video === 'undefined' || media.video || media.videoSend || media.videoRecv || media.addVideo || media.replaceVideo || media.removeVideo)) {
|
|
263
|
+
// We may need a video track
|
|
264
|
+
let track = {
|
|
265
|
+
type: 'video'
|
|
266
|
+
};
|
|
267
|
+
if (media.removeVideo) {
|
|
268
|
+
track.remove = true;
|
|
269
|
+
} else {
|
|
270
|
+
if (media.addVideo) track.add = true;else if (media.replaceVideo) track.replace = true;
|
|
271
|
+
// Check if we need to capture a video device
|
|
272
|
+
if (media.videoSend !== false) {
|
|
273
|
+
track.capture = media.video || true;
|
|
274
|
+
if (['screen', 'window', 'desktop'].includes(track.capture)) {
|
|
275
|
+
// Change the type to 'screen'
|
|
276
|
+
track.type = 'screen';
|
|
277
|
+
track.capture = {
|
|
278
|
+
video: {}
|
|
279
|
+
};
|
|
280
|
+
// Check if there's constraints
|
|
281
|
+
if (media.screenshareFrameRate) track.capture.frameRate = media.screenshareFrameRate;
|
|
282
|
+
if (media.screenshareHeight) track.capture.height = media.screenshareHeight;
|
|
283
|
+
if (media.screenshareWidth) track.capture.width = media.screenshareWidth;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Check if we need to receive video
|
|
287
|
+
if (media.videoRecv !== false) track.recv = true;
|
|
288
|
+
}
|
|
289
|
+
// Add a video track if needed
|
|
290
|
+
if (track.remove || track.capture || track.recv) tracks.push(track);
|
|
291
|
+
}
|
|
292
|
+
if (media.data) {
|
|
293
|
+
// We need a data channel
|
|
294
|
+
tracks.push({
|
|
295
|
+
type: 'data'
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Done
|
|
300
|
+
return tracks;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// Helper function to convert a track object to a set of constraints
|
|
304
|
+
Janus.trackConstraints = function (track) {
|
|
305
|
+
let constraints = {};
|
|
306
|
+
if (!track || !track.capture) return constraints;
|
|
307
|
+
if (track.type === 'audio') {
|
|
308
|
+
// Just put the capture part in the constraints
|
|
309
|
+
constraints.audio = track.capture;
|
|
310
|
+
} else if (track.type === 'video') {
|
|
311
|
+
// Check if one of the keywords was passed
|
|
312
|
+
if ((track.simulcast || track.svc) && track.capture === true) track.capture = 'hires';
|
|
313
|
+
if (track.capture === true || typeof track.capture === 'object') {
|
|
314
|
+
// Use the provided capture object as video constraint
|
|
315
|
+
constraints.video = track.capture;
|
|
316
|
+
} else {
|
|
317
|
+
let width = 0;
|
|
318
|
+
let height = 0;
|
|
319
|
+
if (track.capture === 'lowres') {
|
|
320
|
+
// Small resolution, 4:3
|
|
321
|
+
width = 320;
|
|
322
|
+
height = 240;
|
|
323
|
+
} else if (track.capture === 'lowres-16:9') {
|
|
324
|
+
// Small resolution, 16:9
|
|
325
|
+
width = 320;
|
|
326
|
+
height = 180;
|
|
327
|
+
} else if (track.capture === 'hires' || track.capture === 'hires-16:9' || track.capture === 'hdres') {
|
|
328
|
+
// High(HD) resolution is only 16:9
|
|
329
|
+
width = 1280;
|
|
330
|
+
height = 720;
|
|
331
|
+
} else if (track.capture === 'fhdres') {
|
|
332
|
+
// Full HD resolution is only 16:9
|
|
333
|
+
width = 1920;
|
|
334
|
+
height = 1080;
|
|
335
|
+
} else if (track.capture === '4kres') {
|
|
336
|
+
// 4K resolution is only 16:9
|
|
337
|
+
width = 3840;
|
|
338
|
+
height = 2160;
|
|
339
|
+
} else if (track.capture === 'stdres') {
|
|
340
|
+
// Normal resolution, 4:3
|
|
341
|
+
width = 640;
|
|
342
|
+
height = 480;
|
|
343
|
+
} else if (track.capture === 'stdres-16:9') {
|
|
344
|
+
// Normal resolution, 16:9
|
|
345
|
+
width = 640;
|
|
346
|
+
height = 360;
|
|
347
|
+
} else {
|
|
348
|
+
Janus.log('Default video setting is stdres 4:3');
|
|
349
|
+
width = 640;
|
|
350
|
+
height = 480;
|
|
351
|
+
}
|
|
352
|
+
constraints.video = {
|
|
353
|
+
width: {
|
|
354
|
+
ideal: width
|
|
355
|
+
},
|
|
356
|
+
height: {
|
|
357
|
+
ideal: height
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} else if (track.type === 'screen') {
|
|
362
|
+
// Use the provided capture object as video constraint
|
|
363
|
+
constraints.video = track.capture;
|
|
364
|
+
}
|
|
365
|
+
return constraints;
|
|
366
|
+
};
|
|
367
|
+
Janus.noop = function () {};
|
|
368
|
+
Janus.dataChanDefaultLabel = "JanusDataChannel";
|
|
369
|
+
|
|
370
|
+
// Note: in the future we may want to change this, e.g., as was
|
|
371
|
+
// attempted in https://github.com/meetecho/janus-gateway/issues/1670
|
|
372
|
+
Janus.endOfCandidates = null;
|
|
373
|
+
|
|
374
|
+
// Stop all tracks from a given stream
|
|
375
|
+
Janus.stopAllTracks = function (stream) {
|
|
376
|
+
try {
|
|
377
|
+
// Try a MediaStreamTrack.stop() for each track
|
|
378
|
+
let tracks = stream.getTracks();
|
|
379
|
+
for (let mst of tracks) {
|
|
380
|
+
Janus.log(mst);
|
|
381
|
+
if (mst && mst.dontStop !== true) {
|
|
382
|
+
mst.stop();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// eslint-disable-next-line no-unused-vars
|
|
386
|
+
} catch (e) {
|
|
387
|
+
// Do nothing if this fails
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Initialization
|
|
392
|
+
Janus.init = function (options) {
|
|
393
|
+
options = options || {};
|
|
394
|
+
options.callback = typeof options.callback == "function" ? options.callback : Janus.noop;
|
|
395
|
+
if (Janus.initDone) {
|
|
396
|
+
// Already initialized
|
|
397
|
+
options.callback();
|
|
398
|
+
} else {
|
|
399
|
+
if (typeof console.log == "undefined") {
|
|
400
|
+
console.log = function () {};
|
|
401
|
+
}
|
|
402
|
+
// Console logging (all debugging disabled by default)
|
|
403
|
+
Janus.trace = Janus.noop;
|
|
404
|
+
Janus.debug = Janus.noop;
|
|
405
|
+
Janus.vdebug = Janus.noop;
|
|
406
|
+
Janus.log = Janus.noop;
|
|
407
|
+
Janus.warn = Janus.noop;
|
|
408
|
+
Janus.error = Janus.noop;
|
|
409
|
+
if (options.debug === true || options.debug === "all") {
|
|
410
|
+
// Enable all debugging levels
|
|
411
|
+
Janus.trace = console.trace.bind(console);
|
|
412
|
+
Janus.debug = console.debug.bind(console);
|
|
413
|
+
Janus.vdebug = console.debug.bind(console);
|
|
414
|
+
Janus.log = console.log.bind(console);
|
|
415
|
+
Janus.warn = console.warn.bind(console);
|
|
416
|
+
Janus.error = console.error.bind(console);
|
|
417
|
+
} else if (Array.isArray(options.debug)) {
|
|
418
|
+
for (let d of options.debug) {
|
|
419
|
+
switch (d) {
|
|
420
|
+
case "trace":
|
|
421
|
+
Janus.trace = console.trace.bind(console);
|
|
422
|
+
break;
|
|
423
|
+
case "debug":
|
|
424
|
+
Janus.debug = console.debug.bind(console);
|
|
425
|
+
break;
|
|
426
|
+
case "vdebug":
|
|
427
|
+
Janus.vdebug = console.debug.bind(console);
|
|
428
|
+
break;
|
|
429
|
+
case "log":
|
|
430
|
+
Janus.log = console.log.bind(console);
|
|
431
|
+
break;
|
|
432
|
+
case "warn":
|
|
433
|
+
Janus.warn = console.warn.bind(console);
|
|
434
|
+
break;
|
|
435
|
+
case "error":
|
|
436
|
+
Janus.error = console.error.bind(console);
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
Janus.log("Initializing library");
|
|
442
|
+
let usedDependencies = options.dependencies || Janus.useDefaultDependencies();
|
|
443
|
+
Janus.isArray = usedDependencies.isArray;
|
|
444
|
+
Janus.webRTCAdapter = usedDependencies.webRTCAdapter;
|
|
445
|
+
Janus.httpAPICall = usedDependencies.httpAPICall;
|
|
446
|
+
Janus.newWebSocket = usedDependencies.newWebSocket;
|
|
447
|
+
Janus.extension = usedDependencies.extension;
|
|
448
|
+
Janus.extension.init();
|
|
449
|
+
|
|
450
|
+
// Helper method to enumerate devices
|
|
451
|
+
Janus.listDevices = function (callback, config) {
|
|
452
|
+
callback = typeof callback == "function" ? callback : Janus.noop;
|
|
453
|
+
if (!config) config = {
|
|
454
|
+
audio: true,
|
|
455
|
+
video: true
|
|
456
|
+
};
|
|
457
|
+
if (Janus.isGetUserMediaAvailable()) {
|
|
458
|
+
navigator.mediaDevices.getUserMedia(config).then(function (stream) {
|
|
459
|
+
navigator.mediaDevices.enumerateDevices().then(function (devices) {
|
|
460
|
+
Janus.debug(devices);
|
|
461
|
+
callback(devices);
|
|
462
|
+
// Get rid of the now useless stream
|
|
463
|
+
Janus.stopAllTracks(stream);
|
|
464
|
+
});
|
|
465
|
+
}).catch(function (err) {
|
|
466
|
+
Janus.error(err);
|
|
467
|
+
callback([]);
|
|
468
|
+
});
|
|
469
|
+
} else {
|
|
470
|
+
Janus.warn("navigator.mediaDevices unavailable");
|
|
471
|
+
callback([]);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
// Helper methods to attach/reattach a stream to a video element (previously part of adapter.js)
|
|
475
|
+
Janus.attachMediaStream = function (element, stream) {
|
|
476
|
+
try {
|
|
477
|
+
element.srcObject = stream;
|
|
478
|
+
// eslint-disable-next-line no-unused-vars
|
|
479
|
+
} catch (e) {
|
|
480
|
+
try {
|
|
481
|
+
element.src = URL.createObjectURL(stream);
|
|
482
|
+
} catch (e) {
|
|
483
|
+
Janus.error("Error attaching stream to element", e);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
Janus.reattachMediaStream = function (to, from) {
|
|
488
|
+
try {
|
|
489
|
+
to.srcObject = from.srcObject;
|
|
490
|
+
// eslint-disable-next-line no-unused-vars
|
|
491
|
+
} catch (e) {
|
|
492
|
+
try {
|
|
493
|
+
to.src = from.src;
|
|
494
|
+
} catch (e) {
|
|
495
|
+
Janus.error("Error reattaching stream to element", e);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
// Detect tab close: make sure we don't loose existing onbeforeunload handlers
|
|
500
|
+
// (note: for iOS we need to subscribe to a different event, 'pagehide', see
|
|
501
|
+
// https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe)
|
|
502
|
+
let iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
|
|
503
|
+
let eventName = iOS ? 'pagehide' : 'beforeunload';
|
|
504
|
+
let oldOBF = window["on" + eventName];
|
|
505
|
+
window.addEventListener(eventName, function () {
|
|
506
|
+
Janus.log("Closing window");
|
|
507
|
+
for (const [sessionId, session] of Janus.sessions) {
|
|
508
|
+
if (session && session.destroyOnUnload) {
|
|
509
|
+
Janus.log("Destroying session " + sessionId);
|
|
510
|
+
session.destroy({
|
|
511
|
+
unload: true,
|
|
512
|
+
notifyDestroyed: false
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (oldOBF && typeof oldOBF == "function") {
|
|
517
|
+
oldOBF();
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
// If this is a Safari, check if VP8 or VP9 are supported
|
|
521
|
+
Janus.safariVp8 = false;
|
|
522
|
+
Janus.safariVp9 = false;
|
|
523
|
+
if (Janus.webRTCAdapter.browserDetails.browser === 'safari' && Janus.webRTCAdapter.browserDetails.version >= 605) {
|
|
524
|
+
// Let's see if RTCRtpSender.getCapabilities() is there
|
|
525
|
+
if (RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") && RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) {
|
|
526
|
+
for (let codec of RTCRtpSender.getCapabilities("video").codecs) {
|
|
527
|
+
if (codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") {
|
|
528
|
+
Janus.safariVp8 = true;
|
|
529
|
+
} else if (codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp9") {
|
|
530
|
+
Janus.safariVp9 = true;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (Janus.safariVp8) {
|
|
534
|
+
Janus.log("This version of Safari supports VP8");
|
|
535
|
+
} else {
|
|
536
|
+
Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
// We do it in a very ugly way, as there's no alternative...
|
|
540
|
+
// We create a PeerConnection to see if VP8 is in an offer
|
|
541
|
+
let testpc = new RTCPeerConnection({});
|
|
542
|
+
testpc.createOffer({
|
|
543
|
+
offerToReceiveVideo: true
|
|
544
|
+
}).then(function (offer) {
|
|
545
|
+
Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1;
|
|
546
|
+
Janus.safariVp9 = offer.sdp.indexOf("VP9") !== -1;
|
|
547
|
+
if (Janus.safariVp8) {
|
|
548
|
+
Janus.log("This version of Safari supports VP8");
|
|
549
|
+
} else {
|
|
550
|
+
Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu");
|
|
551
|
+
}
|
|
552
|
+
testpc.close();
|
|
553
|
+
testpc = null;
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
Janus.initDone = true;
|
|
558
|
+
options.callback();
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// Helper method to check whether WebRTC is supported by this browser
|
|
563
|
+
Janus.isWebrtcSupported = function () {
|
|
564
|
+
return !!window.RTCPeerConnection;
|
|
565
|
+
};
|
|
566
|
+
// Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP)
|
|
567
|
+
Janus.isGetUserMediaAvailable = function () {
|
|
568
|
+
return navigator.mediaDevices && navigator.mediaDevices.getUserMedia;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
// Helper method to create random identifiers (e.g., transaction)
|
|
572
|
+
Janus.randomString = function (len) {
|
|
573
|
+
let charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
574
|
+
let randomString = '';
|
|
575
|
+
for (let i = 0; i < len; i++) {
|
|
576
|
+
let randomPoz = Math.floor(Math.random() * charSet.length);
|
|
577
|
+
randomString += charSet.charAt(randomPoz);
|
|
578
|
+
}
|
|
579
|
+
return randomString;
|
|
580
|
+
};
|
|
581
|
+
function Janus(gatewayCallbacks) {
|
|
582
|
+
gatewayCallbacks = gatewayCallbacks || {};
|
|
583
|
+
gatewayCallbacks.success = typeof gatewayCallbacks.success == "function" ? gatewayCallbacks.success : Janus.noop;
|
|
584
|
+
gatewayCallbacks.error = typeof gatewayCallbacks.error == "function" ? gatewayCallbacks.error : Janus.noop;
|
|
585
|
+
gatewayCallbacks.destroyed = typeof gatewayCallbacks.destroyed == "function" ? gatewayCallbacks.destroyed : Janus.noop;
|
|
586
|
+
if (!Janus.initDone) {
|
|
587
|
+
gatewayCallbacks.error("Library not initialized");
|
|
588
|
+
return {};
|
|
589
|
+
}
|
|
590
|
+
if (!Janus.isWebrtcSupported()) {
|
|
591
|
+
gatewayCallbacks.error("WebRTC not supported by this browser");
|
|
592
|
+
return {};
|
|
593
|
+
}
|
|
594
|
+
Janus.log("Library initialized: " + Janus.initDone);
|
|
595
|
+
if (!gatewayCallbacks.server) {
|
|
596
|
+
gatewayCallbacks.error("Invalid server url");
|
|
597
|
+
return {};
|
|
598
|
+
}
|
|
599
|
+
let websockets = false;
|
|
600
|
+
let ws = null;
|
|
601
|
+
let wsHandlers = {};
|
|
602
|
+
let wsKeepaliveTimeoutId = null;
|
|
603
|
+
let servers = null;
|
|
604
|
+
let serversIndex = 0;
|
|
605
|
+
let server = gatewayCallbacks.server;
|
|
606
|
+
if (Janus.isArray(server)) {
|
|
607
|
+
Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
|
|
608
|
+
server = null;
|
|
609
|
+
servers = gatewayCallbacks.server;
|
|
610
|
+
Janus.debug(servers);
|
|
611
|
+
} else {
|
|
612
|
+
if (server.indexOf("ws") === 0) {
|
|
613
|
+
websockets = true;
|
|
614
|
+
Janus.log("Using WebSockets to contact Janus: " + server);
|
|
615
|
+
} else {
|
|
616
|
+
websockets = false;
|
|
617
|
+
Janus.log("Using REST API to contact Janus: " + server);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
let iceServers = gatewayCallbacks.iceServers || [{
|
|
621
|
+
urls: "stun:stun.l.google.com:19302"
|
|
622
|
+
}];
|
|
623
|
+
let iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;
|
|
624
|
+
let bundlePolicy = gatewayCallbacks.bundlePolicy;
|
|
625
|
+
// Whether we should enable the withCredentials flag for XHR requests
|
|
626
|
+
let withCredentials = false;
|
|
627
|
+
if (typeof gatewayCallbacks.withCredentials !== 'undefined' && gatewayCallbacks.withCredentials !== null) withCredentials = gatewayCallbacks.withCredentials === true;
|
|
628
|
+
// Optional max events
|
|
629
|
+
let maxev = 10;
|
|
630
|
+
if (typeof gatewayCallbacks.max_poll_events !== 'undefined' && gatewayCallbacks.max_poll_events !== null) maxev = gatewayCallbacks.max_poll_events;
|
|
631
|
+
if (maxev < 1) maxev = 1;
|
|
632
|
+
// Token to use (only if the token based authentication mechanism is enabled)
|
|
633
|
+
let token = null;
|
|
634
|
+
if (typeof gatewayCallbacks.token !== 'undefined' && gatewayCallbacks.token !== null) token = gatewayCallbacks.token;
|
|
635
|
+
// API secret to use (only if the shared API secret is enabled)
|
|
636
|
+
let apisecret = null;
|
|
637
|
+
if (typeof gatewayCallbacks.apisecret !== 'undefined' && gatewayCallbacks.apisecret !== null) apisecret = gatewayCallbacks.apisecret;
|
|
638
|
+
// Whether we should destroy this session when onbeforeunload is called
|
|
639
|
+
this.destroyOnUnload = true;
|
|
640
|
+
if (typeof gatewayCallbacks.destroyOnUnload !== 'undefined' && gatewayCallbacks.destroyOnUnload !== null) this.destroyOnUnload = gatewayCallbacks.destroyOnUnload === true;
|
|
641
|
+
// Some timeout-related values
|
|
642
|
+
let keepAlivePeriod = 25000;
|
|
643
|
+
if (typeof gatewayCallbacks.keepAlivePeriod !== 'undefined' && gatewayCallbacks.keepAlivePeriod !== null) keepAlivePeriod = gatewayCallbacks.keepAlivePeriod;
|
|
644
|
+
if (isNaN(keepAlivePeriod)) keepAlivePeriod = 25000;
|
|
645
|
+
let longPollTimeout = 60000;
|
|
646
|
+
if (typeof gatewayCallbacks.longPollTimeout !== 'undefined' && gatewayCallbacks.longPollTimeout !== null) longPollTimeout = gatewayCallbacks.longPollTimeout;
|
|
647
|
+
if (isNaN(longPollTimeout)) longPollTimeout = 60000;
|
|
648
|
+
|
|
649
|
+
// overrides for default maxBitrate values for simulcasting
|
|
650
|
+
function getMaxBitrates(simulcastMaxBitrates) {
|
|
651
|
+
let maxBitrates = {
|
|
652
|
+
high: 900000,
|
|
653
|
+
medium: 300000,
|
|
654
|
+
low: 100000
|
|
655
|
+
};
|
|
656
|
+
if (typeof simulcastMaxBitrates !== 'undefined' && simulcastMaxBitrates !== null) {
|
|
657
|
+
if (simulcastMaxBitrates.high) maxBitrates.high = simulcastMaxBitrates.high;
|
|
658
|
+
if (simulcastMaxBitrates.medium) maxBitrates.medium = simulcastMaxBitrates.medium;
|
|
659
|
+
if (simulcastMaxBitrates.low) maxBitrates.low = simulcastMaxBitrates.low;
|
|
660
|
+
}
|
|
661
|
+
return maxBitrates;
|
|
662
|
+
}
|
|
663
|
+
let connected = false;
|
|
664
|
+
let sessionId = null;
|
|
665
|
+
let pluginHandles = new Map();
|
|
666
|
+
let that = this;
|
|
667
|
+
let retries = 0;
|
|
668
|
+
let transactions = new Map();
|
|
669
|
+
createSession(gatewayCallbacks);
|
|
670
|
+
|
|
671
|
+
// Public methods
|
|
672
|
+
this.getServer = function () {
|
|
673
|
+
return server;
|
|
674
|
+
};
|
|
675
|
+
this.isConnected = function () {
|
|
676
|
+
return connected;
|
|
677
|
+
};
|
|
678
|
+
this.reconnect = function (callbacks) {
|
|
679
|
+
callbacks = callbacks || {};
|
|
680
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
681
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
682
|
+
callbacks["reconnect"] = true;
|
|
683
|
+
createSession(callbacks);
|
|
684
|
+
};
|
|
685
|
+
this.getSessionId = function () {
|
|
686
|
+
return sessionId;
|
|
687
|
+
};
|
|
688
|
+
this.getInfo = function (callbacks) {
|
|
689
|
+
getInfo(callbacks);
|
|
690
|
+
};
|
|
691
|
+
this.destroy = function (callbacks) {
|
|
692
|
+
destroySession(callbacks);
|
|
693
|
+
};
|
|
694
|
+
this.attach = function (callbacks) {
|
|
695
|
+
createHandle(callbacks);
|
|
696
|
+
};
|
|
697
|
+
function eventHandler() {
|
|
698
|
+
if (sessionId == null) return;
|
|
699
|
+
Janus.debug('Long poll...');
|
|
700
|
+
if (!connected) {
|
|
701
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
let longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
|
|
705
|
+
if (maxev) longpoll = longpoll + "&maxev=" + maxev;
|
|
706
|
+
if (token) longpoll = longpoll + "&token=" + encodeURIComponent(token);
|
|
707
|
+
if (apisecret) longpoll = longpoll + "&apisecret=" + encodeURIComponent(apisecret);
|
|
708
|
+
Janus.httpAPICall(longpoll, {
|
|
709
|
+
verb: 'GET',
|
|
710
|
+
withCredentials: withCredentials,
|
|
711
|
+
success: handleEvent,
|
|
712
|
+
timeout: longPollTimeout,
|
|
713
|
+
error: function (textStatus, errorThrown) {
|
|
714
|
+
Janus.error(textStatus + ":", errorThrown);
|
|
715
|
+
retries++;
|
|
716
|
+
if (retries > 3) {
|
|
717
|
+
// Did we just lose the server? :-(
|
|
718
|
+
connected = false;
|
|
719
|
+
gatewayCallbacks.error("Lost connection to the server (is it down?)");
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
eventHandler();
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Private event handler: this will trigger plugin callbacks, if set
|
|
728
|
+
function handleEvent(json, skipTimeout) {
|
|
729
|
+
retries = 0;
|
|
730
|
+
if (!websockets && typeof sessionId !== 'undefined' && sessionId !== null && skipTimeout !== true) eventHandler();
|
|
731
|
+
if (!websockets && Janus.isArray(json)) {
|
|
732
|
+
// We got an array: it means we passed a maxev > 1, iterate on all objects
|
|
733
|
+
for (let i = 0; i < json.length; i++) {
|
|
734
|
+
handleEvent(json[i], true);
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (json["janus"] === "keepalive") {
|
|
739
|
+
// Nothing happened
|
|
740
|
+
Janus.vdebug("Got a keepalive on session " + sessionId);
|
|
741
|
+
return;
|
|
742
|
+
} else if (json["janus"] === "server_info") {
|
|
743
|
+
// Just info on the Janus instance
|
|
744
|
+
Janus.debug("Got info on the Janus instance");
|
|
745
|
+
Janus.debug(json);
|
|
746
|
+
const transaction = json["transaction"];
|
|
747
|
+
if (transaction) {
|
|
748
|
+
const reportSuccess = transactions.get(transaction);
|
|
749
|
+
if (reportSuccess) reportSuccess(json);
|
|
750
|
+
transactions.delete(transaction);
|
|
751
|
+
}
|
|
752
|
+
return;
|
|
753
|
+
} else if (json["janus"] === "ack") {
|
|
754
|
+
// Just an ack, we can probably ignore
|
|
755
|
+
Janus.debug("Got an ack on session " + sessionId);
|
|
756
|
+
Janus.debug(json);
|
|
757
|
+
const transaction = json["transaction"];
|
|
758
|
+
if (transaction) {
|
|
759
|
+
const reportSuccess = transactions.get(transaction);
|
|
760
|
+
if (reportSuccess) reportSuccess(json);
|
|
761
|
+
transactions.delete(transaction);
|
|
762
|
+
}
|
|
763
|
+
return;
|
|
764
|
+
} else if (json["janus"] === "success") {
|
|
765
|
+
// Success!
|
|
766
|
+
Janus.debug("Got a success on session " + sessionId);
|
|
767
|
+
Janus.debug(json);
|
|
768
|
+
const transaction = json["transaction"];
|
|
769
|
+
if (transaction) {
|
|
770
|
+
const reportSuccess = transactions.get(transaction);
|
|
771
|
+
if (reportSuccess) reportSuccess(json);
|
|
772
|
+
transactions.delete(transaction);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
} else if (json["janus"] === "trickle") {
|
|
776
|
+
// We got a trickle candidate from Janus
|
|
777
|
+
const sender = json["sender"];
|
|
778
|
+
if (!sender) {
|
|
779
|
+
Janus.warn("Missing sender...");
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
783
|
+
if (!pluginHandle) {
|
|
784
|
+
Janus.debug("This handle is not attached to this session");
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
let candidate = json["candidate"];
|
|
788
|
+
Janus.debug("Got a trickled candidate on session " + sessionId);
|
|
789
|
+
Janus.debug(candidate);
|
|
790
|
+
let config = pluginHandle.webrtcStuff;
|
|
791
|
+
if (config.pc && config.remoteSdp) {
|
|
792
|
+
// Add candidate right now
|
|
793
|
+
Janus.debug("Adding remote candidate:", candidate);
|
|
794
|
+
if (!candidate || candidate.completed === true) {
|
|
795
|
+
// end-of-candidates
|
|
796
|
+
config.pc.addIceCandidate(Janus.endOfCandidates);
|
|
797
|
+
} else {
|
|
798
|
+
// New candidate
|
|
799
|
+
config.pc.addIceCandidate(candidate);
|
|
800
|
+
}
|
|
801
|
+
} else {
|
|
802
|
+
// We didn't do setRemoteDescription (trickle got here before the offer?)
|
|
803
|
+
Janus.debug("We didn't do setRemoteDescription (trickle got here before the offer?), caching candidate");
|
|
804
|
+
if (!config.candidates) config.candidates = [];
|
|
805
|
+
config.candidates.push(candidate);
|
|
806
|
+
Janus.debug(config.candidates);
|
|
807
|
+
}
|
|
808
|
+
} else if (json["janus"] === "webrtcup") {
|
|
809
|
+
// The PeerConnection with the server is up! Notify this
|
|
810
|
+
Janus.debug("Got a webrtcup event on session " + sessionId);
|
|
811
|
+
Janus.debug(json);
|
|
812
|
+
const sender = json["sender"];
|
|
813
|
+
if (!sender) {
|
|
814
|
+
Janus.warn("Missing sender...");
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
818
|
+
if (!pluginHandle) {
|
|
819
|
+
Janus.debug("This handle is not attached to this session");
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
pluginHandle.webrtcState(true);
|
|
823
|
+
return;
|
|
824
|
+
} else if (json["janus"] === "hangup") {
|
|
825
|
+
// A plugin asked the core to hangup a PeerConnection on one of our handles
|
|
826
|
+
Janus.debug("Got a hangup event on session " + sessionId);
|
|
827
|
+
Janus.debug(json);
|
|
828
|
+
const sender = json["sender"];
|
|
829
|
+
if (!sender) {
|
|
830
|
+
Janus.warn("Missing sender...");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
834
|
+
if (!pluginHandle) {
|
|
835
|
+
Janus.debug("This handle is not attached to this session");
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
pluginHandle.webrtcState(false, json["reason"]);
|
|
839
|
+
pluginHandle.hangup();
|
|
840
|
+
} else if (json["janus"] === "detached") {
|
|
841
|
+
// A plugin asked the core to detach one of our handles
|
|
842
|
+
Janus.debug("Got a detached event on session " + sessionId);
|
|
843
|
+
Janus.debug(json);
|
|
844
|
+
const sender = json["sender"];
|
|
845
|
+
if (!sender) {
|
|
846
|
+
Janus.warn("Missing sender...");
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
850
|
+
if (!pluginHandle) {
|
|
851
|
+
// Don't warn here because destroyHandle causes this situation.
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
pluginHandle.ondetached();
|
|
855
|
+
pluginHandle.detach();
|
|
856
|
+
} else if (json["janus"] === "media") {
|
|
857
|
+
// Media started/stopped flowing
|
|
858
|
+
Janus.debug("Got a media event on session " + sessionId);
|
|
859
|
+
Janus.debug(json);
|
|
860
|
+
const sender = json["sender"];
|
|
861
|
+
if (!sender) {
|
|
862
|
+
Janus.warn("Missing sender...");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
866
|
+
if (!pluginHandle) {
|
|
867
|
+
Janus.debug("This handle is not attached to this session");
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
pluginHandle.mediaState(json["type"], json["receiving"], json["mid"]);
|
|
871
|
+
} else if (json["janus"] === "slowlink") {
|
|
872
|
+
Janus.debug("Got a slowlink event on session " + sessionId);
|
|
873
|
+
Janus.debug(json);
|
|
874
|
+
// Trouble uplink or downlink
|
|
875
|
+
const sender = json["sender"];
|
|
876
|
+
if (!sender) {
|
|
877
|
+
Janus.warn("Missing sender...");
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
881
|
+
if (!pluginHandle) {
|
|
882
|
+
Janus.debug("This handle is not attached to this session");
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
pluginHandle.slowLink(json["uplink"], json["lost"], json["mid"]);
|
|
886
|
+
} else if (json["janus"] === "error") {
|
|
887
|
+
// Oops, something wrong happened
|
|
888
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
889
|
+
Janus.debug(json);
|
|
890
|
+
let transaction = json["transaction"];
|
|
891
|
+
if (transaction) {
|
|
892
|
+
let reportSuccess = transactions.get(transaction);
|
|
893
|
+
if (reportSuccess) {
|
|
894
|
+
reportSuccess(json);
|
|
895
|
+
}
|
|
896
|
+
transactions.delete(transaction);
|
|
897
|
+
}
|
|
898
|
+
return;
|
|
899
|
+
} else if (json["janus"] === "event") {
|
|
900
|
+
Janus.debug("Got a plugin event on session " + sessionId);
|
|
901
|
+
Janus.debug(json);
|
|
902
|
+
const sender = json["sender"];
|
|
903
|
+
if (!sender) {
|
|
904
|
+
Janus.warn("Missing sender...");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
let plugindata = json["plugindata"];
|
|
908
|
+
if (!plugindata) {
|
|
909
|
+
Janus.warn("Missing plugindata...");
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
Janus.debug(" -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
|
|
913
|
+
let data = plugindata["data"];
|
|
914
|
+
Janus.debug(data);
|
|
915
|
+
const pluginHandle = pluginHandles.get(sender);
|
|
916
|
+
if (!pluginHandle) {
|
|
917
|
+
Janus.warn("This handle is not attached to this session");
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
let jsep = json["jsep"];
|
|
921
|
+
if (jsep) {
|
|
922
|
+
Janus.debug("Handling SDP as well...");
|
|
923
|
+
Janus.debug(jsep);
|
|
924
|
+
}
|
|
925
|
+
let callback = pluginHandle.onmessage;
|
|
926
|
+
if (callback) {
|
|
927
|
+
Janus.debug("Notifying application...");
|
|
928
|
+
// Send to callback specified when attaching plugin handle
|
|
929
|
+
callback(data, jsep);
|
|
930
|
+
} else {
|
|
931
|
+
// Send to generic callback (?)
|
|
932
|
+
Janus.debug("No provided notification callback");
|
|
933
|
+
}
|
|
934
|
+
} else if (json["janus"] === "timeout") {
|
|
935
|
+
Janus.error("Timeout on session " + sessionId);
|
|
936
|
+
Janus.debug(json);
|
|
937
|
+
if (websockets) {
|
|
938
|
+
ws.close(3504, "Gateway timeout");
|
|
939
|
+
}
|
|
940
|
+
return;
|
|
941
|
+
} else {
|
|
942
|
+
Janus.warn("Unknown message/event '" + json["janus"] + "' on session " + sessionId);
|
|
943
|
+
Janus.debug(json);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Private helper to send keep-alive messages on WebSockets
|
|
948
|
+
function keepAlive() {
|
|
949
|
+
if (!server || !websockets || !connected) return;
|
|
950
|
+
wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
|
|
951
|
+
let request = {
|
|
952
|
+
"janus": "keepalive",
|
|
953
|
+
"session_id": sessionId,
|
|
954
|
+
"transaction": Janus.randomString(12)
|
|
955
|
+
};
|
|
956
|
+
if (token) request["token"] = token;
|
|
957
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
958
|
+
ws.send(JSON.stringify(request));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Private method to create a session
|
|
962
|
+
function createSession(callbacks) {
|
|
963
|
+
let transaction = Janus.randomString(12);
|
|
964
|
+
let request = {
|
|
965
|
+
"janus": "create",
|
|
966
|
+
"transaction": transaction
|
|
967
|
+
};
|
|
968
|
+
if (callbacks["reconnect"]) {
|
|
969
|
+
// We're reconnecting, claim the session
|
|
970
|
+
connected = false;
|
|
971
|
+
request["janus"] = "claim";
|
|
972
|
+
request["session_id"] = sessionId;
|
|
973
|
+
// If we were using websockets, ignore the old connection
|
|
974
|
+
if (ws) {
|
|
975
|
+
ws.onopen = null;
|
|
976
|
+
ws.onerror = null;
|
|
977
|
+
ws.onclose = null;
|
|
978
|
+
if (wsKeepaliveTimeoutId) {
|
|
979
|
+
clearTimeout(wsKeepaliveTimeoutId);
|
|
980
|
+
wsKeepaliveTimeoutId = null;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (token) request["token"] = token;
|
|
985
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
986
|
+
if (!server && Janus.isArray(servers)) {
|
|
987
|
+
// We still need to find a working server from the list we were given
|
|
988
|
+
server = servers[serversIndex];
|
|
989
|
+
if (server.indexOf("ws") === 0) {
|
|
990
|
+
websockets = true;
|
|
991
|
+
Janus.log("Server #" + (serversIndex + 1) + ": trying WebSockets to contact Janus (" + server + ")");
|
|
992
|
+
} else {
|
|
993
|
+
websockets = false;
|
|
994
|
+
Janus.log("Server #" + (serversIndex + 1) + ": trying REST API to contact Janus (" + server + ")");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (websockets) {
|
|
998
|
+
ws = Janus.newWebSocket(server, 'janus-protocol');
|
|
999
|
+
wsHandlers = {
|
|
1000
|
+
'error': function () {
|
|
1001
|
+
Janus.error("Error connecting to the Janus WebSockets server... " + server);
|
|
1002
|
+
if (Janus.isArray(servers) && !callbacks["reconnect"]) {
|
|
1003
|
+
serversIndex++;
|
|
1004
|
+
if (serversIndex === servers.length) {
|
|
1005
|
+
// We tried all the servers the user gave us and they all failed
|
|
1006
|
+
callbacks.error("Error connecting to any of the provided Janus servers: Is the server down?");
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
// Let's try the next server
|
|
1010
|
+
server = null;
|
|
1011
|
+
setTimeout(function () {
|
|
1012
|
+
createSession(callbacks);
|
|
1013
|
+
}, 200);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
callbacks.error("Error connecting to the Janus WebSockets server: Is the server down?");
|
|
1017
|
+
},
|
|
1018
|
+
'open': function () {
|
|
1019
|
+
// We need to be notified about the success
|
|
1020
|
+
transactions.set(transaction, function (json) {
|
|
1021
|
+
Janus.debug(json);
|
|
1022
|
+
if (json["janus"] !== "success") {
|
|
1023
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1024
|
+
callbacks.error(json["error"].reason);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);
|
|
1028
|
+
connected = true;
|
|
1029
|
+
sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
|
|
1030
|
+
if (callbacks["reconnect"]) {
|
|
1031
|
+
Janus.log("Claimed session: " + sessionId);
|
|
1032
|
+
} else {
|
|
1033
|
+
Janus.log("Created session: " + sessionId);
|
|
1034
|
+
}
|
|
1035
|
+
Janus.sessions.set(sessionId, that);
|
|
1036
|
+
callbacks.success();
|
|
1037
|
+
});
|
|
1038
|
+
ws.send(JSON.stringify(request));
|
|
1039
|
+
},
|
|
1040
|
+
'message': function (event) {
|
|
1041
|
+
handleEvent(JSON.parse(event.data));
|
|
1042
|
+
},
|
|
1043
|
+
'close': function () {
|
|
1044
|
+
if (!server || !connected) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
connected = false;
|
|
1048
|
+
// FIXME What if this is called when the page is closed?
|
|
1049
|
+
gatewayCallbacks.error("Lost connection to the server (is it down?)");
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
for (let eventName in wsHandlers) {
|
|
1053
|
+
ws.addEventListener(eventName, wsHandlers[eventName]);
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
Janus.httpAPICall(server, {
|
|
1058
|
+
verb: 'POST',
|
|
1059
|
+
withCredentials: withCredentials,
|
|
1060
|
+
body: request,
|
|
1061
|
+
success: function (json) {
|
|
1062
|
+
Janus.debug(json);
|
|
1063
|
+
if (json["janus"] !== "success") {
|
|
1064
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1065
|
+
callbacks.error(json["error"].reason);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
connected = true;
|
|
1069
|
+
sessionId = json["session_id"] ? json["session_id"] : json.data["id"];
|
|
1070
|
+
if (callbacks["reconnect"]) {
|
|
1071
|
+
Janus.log("Claimed session: " + sessionId);
|
|
1072
|
+
} else {
|
|
1073
|
+
Janus.log("Created session: " + sessionId);
|
|
1074
|
+
}
|
|
1075
|
+
Janus.sessions.set(sessionId, that);
|
|
1076
|
+
eventHandler();
|
|
1077
|
+
callbacks.success();
|
|
1078
|
+
},
|
|
1079
|
+
error: function (textStatus, errorThrown) {
|
|
1080
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1081
|
+
if (Janus.isArray(servers) && !callbacks["reconnect"]) {
|
|
1082
|
+
serversIndex++;
|
|
1083
|
+
if (serversIndex === servers.length) {
|
|
1084
|
+
// We tried all the servers the user gave us and they all failed
|
|
1085
|
+
callbacks.error("Error connecting to any of the provided Janus servers: Is the server down?");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
// Let's try the next server
|
|
1089
|
+
server = null;
|
|
1090
|
+
setTimeout(function () {
|
|
1091
|
+
createSession(callbacks);
|
|
1092
|
+
}, 200);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (errorThrown === "") callbacks.error(textStatus + ": Is the server down?");else if (errorThrown && errorThrown.error) callbacks.error(textStatus + ": " + errorThrown.error.message);else callbacks.error(textStatus + ": " + errorThrown);
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Private method to get info on the server
|
|
1101
|
+
function getInfo(callbacks) {
|
|
1102
|
+
callbacks = callbacks || {};
|
|
1103
|
+
// FIXME This method triggers a success even when we fail
|
|
1104
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1105
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1106
|
+
Janus.log("Getting info on Janus instance");
|
|
1107
|
+
if (!connected) {
|
|
1108
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1109
|
+
callbacks.error("Is the server down? (connected=false)");
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
// We just need to send an "info" request
|
|
1113
|
+
let transaction = Janus.randomString(12);
|
|
1114
|
+
let request = {
|
|
1115
|
+
"janus": "info",
|
|
1116
|
+
"transaction": transaction
|
|
1117
|
+
};
|
|
1118
|
+
if (token) request["token"] = token;
|
|
1119
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1120
|
+
if (websockets) {
|
|
1121
|
+
transactions.set(transaction, function (json) {
|
|
1122
|
+
Janus.log("Server info:");
|
|
1123
|
+
Janus.debug(json);
|
|
1124
|
+
if (json["janus"] !== "server_info") {
|
|
1125
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1126
|
+
}
|
|
1127
|
+
callbacks.success(json);
|
|
1128
|
+
});
|
|
1129
|
+
ws.send(JSON.stringify(request));
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
Janus.httpAPICall(server, {
|
|
1133
|
+
verb: 'POST',
|
|
1134
|
+
withCredentials: withCredentials,
|
|
1135
|
+
body: request,
|
|
1136
|
+
success: function (json) {
|
|
1137
|
+
Janus.log("Server info:");
|
|
1138
|
+
Janus.debug(json);
|
|
1139
|
+
if (json["janus"] !== "server_info") {
|
|
1140
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1141
|
+
}
|
|
1142
|
+
callbacks.success(json);
|
|
1143
|
+
},
|
|
1144
|
+
error: function (textStatus, errorThrown) {
|
|
1145
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1146
|
+
if (errorThrown === "") callbacks.error(textStatus + ": Is the server down?");else callbacks.error(textStatus + ": " + errorThrown);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Private method to destroy a session
|
|
1152
|
+
function destroySession(callbacks) {
|
|
1153
|
+
callbacks = callbacks || {};
|
|
1154
|
+
// FIXME This method triggers a success even when we fail
|
|
1155
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1156
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1157
|
+
let unload = callbacks.unload === true;
|
|
1158
|
+
let notifyDestroyed = true;
|
|
1159
|
+
if (typeof callbacks.notifyDestroyed !== 'undefined' && callbacks.notifyDestroyed !== null) notifyDestroyed = callbacks.notifyDestroyed === true;
|
|
1160
|
+
let cleanupHandles = callbacks.cleanupHandles === true;
|
|
1161
|
+
Janus.log("Destroying session " + sessionId + " (unload=" + unload + ")");
|
|
1162
|
+
if (!sessionId) {
|
|
1163
|
+
Janus.warn("No session to destroy");
|
|
1164
|
+
callbacks.success();
|
|
1165
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (cleanupHandles) {
|
|
1169
|
+
for (const handleId of pluginHandles.keys()) destroyHandle(handleId, {
|
|
1170
|
+
noRequest: true
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
if (!connected) {
|
|
1174
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1175
|
+
sessionId = null;
|
|
1176
|
+
callbacks.success();
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
// No need to destroy all handles first, Janus will do that itself
|
|
1180
|
+
let request = {
|
|
1181
|
+
"janus": "destroy",
|
|
1182
|
+
"transaction": Janus.randomString(12)
|
|
1183
|
+
};
|
|
1184
|
+
if (token) request["token"] = token;
|
|
1185
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1186
|
+
if (unload) {
|
|
1187
|
+
// We're unloading the page: use sendBeacon for HTTP instead,
|
|
1188
|
+
// or just close the WebSocket connection if we're using that
|
|
1189
|
+
if (websockets) {
|
|
1190
|
+
ws.onclose = null;
|
|
1191
|
+
ws.close();
|
|
1192
|
+
ws = null;
|
|
1193
|
+
} else {
|
|
1194
|
+
navigator.sendBeacon(server + "/" + sessionId, JSON.stringify(request));
|
|
1195
|
+
}
|
|
1196
|
+
Janus.log("Destroyed session:");
|
|
1197
|
+
sessionId = null;
|
|
1198
|
+
connected = false;
|
|
1199
|
+
callbacks.success();
|
|
1200
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (websockets) {
|
|
1204
|
+
request["session_id"] = sessionId;
|
|
1205
|
+
let unbindWebSocket = function () {
|
|
1206
|
+
for (let eventName in wsHandlers) {
|
|
1207
|
+
ws.removeEventListener(eventName, wsHandlers[eventName]);
|
|
1208
|
+
}
|
|
1209
|
+
ws.removeEventListener('message', onUnbindMessage);
|
|
1210
|
+
ws.removeEventListener('error', onUnbindError);
|
|
1211
|
+
if (wsKeepaliveTimeoutId) {
|
|
1212
|
+
clearTimeout(wsKeepaliveTimeoutId);
|
|
1213
|
+
}
|
|
1214
|
+
ws.close();
|
|
1215
|
+
};
|
|
1216
|
+
let onUnbindMessage = function (event) {
|
|
1217
|
+
let data = JSON.parse(event.data);
|
|
1218
|
+
if (data.session_id == request.session_id && data.transaction == request.transaction) {
|
|
1219
|
+
unbindWebSocket();
|
|
1220
|
+
callbacks.success();
|
|
1221
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
let onUnbindError = function () {
|
|
1225
|
+
unbindWebSocket();
|
|
1226
|
+
callbacks.error("Failed to destroy the server: Is the server down?");
|
|
1227
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1228
|
+
};
|
|
1229
|
+
ws.addEventListener('message', onUnbindMessage);
|
|
1230
|
+
ws.addEventListener('error', onUnbindError);
|
|
1231
|
+
if (ws.readyState === 1) {
|
|
1232
|
+
ws.send(JSON.stringify(request));
|
|
1233
|
+
} else {
|
|
1234
|
+
onUnbindError();
|
|
1235
|
+
}
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
Janus.httpAPICall(server + "/" + sessionId, {
|
|
1239
|
+
verb: 'POST',
|
|
1240
|
+
withCredentials: withCredentials,
|
|
1241
|
+
body: request,
|
|
1242
|
+
success: function (json) {
|
|
1243
|
+
Janus.log("Destroyed session:");
|
|
1244
|
+
Janus.debug(json);
|
|
1245
|
+
sessionId = null;
|
|
1246
|
+
connected = false;
|
|
1247
|
+
if (json["janus"] !== "success") {
|
|
1248
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1249
|
+
}
|
|
1250
|
+
callbacks.success();
|
|
1251
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1252
|
+
},
|
|
1253
|
+
error: function (textStatus, errorThrown) {
|
|
1254
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1255
|
+
// Reset everything anyway
|
|
1256
|
+
sessionId = null;
|
|
1257
|
+
connected = false;
|
|
1258
|
+
callbacks.success();
|
|
1259
|
+
if (notifyDestroyed) gatewayCallbacks.destroyed();
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// Private method to create a plugin handle
|
|
1265
|
+
function createHandle(callbacks) {
|
|
1266
|
+
callbacks = callbacks || {};
|
|
1267
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1268
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1269
|
+
callbacks.dataChannelOptions = callbacks.dataChannelOptions || {
|
|
1270
|
+
ordered: true
|
|
1271
|
+
};
|
|
1272
|
+
callbacks.consentDialog = typeof callbacks.consentDialog == "function" ? callbacks.consentDialog : Janus.noop;
|
|
1273
|
+
callbacks.connectionState = typeof callbacks.connectionState == "function" ? callbacks.connectionState : Janus.noop;
|
|
1274
|
+
callbacks.iceState = typeof callbacks.iceState == "function" ? callbacks.iceState : Janus.noop;
|
|
1275
|
+
callbacks.mediaState = typeof callbacks.mediaState == "function" ? callbacks.mediaState : Janus.noop;
|
|
1276
|
+
callbacks.webrtcState = typeof callbacks.webrtcState == "function" ? callbacks.webrtcState : Janus.noop;
|
|
1277
|
+
callbacks.slowLink = typeof callbacks.slowLink == "function" ? callbacks.slowLink : Janus.noop;
|
|
1278
|
+
callbacks.onmessage = typeof callbacks.onmessage == "function" ? callbacks.onmessage : Janus.noop;
|
|
1279
|
+
callbacks.onlocaltrack = typeof callbacks.onlocaltrack == "function" ? callbacks.onlocaltrack : Janus.noop;
|
|
1280
|
+
callbacks.onremotetrack = typeof callbacks.onremotetrack == "function" ? callbacks.onremotetrack : Janus.noop;
|
|
1281
|
+
callbacks.ondata = typeof callbacks.ondata == "function" ? callbacks.ondata : Janus.noop;
|
|
1282
|
+
callbacks.ondataopen = typeof callbacks.ondataopen == "function" ? callbacks.ondataopen : Janus.noop;
|
|
1283
|
+
callbacks.oncleanup = typeof callbacks.oncleanup == "function" ? callbacks.oncleanup : Janus.noop;
|
|
1284
|
+
callbacks.ondetached = typeof callbacks.ondetached == "function" ? callbacks.ondetached : Janus.noop;
|
|
1285
|
+
if (!connected) {
|
|
1286
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1287
|
+
callbacks.error("Is the server down? (connected=false)");
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
let plugin = callbacks.plugin;
|
|
1291
|
+
if (!plugin) {
|
|
1292
|
+
Janus.error("Invalid plugin");
|
|
1293
|
+
callbacks.error("Invalid plugin");
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
let opaqueId = callbacks.opaqueId;
|
|
1297
|
+
let loopIndex = callbacks.loopIndex;
|
|
1298
|
+
let handleToken = callbacks.token ? callbacks.token : token;
|
|
1299
|
+
let transaction = Janus.randomString(12);
|
|
1300
|
+
let request = {
|
|
1301
|
+
"janus": "attach",
|
|
1302
|
+
"plugin": plugin,
|
|
1303
|
+
"opaque_id": opaqueId,
|
|
1304
|
+
"loop_index": loopIndex,
|
|
1305
|
+
"transaction": transaction
|
|
1306
|
+
};
|
|
1307
|
+
if (handleToken) request["token"] = handleToken;
|
|
1308
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1309
|
+
if (websockets) {
|
|
1310
|
+
transactions.set(transaction, function (json) {
|
|
1311
|
+
Janus.debug(json);
|
|
1312
|
+
if (json["janus"] !== "success") {
|
|
1313
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1314
|
+
callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
let handleId = json.data["id"];
|
|
1318
|
+
Janus.log("Created handle: " + handleId);
|
|
1319
|
+
let pluginHandle = {
|
|
1320
|
+
session: that,
|
|
1321
|
+
plugin: plugin,
|
|
1322
|
+
id: handleId,
|
|
1323
|
+
token: handleToken,
|
|
1324
|
+
detached: false,
|
|
1325
|
+
webrtcStuff: {
|
|
1326
|
+
started: false,
|
|
1327
|
+
myStream: null,
|
|
1328
|
+
streamExternal: false,
|
|
1329
|
+
mySdp: null,
|
|
1330
|
+
mediaConstraints: null,
|
|
1331
|
+
pc: null,
|
|
1332
|
+
dataChannelOptions: callbacks.dataChannelOptions,
|
|
1333
|
+
dataChannel: {},
|
|
1334
|
+
dtmfSender: null,
|
|
1335
|
+
trickle: true,
|
|
1336
|
+
iceDone: false,
|
|
1337
|
+
bitrate: {}
|
|
1338
|
+
},
|
|
1339
|
+
getId: function () {
|
|
1340
|
+
return handleId;
|
|
1341
|
+
},
|
|
1342
|
+
getPlugin: function () {
|
|
1343
|
+
return plugin;
|
|
1344
|
+
},
|
|
1345
|
+
getVolume: function (mid, result) {
|
|
1346
|
+
return getVolume(handleId, mid, true, result);
|
|
1347
|
+
},
|
|
1348
|
+
getRemoteVolume: function (mid, result) {
|
|
1349
|
+
return getVolume(handleId, mid, true, result);
|
|
1350
|
+
},
|
|
1351
|
+
getLocalVolume: function (mid, result) {
|
|
1352
|
+
return getVolume(handleId, mid, false, result);
|
|
1353
|
+
},
|
|
1354
|
+
isAudioMuted: function (mid) {
|
|
1355
|
+
return isMuted(handleId, mid, false);
|
|
1356
|
+
},
|
|
1357
|
+
muteAudio: function (mid) {
|
|
1358
|
+
return mute(handleId, mid, false, true);
|
|
1359
|
+
},
|
|
1360
|
+
unmuteAudio: function (mid) {
|
|
1361
|
+
return mute(handleId, mid, false, false);
|
|
1362
|
+
},
|
|
1363
|
+
isVideoMuted: function (mid) {
|
|
1364
|
+
return isMuted(handleId, mid, true);
|
|
1365
|
+
},
|
|
1366
|
+
muteVideo: function (mid) {
|
|
1367
|
+
return mute(handleId, mid, true, true);
|
|
1368
|
+
},
|
|
1369
|
+
unmuteVideo: function (mid) {
|
|
1370
|
+
return mute(handleId, mid, true, false);
|
|
1371
|
+
},
|
|
1372
|
+
getBitrate: function (mid) {
|
|
1373
|
+
return getBitrate(handleId, mid);
|
|
1374
|
+
},
|
|
1375
|
+
setMaxBitrate: function (mid, bitrate) {
|
|
1376
|
+
return setBitrate(handleId, mid, bitrate);
|
|
1377
|
+
},
|
|
1378
|
+
send: function (callbacks) {
|
|
1379
|
+
sendMessage(handleId, callbacks);
|
|
1380
|
+
},
|
|
1381
|
+
data: function (callbacks) {
|
|
1382
|
+
sendData(handleId, callbacks);
|
|
1383
|
+
},
|
|
1384
|
+
dtmf: function (callbacks) {
|
|
1385
|
+
sendDtmf(handleId, callbacks);
|
|
1386
|
+
},
|
|
1387
|
+
consentDialog: callbacks.consentDialog,
|
|
1388
|
+
connectionState: callbacks.connectionState,
|
|
1389
|
+
iceState: callbacks.iceState,
|
|
1390
|
+
mediaState: callbacks.mediaState,
|
|
1391
|
+
webrtcState: callbacks.webrtcState,
|
|
1392
|
+
slowLink: callbacks.slowLink,
|
|
1393
|
+
onmessage: callbacks.onmessage,
|
|
1394
|
+
createOffer: function (callbacks) {
|
|
1395
|
+
prepareWebrtc(handleId, true, callbacks);
|
|
1396
|
+
},
|
|
1397
|
+
createAnswer: function (callbacks) {
|
|
1398
|
+
prepareWebrtc(handleId, false, callbacks);
|
|
1399
|
+
},
|
|
1400
|
+
handleRemoteJsep: function (callbacks) {
|
|
1401
|
+
prepareWebrtcPeer(handleId, callbacks);
|
|
1402
|
+
},
|
|
1403
|
+
replaceTracks: function (callbacks) {
|
|
1404
|
+
replaceTracks(handleId, callbacks);
|
|
1405
|
+
},
|
|
1406
|
+
getLocalTracks: function () {
|
|
1407
|
+
return getLocalTracks(handleId);
|
|
1408
|
+
},
|
|
1409
|
+
getRemoteTracks: function () {
|
|
1410
|
+
return getRemoteTracks(handleId);
|
|
1411
|
+
},
|
|
1412
|
+
onlocaltrack: callbacks.onlocaltrack,
|
|
1413
|
+
onremotetrack: callbacks.onremotetrack,
|
|
1414
|
+
ondata: callbacks.ondata,
|
|
1415
|
+
ondataopen: callbacks.ondataopen,
|
|
1416
|
+
oncleanup: callbacks.oncleanup,
|
|
1417
|
+
ondetached: callbacks.ondetached,
|
|
1418
|
+
hangup: function (sendRequest) {
|
|
1419
|
+
cleanupWebrtc(handleId, sendRequest === true);
|
|
1420
|
+
},
|
|
1421
|
+
detach: function (callbacks) {
|
|
1422
|
+
destroyHandle(handleId, callbacks);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
pluginHandles.set(handleId, pluginHandle);
|
|
1426
|
+
callbacks.success(pluginHandle);
|
|
1427
|
+
});
|
|
1428
|
+
request["session_id"] = sessionId;
|
|
1429
|
+
ws.send(JSON.stringify(request));
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
Janus.httpAPICall(server + "/" + sessionId, {
|
|
1433
|
+
verb: 'POST',
|
|
1434
|
+
withCredentials: withCredentials,
|
|
1435
|
+
body: request,
|
|
1436
|
+
success: function (json) {
|
|
1437
|
+
Janus.debug(json);
|
|
1438
|
+
if (json["janus"] !== "success") {
|
|
1439
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1440
|
+
callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
let handleId = json.data["id"];
|
|
1444
|
+
Janus.log("Created handle: " + handleId);
|
|
1445
|
+
let pluginHandle = {
|
|
1446
|
+
session: that,
|
|
1447
|
+
plugin: plugin,
|
|
1448
|
+
id: handleId,
|
|
1449
|
+
token: handleToken,
|
|
1450
|
+
detached: false,
|
|
1451
|
+
webrtcStuff: {
|
|
1452
|
+
started: false,
|
|
1453
|
+
myStream: null,
|
|
1454
|
+
streamExternal: false,
|
|
1455
|
+
mySdp: null,
|
|
1456
|
+
mediaConstraints: null,
|
|
1457
|
+
pc: null,
|
|
1458
|
+
dataChannelOptions: callbacks.dataChannelOptions,
|
|
1459
|
+
dataChannel: {},
|
|
1460
|
+
dtmfSender: null,
|
|
1461
|
+
trickle: true,
|
|
1462
|
+
iceDone: false,
|
|
1463
|
+
bitrate: {}
|
|
1464
|
+
},
|
|
1465
|
+
getId: function () {
|
|
1466
|
+
return handleId;
|
|
1467
|
+
},
|
|
1468
|
+
getPlugin: function () {
|
|
1469
|
+
return plugin;
|
|
1470
|
+
},
|
|
1471
|
+
getVolume: function (mid, result) {
|
|
1472
|
+
return getVolume(handleId, mid, true, result);
|
|
1473
|
+
},
|
|
1474
|
+
getRemoteVolume: function (mid, result) {
|
|
1475
|
+
return getVolume(handleId, mid, true, result);
|
|
1476
|
+
},
|
|
1477
|
+
getLocalVolume: function (mid, result) {
|
|
1478
|
+
return getVolume(handleId, mid, false, result);
|
|
1479
|
+
},
|
|
1480
|
+
isAudioMuted: function (mid) {
|
|
1481
|
+
return isMuted(handleId, mid, false);
|
|
1482
|
+
},
|
|
1483
|
+
muteAudio: function (mid) {
|
|
1484
|
+
return mute(handleId, mid, false, true);
|
|
1485
|
+
},
|
|
1486
|
+
unmuteAudio: function (mid) {
|
|
1487
|
+
return mute(handleId, mid, false, false);
|
|
1488
|
+
},
|
|
1489
|
+
isVideoMuted: function (mid) {
|
|
1490
|
+
return isMuted(handleId, mid, true);
|
|
1491
|
+
},
|
|
1492
|
+
muteVideo: function (mid) {
|
|
1493
|
+
return mute(handleId, mid, true, true);
|
|
1494
|
+
},
|
|
1495
|
+
unmuteVideo: function (mid) {
|
|
1496
|
+
return mute(handleId, mid, true, false);
|
|
1497
|
+
},
|
|
1498
|
+
getBitrate: function (mid) {
|
|
1499
|
+
return getBitrate(handleId, mid);
|
|
1500
|
+
},
|
|
1501
|
+
setMaxBitrate: function (mid, bitrate) {
|
|
1502
|
+
return setBitrate(handleId, mid, bitrate);
|
|
1503
|
+
},
|
|
1504
|
+
send: function (callbacks) {
|
|
1505
|
+
sendMessage(handleId, callbacks);
|
|
1506
|
+
},
|
|
1507
|
+
data: function (callbacks) {
|
|
1508
|
+
sendData(handleId, callbacks);
|
|
1509
|
+
},
|
|
1510
|
+
dtmf: function (callbacks) {
|
|
1511
|
+
sendDtmf(handleId, callbacks);
|
|
1512
|
+
},
|
|
1513
|
+
consentDialog: callbacks.consentDialog,
|
|
1514
|
+
connectionState: callbacks.connectionState,
|
|
1515
|
+
iceState: callbacks.iceState,
|
|
1516
|
+
mediaState: callbacks.mediaState,
|
|
1517
|
+
webrtcState: callbacks.webrtcState,
|
|
1518
|
+
slowLink: callbacks.slowLink,
|
|
1519
|
+
onmessage: callbacks.onmessage,
|
|
1520
|
+
createOffer: function (callbacks) {
|
|
1521
|
+
prepareWebrtc(handleId, true, callbacks);
|
|
1522
|
+
},
|
|
1523
|
+
createAnswer: function (callbacks) {
|
|
1524
|
+
prepareWebrtc(handleId, false, callbacks);
|
|
1525
|
+
},
|
|
1526
|
+
handleRemoteJsep: function (callbacks) {
|
|
1527
|
+
prepareWebrtcPeer(handleId, callbacks);
|
|
1528
|
+
},
|
|
1529
|
+
replaceTracks: function (callbacks) {
|
|
1530
|
+
replaceTracks(handleId, callbacks);
|
|
1531
|
+
},
|
|
1532
|
+
getLocalTracks: function () {
|
|
1533
|
+
return getLocalTracks(handleId);
|
|
1534
|
+
},
|
|
1535
|
+
getRemoteTracks: function () {
|
|
1536
|
+
return getRemoteTracks(handleId);
|
|
1537
|
+
},
|
|
1538
|
+
onlocaltrack: callbacks.onlocaltrack,
|
|
1539
|
+
onremotetrack: callbacks.onremotetrack,
|
|
1540
|
+
ondata: callbacks.ondata,
|
|
1541
|
+
ondataopen: callbacks.ondataopen,
|
|
1542
|
+
oncleanup: callbacks.oncleanup,
|
|
1543
|
+
ondetached: callbacks.ondetached,
|
|
1544
|
+
hangup: function (sendRequest) {
|
|
1545
|
+
cleanupWebrtc(handleId, sendRequest === true);
|
|
1546
|
+
},
|
|
1547
|
+
detach: function (callbacks) {
|
|
1548
|
+
destroyHandle(handleId, callbacks);
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
pluginHandles.set(handleId, pluginHandle);
|
|
1552
|
+
callbacks.success(pluginHandle);
|
|
1553
|
+
},
|
|
1554
|
+
error: function (textStatus, errorThrown) {
|
|
1555
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1556
|
+
if (errorThrown === "") callbacks.error(textStatus + ": Is the server down?");else callbacks.error(textStatus + ": " + errorThrown);
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// Private method to send a message
|
|
1562
|
+
function sendMessage(handleId, callbacks) {
|
|
1563
|
+
callbacks = callbacks || {};
|
|
1564
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1565
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1566
|
+
if (!connected) {
|
|
1567
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1568
|
+
callbacks.error("Is the server down? (connected=false)");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1572
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1573
|
+
Janus.warn("Invalid handle");
|
|
1574
|
+
callbacks.error("Invalid handle");
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
let message = callbacks.message;
|
|
1578
|
+
let jsep = callbacks.jsep;
|
|
1579
|
+
let transaction = Janus.randomString(12);
|
|
1580
|
+
let request = {
|
|
1581
|
+
"janus": "message",
|
|
1582
|
+
"body": message,
|
|
1583
|
+
"transaction": transaction
|
|
1584
|
+
};
|
|
1585
|
+
if (pluginHandle.token) request["token"] = pluginHandle.token;
|
|
1586
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1587
|
+
if (jsep) {
|
|
1588
|
+
request.jsep = {
|
|
1589
|
+
type: jsep.type,
|
|
1590
|
+
sdp: jsep.sdp
|
|
1591
|
+
};
|
|
1592
|
+
if (jsep.e2ee) request.jsep.e2ee = true;
|
|
1593
|
+
if (jsep.rid_order === "hml" || jsep.rid_order === "lmh") request.jsep.rid_order = jsep.rid_order;
|
|
1594
|
+
if (jsep.force_relay) request.jsep.force_relay = true;
|
|
1595
|
+
// Check if there's SVC video streams to tell Janus about
|
|
1596
|
+
let svc = null;
|
|
1597
|
+
let config = pluginHandle.webrtcStuff;
|
|
1598
|
+
if (config.pc) {
|
|
1599
|
+
let transceivers = config.pc.getTransceivers();
|
|
1600
|
+
if (transceivers && transceivers.length > 0) {
|
|
1601
|
+
for (let mindex in transceivers) {
|
|
1602
|
+
let tr = transceivers[mindex];
|
|
1603
|
+
if (tr && tr.sender && tr.sender.track && tr.sender.track.kind === 'video') {
|
|
1604
|
+
let params = tr.sender.getParameters();
|
|
1605
|
+
if (params && params.encodings && params.encodings[0] && params.encodings[0].scalabilityMode) {
|
|
1606
|
+
// This video stream uses SVC
|
|
1607
|
+
if (!svc) svc = [];
|
|
1608
|
+
svc.push({
|
|
1609
|
+
mindex: parseInt(mindex),
|
|
1610
|
+
mid: tr.mid,
|
|
1611
|
+
svc: params.encodings[0].scalabilityMode
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if (svc) request.jsep.svc = svc;
|
|
1619
|
+
}
|
|
1620
|
+
Janus.debug("Sending message to plugin (handle=" + handleId + "):");
|
|
1621
|
+
Janus.debug(request);
|
|
1622
|
+
if (websockets) {
|
|
1623
|
+
request["session_id"] = sessionId;
|
|
1624
|
+
request["handle_id"] = handleId;
|
|
1625
|
+
transactions.set(transaction, function (json) {
|
|
1626
|
+
Janus.debug("Message sent!");
|
|
1627
|
+
Janus.debug(json);
|
|
1628
|
+
if (json["janus"] === "success") {
|
|
1629
|
+
// We got a success, must have been a synchronous transaction
|
|
1630
|
+
let plugindata = json["plugindata"];
|
|
1631
|
+
if (!plugindata) {
|
|
1632
|
+
Janus.warn("Request succeeded, but missing plugindata...");
|
|
1633
|
+
callbacks.success();
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
|
|
1637
|
+
let data = plugindata["data"];
|
|
1638
|
+
Janus.debug(data);
|
|
1639
|
+
callbacks.success(data);
|
|
1640
|
+
return;
|
|
1641
|
+
} else if (json["janus"] !== "ack") {
|
|
1642
|
+
// Not a success and not an ack, must be an error
|
|
1643
|
+
if (json["error"]) {
|
|
1644
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1645
|
+
callbacks.error(json["error"].code + " " + json["error"].reason);
|
|
1646
|
+
} else {
|
|
1647
|
+
Janus.error("Unknown error"); // FIXME
|
|
1648
|
+
callbacks.error("Unknown error");
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
// If we got here, the plugin decided to handle the request asynchronously
|
|
1653
|
+
callbacks.success();
|
|
1654
|
+
});
|
|
1655
|
+
ws.send(JSON.stringify(request));
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
|
|
1659
|
+
verb: 'POST',
|
|
1660
|
+
withCredentials: withCredentials,
|
|
1661
|
+
body: request,
|
|
1662
|
+
success: function (json) {
|
|
1663
|
+
Janus.debug("Message sent!");
|
|
1664
|
+
Janus.debug(json);
|
|
1665
|
+
if (json["janus"] === "success") {
|
|
1666
|
+
// We got a success, must have been a synchronous transaction
|
|
1667
|
+
let plugindata = json["plugindata"];
|
|
1668
|
+
if (!plugindata) {
|
|
1669
|
+
Janus.warn("Request succeeded, but missing plugindata...");
|
|
1670
|
+
callbacks.success();
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
|
|
1674
|
+
let data = plugindata["data"];
|
|
1675
|
+
Janus.debug(data);
|
|
1676
|
+
callbacks.success(data);
|
|
1677
|
+
return;
|
|
1678
|
+
} else if (json["janus"] !== "ack") {
|
|
1679
|
+
// Not a success and not an ack, must be an error
|
|
1680
|
+
if (json["error"]) {
|
|
1681
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1682
|
+
callbacks.error(json["error"].code + " " + json["error"].reason);
|
|
1683
|
+
} else {
|
|
1684
|
+
Janus.error("Unknown error"); // FIXME
|
|
1685
|
+
callbacks.error("Unknown error");
|
|
1686
|
+
}
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
// If we got here, the plugin decided to handle the request asynchronously
|
|
1690
|
+
callbacks.success();
|
|
1691
|
+
},
|
|
1692
|
+
error: function (textStatus, errorThrown) {
|
|
1693
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1694
|
+
callbacks.error(textStatus + ": " + errorThrown);
|
|
1695
|
+
}
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// Private method to send a trickle candidate
|
|
1700
|
+
function sendTrickleCandidate(handleId, candidate) {
|
|
1701
|
+
if (!connected) {
|
|
1702
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1706
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1707
|
+
Janus.warn("Invalid handle");
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
let request = {
|
|
1711
|
+
"janus": "trickle",
|
|
1712
|
+
"candidate": candidate,
|
|
1713
|
+
"transaction": Janus.randomString(12)
|
|
1714
|
+
};
|
|
1715
|
+
if (pluginHandle.token) request["token"] = pluginHandle.token;
|
|
1716
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1717
|
+
Janus.vdebug("Sending trickle candidate (handle=" + handleId + "):");
|
|
1718
|
+
Janus.vdebug(request);
|
|
1719
|
+
if (websockets) {
|
|
1720
|
+
request["session_id"] = sessionId;
|
|
1721
|
+
request["handle_id"] = handleId;
|
|
1722
|
+
ws.send(JSON.stringify(request));
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
|
|
1726
|
+
verb: 'POST',
|
|
1727
|
+
withCredentials: withCredentials,
|
|
1728
|
+
body: request,
|
|
1729
|
+
success: function (json) {
|
|
1730
|
+
Janus.vdebug("Candidate sent!");
|
|
1731
|
+
Janus.vdebug(json);
|
|
1732
|
+
if (json["janus"] !== "ack") {
|
|
1733
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
},
|
|
1737
|
+
error: function (textStatus, errorThrown) {
|
|
1738
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// Private method to create a data channel
|
|
1744
|
+
function createDataChannel(handleId, dclabel, dcprotocol, incoming, pendingData) {
|
|
1745
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1746
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1747
|
+
Janus.warn("Invalid handle");
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
let config = pluginHandle.webrtcStuff;
|
|
1751
|
+
if (!config.pc) {
|
|
1752
|
+
Janus.warn("Invalid PeerConnection");
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
let onDataChannelMessage = function (event) {
|
|
1756
|
+
Janus.log('Received message on data channel:', event);
|
|
1757
|
+
let label = event.target.label;
|
|
1758
|
+
pluginHandle.ondata(event.data, label);
|
|
1759
|
+
};
|
|
1760
|
+
let onDataChannelStateChange = function (event) {
|
|
1761
|
+
Janus.log('Received state change on data channel:', event);
|
|
1762
|
+
let label = event.target.label;
|
|
1763
|
+
let protocol = event.target.protocol;
|
|
1764
|
+
let dcState = config.dataChannel[label] ? config.dataChannel[label].readyState : "null";
|
|
1765
|
+
Janus.log('State change on <' + label + '> data channel: ' + dcState);
|
|
1766
|
+
if (dcState === 'open') {
|
|
1767
|
+
// Any pending messages to send?
|
|
1768
|
+
if (config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) {
|
|
1769
|
+
Janus.log("Sending pending messages on <" + label + ">:", config.dataChannel[label].pending.length);
|
|
1770
|
+
for (let data of config.dataChannel[label].pending) {
|
|
1771
|
+
Janus.log("Sending data on data channel <" + label + ">");
|
|
1772
|
+
Janus.debug(data);
|
|
1773
|
+
config.dataChannel[label].send(data);
|
|
1774
|
+
}
|
|
1775
|
+
config.dataChannel[label].pending = [];
|
|
1776
|
+
}
|
|
1777
|
+
// Notify the open data channel
|
|
1778
|
+
pluginHandle.ondataopen(label, protocol);
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
let onDataChannelError = function (error) {
|
|
1782
|
+
Janus.error('Got error on data channel:', error);
|
|
1783
|
+
// TODO
|
|
1784
|
+
};
|
|
1785
|
+
if (!incoming) {
|
|
1786
|
+
// FIXME Add options (ordered, maxRetransmits, etc.)
|
|
1787
|
+
let dcoptions = config.dataChannelOptions;
|
|
1788
|
+
if (dcprotocol) dcoptions.protocol = dcprotocol;
|
|
1789
|
+
config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, dcoptions);
|
|
1790
|
+
} else {
|
|
1791
|
+
// The channel was created by Janus
|
|
1792
|
+
config.dataChannel[dclabel] = incoming;
|
|
1793
|
+
}
|
|
1794
|
+
config.dataChannel[dclabel].onmessage = onDataChannelMessage;
|
|
1795
|
+
config.dataChannel[dclabel].onopen = onDataChannelStateChange;
|
|
1796
|
+
config.dataChannel[dclabel].onclose = onDataChannelStateChange;
|
|
1797
|
+
config.dataChannel[dclabel].onerror = onDataChannelError;
|
|
1798
|
+
config.dataChannel[dclabel].pending = [];
|
|
1799
|
+
if (pendingData) config.dataChannel[dclabel].pending.push(pendingData);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// Private method to send a data channel message
|
|
1803
|
+
function sendData(handleId, callbacks) {
|
|
1804
|
+
callbacks = callbacks || {};
|
|
1805
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1806
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1807
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1808
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1809
|
+
Janus.warn("Invalid handle");
|
|
1810
|
+
callbacks.error("Invalid handle");
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
let config = pluginHandle.webrtcStuff;
|
|
1814
|
+
let data = callbacks.text || callbacks.data;
|
|
1815
|
+
if (!data) {
|
|
1816
|
+
Janus.warn("Invalid data");
|
|
1817
|
+
callbacks.error("Invalid data");
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
let label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel;
|
|
1821
|
+
if (!config.dataChannel[label]) {
|
|
1822
|
+
// Create new data channel and wait for it to open
|
|
1823
|
+
createDataChannel(handleId, label, callbacks.protocol, false, data, callbacks.protocol);
|
|
1824
|
+
callbacks.success();
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
if (config.dataChannel[label].readyState !== "open") {
|
|
1828
|
+
config.dataChannel[label].pending.push(data);
|
|
1829
|
+
callbacks.success();
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
Janus.log("Sending data on data channel <" + label + ">");
|
|
1833
|
+
Janus.debug(data);
|
|
1834
|
+
config.dataChannel[label].send(data);
|
|
1835
|
+
callbacks.success();
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// Private method to send a DTMF tone
|
|
1839
|
+
function sendDtmf(handleId, callbacks) {
|
|
1840
|
+
callbacks = callbacks || {};
|
|
1841
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1842
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1843
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1844
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1845
|
+
Janus.warn("Invalid handle");
|
|
1846
|
+
callbacks.error("Invalid handle");
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
let config = pluginHandle.webrtcStuff;
|
|
1850
|
+
if (!config.dtmfSender) {
|
|
1851
|
+
// Create the DTMF sender the proper way, if possible
|
|
1852
|
+
if (config.pc) {
|
|
1853
|
+
let senders = config.pc.getSenders();
|
|
1854
|
+
let audioSender = senders.find(function (sender) {
|
|
1855
|
+
return sender.track && sender.track.kind === 'audio';
|
|
1856
|
+
});
|
|
1857
|
+
if (!audioSender) {
|
|
1858
|
+
Janus.warn("Invalid DTMF configuration (no audio track)");
|
|
1859
|
+
callbacks.error("Invalid DTMF configuration (no audio track)");
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
config.dtmfSender = audioSender.dtmf;
|
|
1863
|
+
if (config.dtmfSender) {
|
|
1864
|
+
Janus.log("Created DTMF Sender");
|
|
1865
|
+
config.dtmfSender.ontonechange = function (tone) {
|
|
1866
|
+
Janus.debug("Sent DTMF tone: " + tone.tone);
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
if (!config.dtmfSender) {
|
|
1871
|
+
Janus.warn("Invalid DTMF configuration");
|
|
1872
|
+
callbacks.error("Invalid DTMF configuration");
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
let dtmf = callbacks.dtmf;
|
|
1877
|
+
if (!dtmf) {
|
|
1878
|
+
Janus.warn("Invalid DTMF parameters");
|
|
1879
|
+
callbacks.error("Invalid DTMF parameters");
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
let tones = dtmf.tones;
|
|
1883
|
+
if (!tones) {
|
|
1884
|
+
Janus.warn("Invalid DTMF string");
|
|
1885
|
+
callbacks.error("Invalid DTMF string");
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
let duration = typeof dtmf.duration === 'number' ? dtmf.duration : 500; // We choose 500ms as the default duration for a tone
|
|
1889
|
+
let gap = typeof dtmf.gap === 'number' ? dtmf.gap : 50; // We choose 50ms as the default gap between tones
|
|
1890
|
+
Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)");
|
|
1891
|
+
config.dtmfSender.insertDTMF(tones, duration, gap);
|
|
1892
|
+
callbacks.success();
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// Private method to destroy a plugin handle
|
|
1896
|
+
function destroyHandle(handleId, callbacks) {
|
|
1897
|
+
callbacks = callbacks || {};
|
|
1898
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
1899
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
1900
|
+
let noRequest = callbacks.noRequest === true;
|
|
1901
|
+
Janus.log("Destroying handle " + handleId + " (only-locally=" + noRequest + ")");
|
|
1902
|
+
cleanupWebrtc(handleId);
|
|
1903
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1904
|
+
if (!pluginHandle || pluginHandle.detached) {
|
|
1905
|
+
// Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here
|
|
1906
|
+
pluginHandles.delete(handleId);
|
|
1907
|
+
callbacks.success();
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
pluginHandle.detached = true;
|
|
1911
|
+
if (noRequest) {
|
|
1912
|
+
// We're only removing the handle locally
|
|
1913
|
+
pluginHandles.delete(handleId);
|
|
1914
|
+
callbacks.success();
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (!connected) {
|
|
1918
|
+
Janus.warn("Is the server down? (connected=false)");
|
|
1919
|
+
callbacks.error("Is the server down? (connected=false)");
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
let request = {
|
|
1923
|
+
"janus": "detach",
|
|
1924
|
+
"transaction": Janus.randomString(12)
|
|
1925
|
+
};
|
|
1926
|
+
if (pluginHandle.token) request["token"] = pluginHandle.token;
|
|
1927
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
1928
|
+
if (websockets) {
|
|
1929
|
+
request["session_id"] = sessionId;
|
|
1930
|
+
request["handle_id"] = handleId;
|
|
1931
|
+
ws.send(JSON.stringify(request));
|
|
1932
|
+
pluginHandles.delete(handleId);
|
|
1933
|
+
callbacks.success();
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
|
|
1937
|
+
verb: 'POST',
|
|
1938
|
+
withCredentials: withCredentials,
|
|
1939
|
+
body: request,
|
|
1940
|
+
success: function (json) {
|
|
1941
|
+
Janus.log("Destroyed handle:");
|
|
1942
|
+
Janus.debug(json);
|
|
1943
|
+
if (json["janus"] !== "success") {
|
|
1944
|
+
Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME
|
|
1945
|
+
}
|
|
1946
|
+
pluginHandles.delete(handleId);
|
|
1947
|
+
callbacks.success();
|
|
1948
|
+
},
|
|
1949
|
+
error: function (textStatus, errorThrown) {
|
|
1950
|
+
Janus.error(textStatus + ":", errorThrown); // FIXME
|
|
1951
|
+
// We cleanup anyway
|
|
1952
|
+
pluginHandles.delete(handleId);
|
|
1953
|
+
callbacks.success();
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// WebRTC stuff
|
|
1959
|
+
// Helper function to create a new PeerConnection, if we need one
|
|
1960
|
+
function createPeerconnectionIfNeeded(handleId, callbacks) {
|
|
1961
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
1962
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
1963
|
+
Janus.warn("Invalid handle");
|
|
1964
|
+
throw "Invalid handle";
|
|
1965
|
+
}
|
|
1966
|
+
let config = pluginHandle.webrtcStuff;
|
|
1967
|
+
if (config.pc) {
|
|
1968
|
+
// Nothing to do, we have a PeerConnection already
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
let pc_config = {
|
|
1972
|
+
iceServers: iceServers,
|
|
1973
|
+
iceTransportPolicy: iceTransportPolicy,
|
|
1974
|
+
bundlePolicy: bundlePolicy
|
|
1975
|
+
};
|
|
1976
|
+
pc_config.sdpSemantics = 'unified-plan';
|
|
1977
|
+
// Check if a sender or receiver transform has been provided
|
|
1978
|
+
let insertableStreams = false;
|
|
1979
|
+
if (callbacks.tracks) {
|
|
1980
|
+
for (let track of callbacks.tracks) {
|
|
1981
|
+
if (track.transforms && (track.transforms.sender || track.transforms.receiver)) {
|
|
1982
|
+
insertableStreams = true;
|
|
1983
|
+
break;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (callbacks.externalEncryption) {
|
|
1988
|
+
insertableStreams = true;
|
|
1989
|
+
config.externalEncryption = true;
|
|
1990
|
+
}
|
|
1991
|
+
if (RTCRtpSender && (RTCRtpSender.prototype.createEncodedStreams || RTCRtpSender.prototype.createEncodedAudioStreams && RTCRtpSender.prototype.createEncodedVideoStreams) && insertableStreams) {
|
|
1992
|
+
config.insertableStreams = true;
|
|
1993
|
+
pc_config.forceEncodedAudioInsertableStreams = true;
|
|
1994
|
+
pc_config.forceEncodedVideoInsertableStreams = true;
|
|
1995
|
+
pc_config.encodedInsertableStreams = true;
|
|
1996
|
+
}
|
|
1997
|
+
Janus.log('Creating PeerConnection');
|
|
1998
|
+
config.pc = new RTCPeerConnection(pc_config);
|
|
1999
|
+
Janus.debug(config.pc);
|
|
2000
|
+
if (config.pc.getStats) {
|
|
2001
|
+
// FIXME
|
|
2002
|
+
config.volume = {};
|
|
2003
|
+
config.bitrate.value = '0 kbits/sec';
|
|
2004
|
+
}
|
|
2005
|
+
Janus.log('Preparing local SDP and gathering candidates (trickle=' + config.trickle + ')');
|
|
2006
|
+
config.pc.onconnectionstatechange = function () {
|
|
2007
|
+
if (config.pc) pluginHandle.connectionState(config.pc.connectionState);
|
|
2008
|
+
};
|
|
2009
|
+
config.pc.oniceconnectionstatechange = function () {
|
|
2010
|
+
if (config.pc) pluginHandle.iceState(config.pc.iceConnectionState);
|
|
2011
|
+
};
|
|
2012
|
+
config.pc.onicecandidate = function (event) {
|
|
2013
|
+
if (!event.candidate || event.candidate.candidate && event.candidate.candidate.indexOf('endOfCandidates') > 0) {
|
|
2014
|
+
Janus.log('End of candidates.');
|
|
2015
|
+
config.iceDone = true;
|
|
2016
|
+
if (config.trickle === true) {
|
|
2017
|
+
// Notify end of candidates
|
|
2018
|
+
sendTrickleCandidate(handleId, {
|
|
2019
|
+
completed: true
|
|
2020
|
+
});
|
|
2021
|
+
} else {
|
|
2022
|
+
// No trickle, time to send the complete SDP (including all candidates)
|
|
2023
|
+
sendSDP(handleId, callbacks);
|
|
2024
|
+
}
|
|
2025
|
+
} else {
|
|
2026
|
+
// JSON.stringify doesn't work on some WebRTC objects anymore
|
|
2027
|
+
// See https://code.google.com/p/chromium/issues/detail?id=467366
|
|
2028
|
+
let candidate = {
|
|
2029
|
+
candidate: event.candidate.candidate,
|
|
2030
|
+
sdpMid: event.candidate.sdpMid,
|
|
2031
|
+
sdpMLineIndex: event.candidate.sdpMLineIndex
|
|
2032
|
+
};
|
|
2033
|
+
if (config.trickle === true) {
|
|
2034
|
+
// Send candidate
|
|
2035
|
+
sendTrickleCandidate(handleId, candidate);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
};
|
|
2039
|
+
config.pc.ontrack = function (event) {
|
|
2040
|
+
Janus.log('Handling Remote Track', event);
|
|
2041
|
+
if (!event.streams) return;
|
|
2042
|
+
if (!event.track) return;
|
|
2043
|
+
// Notify about the new track event
|
|
2044
|
+
let mid = event.transceiver ? event.transceiver.mid : event.track.id;
|
|
2045
|
+
try {
|
|
2046
|
+
if (event.transceiver && event.transceiver.mid && event.track.id) {
|
|
2047
|
+
// Keep track of the mapping between track ID and mid, since
|
|
2048
|
+
// when a track is removed the transceiver may be gone already
|
|
2049
|
+
if (!pluginHandle.mids) pluginHandle.mids = {};
|
|
2050
|
+
pluginHandle.mids[event.track.id] = event.transceiver.mid;
|
|
2051
|
+
}
|
|
2052
|
+
pluginHandle.onremotetrack(event.track, mid, true, {
|
|
2053
|
+
reason: 'created'
|
|
2054
|
+
});
|
|
2055
|
+
} catch (e) {
|
|
2056
|
+
Janus.error("Error calling onremotetrack", e);
|
|
2057
|
+
}
|
|
2058
|
+
if (event.track.onended) return;
|
|
2059
|
+
let trackMutedTimeoutId = null;
|
|
2060
|
+
Janus.log('Adding onended callback to track:', event.track);
|
|
2061
|
+
event.track.onended = function (ev) {
|
|
2062
|
+
Janus.log('Remote track removed:', ev);
|
|
2063
|
+
clearTimeout(trackMutedTimeoutId);
|
|
2064
|
+
// Notify the application
|
|
2065
|
+
let transceivers = config.pc ? config.pc.getTransceivers() : null;
|
|
2066
|
+
let transceiver = transceivers ? transceivers.find(t => t.receiver.track === ev.target) : null;
|
|
2067
|
+
let mid = transceiver ? transceiver.mid : ev.target.id;
|
|
2068
|
+
if (mid === ev.target.id && pluginHandle.mids && pluginHandle.mids[event.track.id]) mid = pluginHandle.mids[event.track.id];
|
|
2069
|
+
try {
|
|
2070
|
+
pluginHandle.onremotetrack(ev.target, mid, false, {
|
|
2071
|
+
reason: 'ended'
|
|
2072
|
+
});
|
|
2073
|
+
} catch (e) {
|
|
2074
|
+
Janus.error("Error calling onremotetrack on removal", e);
|
|
2075
|
+
}
|
|
2076
|
+
delete pluginHandle.mids[event.track.id];
|
|
2077
|
+
};
|
|
2078
|
+
event.track.onmute = function (ev) {
|
|
2079
|
+
Janus.log('Remote track muted:', ev);
|
|
2080
|
+
if (!trackMutedTimeoutId) {
|
|
2081
|
+
trackMutedTimeoutId = setTimeout(function () {
|
|
2082
|
+
Janus.log('Removing remote track');
|
|
2083
|
+
// Notify the application the track is gone
|
|
2084
|
+
let transceivers = config.pc ? config.pc.getTransceivers() : null;
|
|
2085
|
+
let transceiver = transceivers ? transceivers.find(t => t.receiver.track === ev.target) : null;
|
|
2086
|
+
let mid = transceiver ? transceiver.mid : ev.target.id;
|
|
2087
|
+
if (mid === ev.target.id && pluginHandle.mids && pluginHandle.mids[event.track.id]) mid = pluginHandle.mids[event.track.id];
|
|
2088
|
+
try {
|
|
2089
|
+
pluginHandle.onremotetrack(ev.target, mid, false, {
|
|
2090
|
+
reason: 'mute'
|
|
2091
|
+
});
|
|
2092
|
+
} catch (e) {
|
|
2093
|
+
Janus.error("Error calling onremotetrack on mute", e);
|
|
2094
|
+
}
|
|
2095
|
+
trackMutedTimeoutId = null;
|
|
2096
|
+
// Chrome seems to raise mute events only at multiples of 834ms;
|
|
2097
|
+
// we set the timeout to three times this value (rounded to 840ms)
|
|
2098
|
+
}, 3 * 840);
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
2101
|
+
event.track.onunmute = function (ev) {
|
|
2102
|
+
Janus.log('Remote track flowing again:', ev);
|
|
2103
|
+
if (trackMutedTimeoutId != null) {
|
|
2104
|
+
clearTimeout(trackMutedTimeoutId);
|
|
2105
|
+
trackMutedTimeoutId = null;
|
|
2106
|
+
} else {
|
|
2107
|
+
try {
|
|
2108
|
+
// Notify the application the track is back
|
|
2109
|
+
let transceivers = config.pc ? config.pc.getTransceivers() : null;
|
|
2110
|
+
let transceiver = transceivers ? transceivers.find(t => t.receiver.track === ev.target) : null;
|
|
2111
|
+
let mid = transceiver ? transceiver.mid : ev.target.id;
|
|
2112
|
+
pluginHandle.onremotetrack(ev.target, mid, true, {
|
|
2113
|
+
reason: 'unmute'
|
|
2114
|
+
});
|
|
2115
|
+
} catch (e) {
|
|
2116
|
+
Janus.error("Error calling onremotetrack on unmute", e);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// Helper function used when creating either an offer or answer: it
|
|
2124
|
+
// prepares what needs to be prepared, including creating a new
|
|
2125
|
+
// PeerConnection (if needed) and updating the tracks configuration,
|
|
2126
|
+
// before invoking the function to actually generate the offer/answer
|
|
2127
|
+
async function prepareWebrtc(handleId, offer, callbacks) {
|
|
2128
|
+
callbacks = callbacks || {};
|
|
2129
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
2130
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : webrtcError;
|
|
2131
|
+
let jsep = callbacks.jsep;
|
|
2132
|
+
if (offer && jsep) {
|
|
2133
|
+
Janus.error("Provided a JSEP to a createOffer");
|
|
2134
|
+
callbacks.error("Provided a JSEP to a createOffer");
|
|
2135
|
+
return;
|
|
2136
|
+
} else if (!offer && (!jsep || !jsep.type || !jsep.sdp)) {
|
|
2137
|
+
Janus.error("A valid JSEP is required for createAnswer");
|
|
2138
|
+
callbacks.error("A valid JSEP is required for createAnswer");
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
// If the deprecated media was provided instead of tracks, translate it
|
|
2142
|
+
if (callbacks.media && !callbacks.tracks) {
|
|
2143
|
+
callbacks.tracks = Janus.mediaToTracks(callbacks.media);
|
|
2144
|
+
if (callbacks.simulcast === true || callbacks.simulcast2 === true || callbacks.svc) {
|
|
2145
|
+
// Find the video track and add simulcast/SVC info there
|
|
2146
|
+
for (let track of callbacks.tracks) {
|
|
2147
|
+
if (track.type === 'video') {
|
|
2148
|
+
if (callbacks.simulcast === true || callbacks.simulcast2 === true) track.simulcast = true;else if (callbacks.svc) track.svc = callbacks.svc;
|
|
2149
|
+
break;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
Janus.warn('Deprecated media object passed, use tracks instead. Automatically translated to:', callbacks.tracks);
|
|
2154
|
+
}
|
|
2155
|
+
// Check that callbacks.array is a valid array
|
|
2156
|
+
if (callbacks.tracks && !Array.isArray(callbacks.tracks)) {
|
|
2157
|
+
Janus.error("Tracks must be an array");
|
|
2158
|
+
callbacks.error("Tracks must be an array");
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
// Get the plugin handle
|
|
2162
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2163
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2164
|
+
Janus.warn("Invalid handle");
|
|
2165
|
+
callbacks.error("Invalid handle");
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
let config = pluginHandle.webrtcStuff;
|
|
2169
|
+
config.trickle = isTrickleEnabled(callbacks.trickle);
|
|
2170
|
+
try {
|
|
2171
|
+
// Create a PeerConnection, if needed
|
|
2172
|
+
createPeerconnectionIfNeeded(handleId, callbacks);
|
|
2173
|
+
if (offer) {
|
|
2174
|
+
// Capture devices and setup tracks, if needed
|
|
2175
|
+
await captureDevices(handleId, callbacks);
|
|
2176
|
+
}
|
|
2177
|
+
// Create offer or answer now (depending on the context)
|
|
2178
|
+
if (!jsep) {
|
|
2179
|
+
let offer = await createOffer(handleId, callbacks);
|
|
2180
|
+
callbacks.success(offer);
|
|
2181
|
+
} else {
|
|
2182
|
+
await config.pc.setRemoteDescription(jsep);
|
|
2183
|
+
Janus.log("Remote description accepted!");
|
|
2184
|
+
config.remoteSdp = jsep.sdp;
|
|
2185
|
+
// Any trickle candidate we cached?
|
|
2186
|
+
if (config.candidates && config.candidates.length > 0) {
|
|
2187
|
+
for (let i = 0; i < config.candidates.length; i++) {
|
|
2188
|
+
let candidate = config.candidates[i];
|
|
2189
|
+
Janus.debug("Adding remote candidate:", candidate);
|
|
2190
|
+
if (!candidate || candidate.completed === true) {
|
|
2191
|
+
// end-of-candidates
|
|
2192
|
+
config.pc.addIceCandidate(Janus.endOfCandidates);
|
|
2193
|
+
} else {
|
|
2194
|
+
// New candidate
|
|
2195
|
+
config.pc.addIceCandidate(candidate);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
config.candidates = [];
|
|
2199
|
+
}
|
|
2200
|
+
// Capture devices and setup tracks, if needed
|
|
2201
|
+
await captureDevices(handleId, callbacks);
|
|
2202
|
+
// Create the answer now
|
|
2203
|
+
let answer = await createAnswer(handleId, callbacks);
|
|
2204
|
+
callbacks.success(answer);
|
|
2205
|
+
}
|
|
2206
|
+
} catch (err) {
|
|
2207
|
+
Janus.error(err);
|
|
2208
|
+
callbacks.error(err);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
function prepareWebrtcPeer(handleId, callbacks) {
|
|
2212
|
+
callbacks = callbacks || {};
|
|
2213
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
2214
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : webrtcError;
|
|
2215
|
+
callbacks.customizeSdp = typeof callbacks.customizeSdp == "function" ? callbacks.customizeSdp : Janus.noop;
|
|
2216
|
+
let jsep = callbacks.jsep;
|
|
2217
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2218
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2219
|
+
Janus.warn("Invalid handle");
|
|
2220
|
+
callbacks.error("Invalid handle");
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
let config = pluginHandle.webrtcStuff;
|
|
2224
|
+
if (jsep) {
|
|
2225
|
+
if (!config.pc) {
|
|
2226
|
+
Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep");
|
|
2227
|
+
callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep");
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
callbacks.customizeSdp(jsep);
|
|
2231
|
+
config.pc.setRemoteDescription(jsep).then(function () {
|
|
2232
|
+
Janus.log("Remote description accepted!");
|
|
2233
|
+
config.remoteSdp = jsep.sdp;
|
|
2234
|
+
// Any trickle candidate we cached?
|
|
2235
|
+
if (config.candidates && config.candidates.length > 0) {
|
|
2236
|
+
for (let i = 0; i < config.candidates.length; i++) {
|
|
2237
|
+
let candidate = config.candidates[i];
|
|
2238
|
+
Janus.debug("Adding remote candidate:", candidate);
|
|
2239
|
+
if (!candidate || candidate.completed === true) {
|
|
2240
|
+
// end-of-candidates
|
|
2241
|
+
config.pc.addIceCandidate(Janus.endOfCandidates);
|
|
2242
|
+
} else {
|
|
2243
|
+
// New candidate
|
|
2244
|
+
config.pc.addIceCandidate(candidate);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
config.candidates = [];
|
|
2248
|
+
}
|
|
2249
|
+
// Done
|
|
2250
|
+
callbacks.success();
|
|
2251
|
+
}, callbacks.error);
|
|
2252
|
+
} else {
|
|
2253
|
+
callbacks.error("Invalid JSEP");
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
async function createOffer(handleId, callbacks) {
|
|
2257
|
+
callbacks = callbacks || {};
|
|
2258
|
+
callbacks.customizeSdp = typeof callbacks.customizeSdp == "function" ? callbacks.customizeSdp : Janus.noop;
|
|
2259
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2260
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2261
|
+
Janus.warn("Invalid handle");
|
|
2262
|
+
throw "Invalid handle";
|
|
2263
|
+
}
|
|
2264
|
+
let config = pluginHandle.webrtcStuff;
|
|
2265
|
+
Janus.log("Creating offer (iceDone=" + config.iceDone + ")");
|
|
2266
|
+
// https://code.google.com/p/webrtc/issues/detail?id=3508
|
|
2267
|
+
let mediaConstraints = {};
|
|
2268
|
+
let iceRestart = callbacks.iceRestart === true;
|
|
2269
|
+
// If we need an ICE restart, set the related constraint
|
|
2270
|
+
if (iceRestart) mediaConstraints.iceRestart = true;
|
|
2271
|
+
Janus.debug(mediaConstraints);
|
|
2272
|
+
let offer = await config.pc.createOffer(mediaConstraints);
|
|
2273
|
+
Janus.debug(offer);
|
|
2274
|
+
// JSON.stringify doesn't work on some WebRTC objects anymore
|
|
2275
|
+
// See https://code.google.com/p/chromium/issues/detail?id=467366
|
|
2276
|
+
let jsep = {
|
|
2277
|
+
type: 'offer',
|
|
2278
|
+
sdp: offer.sdp
|
|
2279
|
+
};
|
|
2280
|
+
callbacks.customizeSdp(jsep);
|
|
2281
|
+
offer.sdp = jsep.sdp;
|
|
2282
|
+
Janus.log("Setting local description");
|
|
2283
|
+
config.mySdp = {
|
|
2284
|
+
type: 'offer',
|
|
2285
|
+
sdp: offer.sdp
|
|
2286
|
+
};
|
|
2287
|
+
await config.pc.setLocalDescription(offer);
|
|
2288
|
+
config.mediaConstraints = mediaConstraints;
|
|
2289
|
+
if (!config.iceDone && !config.trickle) {
|
|
2290
|
+
// FIXME Don't do anything until we have all candidates
|
|
2291
|
+
Janus.log("Waiting for all candidates...");
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
// If transforms are present, notify Janus that the media is end-to-end encrypted
|
|
2295
|
+
if (config.insertableStreams || config.externalEncryption) offer.e2ee = true;
|
|
2296
|
+
return offer;
|
|
2297
|
+
}
|
|
2298
|
+
async function createAnswer(handleId, callbacks) {
|
|
2299
|
+
callbacks = callbacks || {};
|
|
2300
|
+
callbacks.customizeSdp = typeof callbacks.customizeSdp == "function" ? callbacks.customizeSdp : Janus.noop;
|
|
2301
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2302
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2303
|
+
Janus.warn("Invalid handle");
|
|
2304
|
+
throw "Invalid handle";
|
|
2305
|
+
}
|
|
2306
|
+
let config = pluginHandle.webrtcStuff;
|
|
2307
|
+
Janus.log("Creating answer (iceDone=" + config.iceDone + ")");
|
|
2308
|
+
let answer = await config.pc.createAnswer();
|
|
2309
|
+
Janus.debug(answer);
|
|
2310
|
+
// JSON.stringify doesn't work on some WebRTC objects anymore
|
|
2311
|
+
// See https://code.google.com/p/chromium/issues/detail?id=467366
|
|
2312
|
+
let jsep = {
|
|
2313
|
+
type: 'answer',
|
|
2314
|
+
sdp: answer.sdp
|
|
2315
|
+
};
|
|
2316
|
+
callbacks.customizeSdp(jsep);
|
|
2317
|
+
answer.sdp = jsep.sdp;
|
|
2318
|
+
Janus.log("Setting local description");
|
|
2319
|
+
config.mySdp = {
|
|
2320
|
+
type: 'answer',
|
|
2321
|
+
sdp: answer.sdp
|
|
2322
|
+
};
|
|
2323
|
+
await config.pc.setLocalDescription(answer);
|
|
2324
|
+
if (!config.iceDone && !config.trickle) {
|
|
2325
|
+
// FIXME Don't do anything until we have all candidates
|
|
2326
|
+
Janus.log("Waiting for all candidates...");
|
|
2327
|
+
return null;
|
|
2328
|
+
}
|
|
2329
|
+
// If transforms are present, notify Janus that the media is end-to-end encrypted
|
|
2330
|
+
if (config.insertableStreams || config.externalEncryption) answer.e2ee = true;
|
|
2331
|
+
return answer;
|
|
2332
|
+
}
|
|
2333
|
+
function sendSDP(handleId, callbacks) {
|
|
2334
|
+
callbacks = callbacks || {};
|
|
2335
|
+
callbacks.success = typeof callbacks.success == "function" ? callbacks.success : Janus.noop;
|
|
2336
|
+
callbacks.error = typeof callbacks.error == "function" ? callbacks.error : Janus.noop;
|
|
2337
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2338
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2339
|
+
Janus.warn("Invalid handle, not sending anything");
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
let config = pluginHandle.webrtcStuff;
|
|
2343
|
+
Janus.log("Sending offer/answer SDP...");
|
|
2344
|
+
if (!config.mySdp) {
|
|
2345
|
+
Janus.warn("Local SDP instance is invalid, not sending anything...");
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
config.mySdp = {
|
|
2349
|
+
type: config.pc.localDescription.type,
|
|
2350
|
+
sdp: config.pc.localDescription.sdp
|
|
2351
|
+
};
|
|
2352
|
+
if (config.trickle === false) config.mySdp["trickle"] = false;
|
|
2353
|
+
Janus.debug(callbacks);
|
|
2354
|
+
config.sdpSent = true;
|
|
2355
|
+
callbacks.success(config.mySdp);
|
|
2356
|
+
}
|
|
2357
|
+
async function replaceTracks(handleId, callbacks) {
|
|
2358
|
+
callbacks = callbacks || {};
|
|
2359
|
+
callbacks.success = typeof callbacks.success == 'function' ? callbacks.success : Janus.noop;
|
|
2360
|
+
callbacks.error = typeof callbacks.error == 'function' ? callbacks.error : Janus.noop;
|
|
2361
|
+
// Check that callbacks.array is a valid array
|
|
2362
|
+
if (callbacks.tracks && !Array.isArray(callbacks.tracks)) {
|
|
2363
|
+
Janus.error('Tracks must be an array');
|
|
2364
|
+
callbacks.error('Tracks must be an array');
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
// Add the replace:true if it's missing
|
|
2368
|
+
for (let track of callbacks.tracks) {
|
|
2369
|
+
if (track.add || !track.replace && !track.remove) track.replace = true;
|
|
2370
|
+
}
|
|
2371
|
+
try {
|
|
2372
|
+
await captureDevices(handleId, callbacks);
|
|
2373
|
+
callbacks.success();
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
Janus.error(err);
|
|
2376
|
+
callbacks.error(err);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
async function captureDevices(handleId, callbacks) {
|
|
2380
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2381
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2382
|
+
Janus.warn('Invalid handle, not sending anything');
|
|
2383
|
+
throw 'Invalid handle';
|
|
2384
|
+
}
|
|
2385
|
+
let config = pluginHandle.webrtcStuff;
|
|
2386
|
+
if (!config.pc) {
|
|
2387
|
+
Janus.warn('Invalid PeerConnection');
|
|
2388
|
+
throw 'Invalid PeerConnection';
|
|
2389
|
+
}
|
|
2390
|
+
let tracks = callbacks.tracks;
|
|
2391
|
+
if (!tracks || !Array.isArray(tracks) || tracks.length === 0) {
|
|
2392
|
+
// Nothing to do
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
let openedConsentDialog = false;
|
|
2396
|
+
// Check if we can/should group getUserMedia calls
|
|
2397
|
+
let groups = {};
|
|
2398
|
+
for (let track of tracks) {
|
|
2399
|
+
delete track.gumGroup;
|
|
2400
|
+
if (!track.type || !['audio', 'video'].includes(track.type)) continue;
|
|
2401
|
+
if (!track.capture || track.capture instanceof MediaStreamTrack) continue;
|
|
2402
|
+
let group = track.group ? track.group : 'default';
|
|
2403
|
+
if (!groups[group]) groups[group] = {};
|
|
2404
|
+
if (groups[group][track.type]) continue;
|
|
2405
|
+
track.gumGroup = group;
|
|
2406
|
+
groups[group][track.type] = track;
|
|
2407
|
+
}
|
|
2408
|
+
let keys = Object.keys(groups);
|
|
2409
|
+
for (let key of keys) {
|
|
2410
|
+
let group = groups[key];
|
|
2411
|
+
if (!group.audio || !group.video) {
|
|
2412
|
+
if (group.audio) delete group.audio.gumGroup;
|
|
2413
|
+
if (group.video) delete group.video.gumGroup;
|
|
2414
|
+
delete groups[key];
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
let answer = callbacks.jsep ? true : false;
|
|
2418
|
+
for (let track of tracks) {
|
|
2419
|
+
if (!track.type) {
|
|
2420
|
+
Janus.warn('Missing track type:', track);
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
if (track.type === 'data') {
|
|
2424
|
+
// Easy enough: create a datachannel if we don't have one already
|
|
2425
|
+
if (config.pc.ondatachannel) {
|
|
2426
|
+
Janus.warn('Data channel exists already, not creating another one');
|
|
2427
|
+
continue;
|
|
2428
|
+
}
|
|
2429
|
+
Janus.log('Creating default data channel');
|
|
2430
|
+
createDataChannel(handleId, Janus.dataChanDefaultLabel, null, false);
|
|
2431
|
+
config.pc.ondatachannel = function (event) {
|
|
2432
|
+
Janus.log('Data channel created by Janus:', event);
|
|
2433
|
+
createDataChannel(handleId, event.channel.label, event.channel.protocol, event.channel);
|
|
2434
|
+
};
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
if ((typeof track.add === 'undefined' || track.add === null) && (typeof track.remove === 'undefined' || track.remove === null) && (typeof track.replace === 'undefined' || track.replace === null)) {
|
|
2438
|
+
// Let's default to 'add'
|
|
2439
|
+
track.add = true;
|
|
2440
|
+
}
|
|
2441
|
+
if (track.add && track.remove || track.add && track.remove && track.replace) {
|
|
2442
|
+
Janus.warn('Conflicting actions for track, ignoring:', track);
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
if (track.add && track.replace) {
|
|
2446
|
+
Janus.warn('Both add and replace provided, falling back to replace:', track);
|
|
2447
|
+
delete track.add;
|
|
2448
|
+
} else if (track.remove && track.replace) {
|
|
2449
|
+
Janus.warn('Both remove and replace provided, falling back to remove:', track);
|
|
2450
|
+
delete track.replace;
|
|
2451
|
+
}
|
|
2452
|
+
let kind = track.type;
|
|
2453
|
+
if (track.type === 'screen') kind = 'video'; // FIXME
|
|
2454
|
+
let transceiver = null,
|
|
2455
|
+
sender = null;
|
|
2456
|
+
if (track.mid) {
|
|
2457
|
+
// Search by mid
|
|
2458
|
+
transceiver = config.pc.getTransceivers().find(t => t.mid === track.mid && t.receiver.track.kind === kind);
|
|
2459
|
+
} else if (!track.add) {
|
|
2460
|
+
// Find the first track of this type
|
|
2461
|
+
transceiver = config.pc.getTransceivers().find(t => t.receiver.track.kind === kind);
|
|
2462
|
+
}
|
|
2463
|
+
if (track.replace || track.remove) {
|
|
2464
|
+
if (!transceiver) {
|
|
2465
|
+
Janus.warn("Couldn't find a transceiver for track:", track);
|
|
2466
|
+
continue;
|
|
2467
|
+
}
|
|
2468
|
+
if (!transceiver.sender) {
|
|
2469
|
+
Janus.warn('No sender in the transceiver for track:', track);
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
2472
|
+
sender = transceiver.sender;
|
|
2473
|
+
}
|
|
2474
|
+
if (answer && !transceiver) {
|
|
2475
|
+
transceiver = config.pc.getTransceivers().find(t => t.receiver.track.kind === kind);
|
|
2476
|
+
if (!transceiver) {
|
|
2477
|
+
Janus.warn("Couldn't find a transceiver for track:", track);
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
// Capture the new track, if we need to
|
|
2482
|
+
let nt = null,
|
|
2483
|
+
trackId = null;
|
|
2484
|
+
if (track.remove || track.replace) {
|
|
2485
|
+
Janus.log('Removing track from PeerConnection', track);
|
|
2486
|
+
trackId = sender.track ? sender.track.id : null;
|
|
2487
|
+
await sender.replaceTrack(null);
|
|
2488
|
+
// Get rid of the old track
|
|
2489
|
+
if (trackId && config.myStream) {
|
|
2490
|
+
let rt = null;
|
|
2491
|
+
if (kind === 'audio' && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {
|
|
2492
|
+
for (let t of config.myStream.getAudioTracks()) {
|
|
2493
|
+
if (t.id === trackId) {
|
|
2494
|
+
rt = t;
|
|
2495
|
+
Janus.log('Removing audio track:', rt);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
} else if (kind === 'video' && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {
|
|
2499
|
+
for (let t of config.myStream.getVideoTracks()) {
|
|
2500
|
+
if (t.id === trackId) {
|
|
2501
|
+
rt = t;
|
|
2502
|
+
Janus.log('Removing video track:', rt);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
if (rt) {
|
|
2507
|
+
// Remove the track and notify the application
|
|
2508
|
+
try {
|
|
2509
|
+
config.myStream.removeTrack(rt);
|
|
2510
|
+
pluginHandle.onlocaltrack(rt, false);
|
|
2511
|
+
} catch (e) {
|
|
2512
|
+
Janus.error("Error calling onlocaltrack on removal for renegotiation", e);
|
|
2513
|
+
}
|
|
2514
|
+
// Close the old track (unless we've been asked not to)
|
|
2515
|
+
if (rt.dontStop !== true) {
|
|
2516
|
+
try {
|
|
2517
|
+
rt.stop();
|
|
2518
|
+
// eslint-disable-next-line no-unused-vars
|
|
2519
|
+
} catch (e) {}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
if (track.capture) {
|
|
2525
|
+
if (track.gumGroup && groups[track.gumGroup] && groups[track.gumGroup].stream) {
|
|
2526
|
+
// We did a getUserMedia before already
|
|
2527
|
+
let stream = groups[track.gumGroup].stream;
|
|
2528
|
+
nt = track.type === 'audio' ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
|
|
2529
|
+
delete groups[track.gumGroup].stream;
|
|
2530
|
+
delete groups[track.gumGroup];
|
|
2531
|
+
delete track.gumGroup;
|
|
2532
|
+
} else if (track.capture instanceof MediaStreamTrack) {
|
|
2533
|
+
// An external track was provided, use that
|
|
2534
|
+
nt = track.capture;
|
|
2535
|
+
} else {
|
|
2536
|
+
if (!openedConsentDialog) {
|
|
2537
|
+
openedConsentDialog = true;
|
|
2538
|
+
pluginHandle.consentDialog(true);
|
|
2539
|
+
}
|
|
2540
|
+
let constraints = Janus.trackConstraints(track),
|
|
2541
|
+
stream = null;
|
|
2542
|
+
if (track.type === 'audio' || track.type === 'video') {
|
|
2543
|
+
// Use getUserMedia: check if we need to group audio and video together
|
|
2544
|
+
if (track.gumGroup) {
|
|
2545
|
+
let otherType = track.type === 'audio' ? 'video' : 'audio';
|
|
2546
|
+
if (groups[track.gumGroup] && groups[track.gumGroup][otherType]) {
|
|
2547
|
+
let otherTrack = groups[track.gumGroup][otherType];
|
|
2548
|
+
let otherConstraints = Janus.trackConstraints(otherTrack);
|
|
2549
|
+
constraints[otherType] = otherConstraints[otherType];
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
2553
|
+
if (track.gumGroup && constraints.audio && constraints.video) {
|
|
2554
|
+
// We just performed a grouped getUserMedia, keep track of the
|
|
2555
|
+
// stream so that we can immediately assign the track later
|
|
2556
|
+
groups[track.gumGroup].stream = stream;
|
|
2557
|
+
delete track.gumGroup;
|
|
2558
|
+
}
|
|
2559
|
+
} else {
|
|
2560
|
+
// Use getDisplayMedia
|
|
2561
|
+
stream = await navigator.mediaDevices.getDisplayMedia(constraints);
|
|
2562
|
+
}
|
|
2563
|
+
nt = track.type === 'audio' ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
|
|
2564
|
+
}
|
|
2565
|
+
if (track.replace) {
|
|
2566
|
+
// Replace the track
|
|
2567
|
+
await sender.replaceTrack(nt);
|
|
2568
|
+
// Update the transceiver direction
|
|
2569
|
+
let newDirection = 'sendrecv';
|
|
2570
|
+
if (track.recv === false || transceiver.direction === 'inactive' || transceiver.direction === 'sendonly') newDirection = 'sendonly';
|
|
2571
|
+
if (transceiver.setDirection) transceiver.setDirection(newDirection);else transceiver.direction = newDirection;
|
|
2572
|
+
} else {
|
|
2573
|
+
// FIXME Add as a new track
|
|
2574
|
+
if (!config.myStream) config.myStream = new MediaStream();
|
|
2575
|
+
if (kind === 'audio' || !track.simulcast && !track.svc) {
|
|
2576
|
+
sender = config.pc.addTrack(nt, config.myStream);
|
|
2577
|
+
transceiver = config.pc.getTransceivers().find(t => t.sender === sender);
|
|
2578
|
+
} else if (track.simulcast) {
|
|
2579
|
+
if (Janus.webRTCAdapter.browserDetails.browser !== 'firefox') {
|
|
2580
|
+
// Standard RID
|
|
2581
|
+
Janus.log('Enabling rid-based simulcasting:', nt);
|
|
2582
|
+
let maxBitrates = getMaxBitrates(track.simulcastMaxBitrates);
|
|
2583
|
+
transceiver = config.pc.addTransceiver(nt, {
|
|
2584
|
+
direction: 'sendrecv',
|
|
2585
|
+
streams: [config.myStream],
|
|
2586
|
+
sendEncodings: track.sendEncodings || [{
|
|
2587
|
+
rid: 'h',
|
|
2588
|
+
active: true,
|
|
2589
|
+
scalabilityMode: 'L1T2',
|
|
2590
|
+
maxBitrate: maxBitrates.high
|
|
2591
|
+
}, {
|
|
2592
|
+
rid: 'm',
|
|
2593
|
+
active: true,
|
|
2594
|
+
scalabilityMode: 'L1T2',
|
|
2595
|
+
maxBitrate: maxBitrates.medium,
|
|
2596
|
+
scaleResolutionDownBy: 2
|
|
2597
|
+
}, {
|
|
2598
|
+
rid: 'l',
|
|
2599
|
+
active: true,
|
|
2600
|
+
scalabilityMode: 'L1T2',
|
|
2601
|
+
maxBitrate: maxBitrates.low,
|
|
2602
|
+
scaleResolutionDownBy: 4
|
|
2603
|
+
}]
|
|
2604
|
+
});
|
|
2605
|
+
} else {
|
|
2606
|
+
// Firefox-based RID, based on https://gist.github.com/voluntas/088bc3cc62094730647b
|
|
2607
|
+
Janus.log('Enabling Simulcasting for Firefox (RID)');
|
|
2608
|
+
transceiver = config.pc.addTransceiver(nt, {
|
|
2609
|
+
direction: 'sendrecv',
|
|
2610
|
+
streams: [config.myStream]
|
|
2611
|
+
});
|
|
2612
|
+
sender = transceiver ? transceiver.sender : null;
|
|
2613
|
+
if (sender) {
|
|
2614
|
+
let parameters = sender.getParameters();
|
|
2615
|
+
if (!parameters) parameters = {};
|
|
2616
|
+
let maxBitrates = getMaxBitrates(track.simulcastMaxBitrates);
|
|
2617
|
+
parameters.encodings = track.sendEncodings || [{
|
|
2618
|
+
rid: 'h',
|
|
2619
|
+
active: true,
|
|
2620
|
+
maxBitrate: maxBitrates.high
|
|
2621
|
+
}, {
|
|
2622
|
+
rid: 'm',
|
|
2623
|
+
active: true,
|
|
2624
|
+
maxBitrate: maxBitrates.medium,
|
|
2625
|
+
scaleResolutionDownBy: 2
|
|
2626
|
+
}, {
|
|
2627
|
+
rid: 'l',
|
|
2628
|
+
active: true,
|
|
2629
|
+
maxBitrate: maxBitrates.low,
|
|
2630
|
+
scaleResolutionDownBy: 4
|
|
2631
|
+
}];
|
|
2632
|
+
sender.setParameters(parameters);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
} else {
|
|
2636
|
+
Janus.log('Enabling SVC (' + track.svc + '):', nt);
|
|
2637
|
+
transceiver = config.pc.addTransceiver(nt, {
|
|
2638
|
+
direction: 'sendrecv',
|
|
2639
|
+
streams: [config.myStream],
|
|
2640
|
+
sendEncodings: [{
|
|
2641
|
+
scalabilityMode: track.svc
|
|
2642
|
+
}]
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
if (!sender) sender = transceiver ? transceiver.sender : null;
|
|
2646
|
+
// Check if we need to override some settings
|
|
2647
|
+
if (track.codec) {
|
|
2648
|
+
if (Janus.webRTCAdapter.browserDetails.browser === 'firefox') {
|
|
2649
|
+
Janus.warn('setCodecPreferences not supported in Firefox, ignoring codec for track:', track);
|
|
2650
|
+
} else if (typeof track.codec !== 'string') {
|
|
2651
|
+
Janus.warn('Invalid codec value, ignoring for track:', track);
|
|
2652
|
+
} else {
|
|
2653
|
+
let mimeType = kind + '/' + track.codec.toLowerCase();
|
|
2654
|
+
let codecs = RTCRtpReceiver.getCapabilities(kind).codecs.filter(function (codec) {
|
|
2655
|
+
return codec.mimeType.toLowerCase() === mimeType;
|
|
2656
|
+
});
|
|
2657
|
+
if (!codecs || codecs.length === 0) {
|
|
2658
|
+
Janus.warn('Codec not supported in this browser for this track, ignoring:', track);
|
|
2659
|
+
} else if (transceiver) {
|
|
2660
|
+
try {
|
|
2661
|
+
transceiver.setCodecPreferences(codecs);
|
|
2662
|
+
} catch (err) {
|
|
2663
|
+
Janus.warn('Failed enforcing codec for this ' + kind + ' track:', err);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
if (track.bitrate) {
|
|
2669
|
+
// Override maximum bitrate
|
|
2670
|
+
if (track.simulcast || track.svc) {
|
|
2671
|
+
Janus.warn('Ignoring bitrate for simulcast/SVC track, use sendEncodings for that');
|
|
2672
|
+
} else if (isNaN(track.bitrate) || track.bitrate < 0) {
|
|
2673
|
+
Janus.warn('Ignoring invalid bitrate for track:', track);
|
|
2674
|
+
} else if (sender) {
|
|
2675
|
+
let params = sender.getParameters();
|
|
2676
|
+
if (!params || !params.encodings || params.encodings.length === 0) {
|
|
2677
|
+
Janus.warn('No encodings in the sender parameters, ignoring bitrate for track:', track);
|
|
2678
|
+
} else {
|
|
2679
|
+
params.encodings[0].maxBitrate = track.bitrate;
|
|
2680
|
+
await sender.setParameters(params);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
if (kind === 'video' && track.framerate) {
|
|
2685
|
+
// Override maximum framerate
|
|
2686
|
+
if (track.simulcast || track.svc) {
|
|
2687
|
+
Janus.warn('Ignoring framerate for simulcast/SVC track, use sendEncodings for that');
|
|
2688
|
+
} else if (isNaN(track.framerate) || track.framerate < 0) {
|
|
2689
|
+
Janus.warn('Ignoring invalid framerate for track:', track);
|
|
2690
|
+
} else if (sender) {
|
|
2691
|
+
let params = sender.getParameters();
|
|
2692
|
+
if (!params || !params.encodings || params.encodings.length === 0) {
|
|
2693
|
+
Janus.warn('No encodings in the sender parameters, ignoring framerate for track:', track);
|
|
2694
|
+
} else {
|
|
2695
|
+
params.encodings[0].maxFramerate = track.framerate;
|
|
2696
|
+
await sender.setParameters(params);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
// Check if insertable streams are involved
|
|
2701
|
+
if (track.transforms) {
|
|
2702
|
+
if (sender && track.transforms.sender) {
|
|
2703
|
+
// There's a sender transform, set it on the transceiver sender
|
|
2704
|
+
let senderStreams = null;
|
|
2705
|
+
if (RTCRtpSender.prototype.createEncodedStreams) {
|
|
2706
|
+
senderStreams = sender.createEncodedStreams();
|
|
2707
|
+
} else if (RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) {
|
|
2708
|
+
if (kind === 'audio') {
|
|
2709
|
+
senderStreams = sender.createEncodedAudioStreams();
|
|
2710
|
+
} else if (kind === 'video') {
|
|
2711
|
+
senderStreams = sender.createEncodedVideoStreams();
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
if (senderStreams) {
|
|
2715
|
+
if (senderStreams.readableStream && senderStreams.writableStream) {
|
|
2716
|
+
senderStreams.readableStream.pipeThrough(track.transforms.sender).pipeTo(senderStreams.writableStream);
|
|
2717
|
+
} else if (senderStreams.readable && senderStreams.writable) {
|
|
2718
|
+
senderStreams.readable.pipeThrough(track.transforms.sender).pipeTo(senderStreams.writable);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (transceiver && transceiver.receiver && track.transforms.receiver) {
|
|
2723
|
+
// There's a receiver transform, set it on the transceiver receiver
|
|
2724
|
+
let receiverStreams = null;
|
|
2725
|
+
if (RTCRtpReceiver.prototype.createEncodedStreams) {
|
|
2726
|
+
receiverStreams = transceiver.receiver.createEncodedStreams();
|
|
2727
|
+
} else if (RTCRtpReceiver.prototype.createAudioEncodedStreams || RTCRtpReceiver.prototype.createEncodedVideoStreams) {
|
|
2728
|
+
if (kind === 'audio') {
|
|
2729
|
+
receiverStreams = transceiver.receiver.createEncodedAudioStreams();
|
|
2730
|
+
} else if (kind === 'video') {
|
|
2731
|
+
receiverStreams = transceiver.receiver.createEncodedVideoStreams();
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
if (receiverStreams) {
|
|
2735
|
+
if (receiverStreams.readableStream && receiverStreams.writableStream) {
|
|
2736
|
+
receiverStreams.readableStream.pipeThrough(track.transforms.receiver).pipeTo(receiverStreams.writableStream);
|
|
2737
|
+
} else if (receiverStreams.readable && receiverStreams.writable) {
|
|
2738
|
+
receiverStreams.readable.pipeThrough(track.transforms.receiver).pipeTo(receiverStreams.writable);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
if (nt && track.dontStop === true) nt.dontStop = true;
|
|
2745
|
+
} else if (track.recv) {
|
|
2746
|
+
// Maybe a new recvonly track
|
|
2747
|
+
if (!transceiver) transceiver = config.pc.addTransceiver(kind);
|
|
2748
|
+
if (transceiver) {
|
|
2749
|
+
// Check if we need to override some settings
|
|
2750
|
+
if (track.codec) {
|
|
2751
|
+
if (Janus.webRTCAdapter.browserDetails.browser === 'firefox') {
|
|
2752
|
+
Janus.warn('setCodecPreferences not supported in Firefox, ignoring codec for track:', track);
|
|
2753
|
+
} else if (typeof track.codec !== 'string') {
|
|
2754
|
+
Janus.warn('Invalid codec value, ignoring for track:', track);
|
|
2755
|
+
} else {
|
|
2756
|
+
let mimeType = kind + '/' + track.codec.toLowerCase();
|
|
2757
|
+
let codecs = RTCRtpReceiver.getCapabilities(kind).codecs.filter(function (codec) {
|
|
2758
|
+
return codec.mimeType.toLowerCase() === mimeType;
|
|
2759
|
+
});
|
|
2760
|
+
if (!codecs || codecs.length === 0) {
|
|
2761
|
+
Janus.warn('Codec not supported in this browser for this track, ignoring:', track);
|
|
2762
|
+
} else {
|
|
2763
|
+
try {
|
|
2764
|
+
transceiver.setCodecPreferences(codecs);
|
|
2765
|
+
} catch (err) {
|
|
2766
|
+
Janus.warn('Failed enforcing codec for this ' + kind + ' track:', err);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
// Check if insertable streams are involved
|
|
2772
|
+
if (transceiver.receiver && track.transforms && track.transforms.receiver) {
|
|
2773
|
+
// There's a receiver transform, set it on the transceiver receiver
|
|
2774
|
+
let receiverStreams = null;
|
|
2775
|
+
if (RTCRtpReceiver.prototype.createEncodedStreams) {
|
|
2776
|
+
receiverStreams = transceiver.receiver.createEncodedStreams();
|
|
2777
|
+
} else if (RTCRtpReceiver.prototype.createAudioEncodedStreams || RTCRtpReceiver.prototype.createEncodedVideoStreams) {
|
|
2778
|
+
if (kind === 'audio') {
|
|
2779
|
+
receiverStreams = transceiver.receiver.createEncodedAudioStreams();
|
|
2780
|
+
} else if (kind === 'video') {
|
|
2781
|
+
receiverStreams = transceiver.receiver.createEncodedVideoStreams();
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
if (receiverStreams) {
|
|
2785
|
+
if (receiverStreams.readableStream && receiverStreams.writableStream) {
|
|
2786
|
+
receiverStreams.readableStream.pipeThrough(track.transforms.receiver).pipeTo(receiverStreams.writableStream);
|
|
2787
|
+
} else if (receiverStreams.readable && receiverStreams.writable) {
|
|
2788
|
+
receiverStreams.readable.pipeThrough(track.transforms.receiver).pipeTo(receiverStreams.writable);
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
if (nt) {
|
|
2795
|
+
// FIXME Add the new track locally
|
|
2796
|
+
config.myStream.addTrack(nt);
|
|
2797
|
+
// Notify the application about the new local track, if any
|
|
2798
|
+
nt.onended = function (ev) {
|
|
2799
|
+
Janus.log('Local track removed:', ev);
|
|
2800
|
+
try {
|
|
2801
|
+
pluginHandle.onlocaltrack(ev.target, false);
|
|
2802
|
+
} catch (e) {
|
|
2803
|
+
Janus.error("Error calling onlocaltrack following end", e);
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2806
|
+
try {
|
|
2807
|
+
pluginHandle.onlocaltrack(nt, true);
|
|
2808
|
+
} catch (e) {
|
|
2809
|
+
Janus.error("Error calling onlocaltrack for track add", e);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
// Update the direction of the transceiver
|
|
2813
|
+
if (transceiver) {
|
|
2814
|
+
let curdir = transceiver.direction,
|
|
2815
|
+
newdir = null;
|
|
2816
|
+
let send = nt && transceiver.sender.track,
|
|
2817
|
+
recv = track.recv !== false && transceiver.receiver.track;
|
|
2818
|
+
if (send && recv) newdir = 'sendrecv';else if (send && !recv) newdir = 'sendonly';else if (!send && recv) newdir = 'recvonly';else if (!send && !recv) newdir = 'inactive';
|
|
2819
|
+
if (newdir && newdir !== curdir) {
|
|
2820
|
+
Janus.warn('Changing direction of transceiver to ' + newdir + ' (was ' + curdir + ')', track);
|
|
2821
|
+
if (transceiver.setDirection) transceiver.setDirection(newdir);else transceiver.direction = newdir;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
if (openedConsentDialog) pluginHandle.consentDialog(false);
|
|
2826
|
+
}
|
|
2827
|
+
function getLocalTracks(handleId) {
|
|
2828
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2829
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2830
|
+
Janus.warn('Invalid handle');
|
|
2831
|
+
return null;
|
|
2832
|
+
}
|
|
2833
|
+
let config = pluginHandle.webrtcStuff;
|
|
2834
|
+
if (!config.pc) {
|
|
2835
|
+
Janus.warn('Invalid PeerConnection');
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
let tracks = [];
|
|
2839
|
+
let transceivers = config.pc.getTransceivers();
|
|
2840
|
+
for (let tr of transceivers) {
|
|
2841
|
+
let track = null;
|
|
2842
|
+
if (tr.sender && tr.sender.track) {
|
|
2843
|
+
track = {
|
|
2844
|
+
mid: tr.mid
|
|
2845
|
+
};
|
|
2846
|
+
track.type = tr.sender.track.kind;
|
|
2847
|
+
track.id = tr.sender.track.id;
|
|
2848
|
+
track.label = tr.sender.track.label;
|
|
2849
|
+
}
|
|
2850
|
+
if (track) tracks.push(track);
|
|
2851
|
+
}
|
|
2852
|
+
return tracks;
|
|
2853
|
+
}
|
|
2854
|
+
function getRemoteTracks(handleId) {
|
|
2855
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2856
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2857
|
+
Janus.warn('Invalid handle');
|
|
2858
|
+
return null;
|
|
2859
|
+
}
|
|
2860
|
+
let config = pluginHandle.webrtcStuff;
|
|
2861
|
+
if (!config.pc) {
|
|
2862
|
+
Janus.warn('Invalid PeerConnection');
|
|
2863
|
+
return null;
|
|
2864
|
+
}
|
|
2865
|
+
let tracks = [];
|
|
2866
|
+
let transceivers = config.pc.getTransceivers();
|
|
2867
|
+
for (let tr of transceivers) {
|
|
2868
|
+
let track = null;
|
|
2869
|
+
if (tr.receiver && tr.receiver.track) {
|
|
2870
|
+
track = {
|
|
2871
|
+
mid: tr.mid
|
|
2872
|
+
};
|
|
2873
|
+
track.type = tr.receiver.track.kind;
|
|
2874
|
+
track.id = tr.receiver.track.id;
|
|
2875
|
+
track.label = tr.receiver.track.label;
|
|
2876
|
+
}
|
|
2877
|
+
if (track) tracks.push(track);
|
|
2878
|
+
}
|
|
2879
|
+
return tracks;
|
|
2880
|
+
}
|
|
2881
|
+
function getVolume(handleId, mid, remote, result) {
|
|
2882
|
+
result = typeof result == "function" ? result : Janus.noop;
|
|
2883
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2884
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2885
|
+
Janus.warn("Invalid handle");
|
|
2886
|
+
result(0);
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
let stream = remote ? "remote" : "local";
|
|
2890
|
+
let config = pluginHandle.webrtcStuff;
|
|
2891
|
+
if (!config.volume[stream]) config.volume[stream] = {
|
|
2892
|
+
value: 0
|
|
2893
|
+
};
|
|
2894
|
+
// Start getting the volume, if audioLevel in getStats is supported (apparently
|
|
2895
|
+
// they're only available in Chrome/Safari right now: https://webrtc-stats.callstats.io/)
|
|
2896
|
+
if (config.pc && config.pc.getStats && (Janus.webRTCAdapter.browserDetails.browser === "chrome" || Janus.webRTCAdapter.browserDetails.browser === "safari")) {
|
|
2897
|
+
// Are we interested in a mid in particular?
|
|
2898
|
+
let query = config.pc;
|
|
2899
|
+
if (mid) {
|
|
2900
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "audio");
|
|
2901
|
+
if (!transceiver) {
|
|
2902
|
+
Janus.warn("No audio transceiver with mid " + mid);
|
|
2903
|
+
result(0);
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
if (remote && !transceiver.receiver) {
|
|
2907
|
+
Janus.warn("Remote transceiver track unavailable");
|
|
2908
|
+
result(0);
|
|
2909
|
+
return;
|
|
2910
|
+
} else if (!remote && !transceiver.sender) {
|
|
2911
|
+
Janus.warn("Local transceiver track unavailable");
|
|
2912
|
+
result(0);
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
query = remote ? transceiver.receiver : transceiver.sender;
|
|
2916
|
+
}
|
|
2917
|
+
query.getStats().then(function (stats) {
|
|
2918
|
+
stats.forEach(function (res) {
|
|
2919
|
+
if (!res || res.kind !== "audio") return;
|
|
2920
|
+
if (remote && !res.remoteSource || !remote && res.type !== "media-source") return;
|
|
2921
|
+
result(res.audioLevel ? res.audioLevel : 0);
|
|
2922
|
+
});
|
|
2923
|
+
});
|
|
2924
|
+
return config.volume[stream].value;
|
|
2925
|
+
} else {
|
|
2926
|
+
// audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel
|
|
2927
|
+
// seems to be available on Chrome and Firefox, but they don't seem to work
|
|
2928
|
+
Janus.warn("Getting the " + stream + " volume unsupported by browser");
|
|
2929
|
+
result(0);
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
function isMuted(handleId, mid, video) {
|
|
2934
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2935
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2936
|
+
Janus.warn("Invalid handle");
|
|
2937
|
+
return true;
|
|
2938
|
+
}
|
|
2939
|
+
let config = pluginHandle.webrtcStuff;
|
|
2940
|
+
if (!config.pc) {
|
|
2941
|
+
Janus.warn("Invalid PeerConnection");
|
|
2942
|
+
return true;
|
|
2943
|
+
}
|
|
2944
|
+
if (!config.myStream) {
|
|
2945
|
+
Janus.warn("Invalid local MediaStream");
|
|
2946
|
+
return true;
|
|
2947
|
+
}
|
|
2948
|
+
if (video) {
|
|
2949
|
+
// Check video track
|
|
2950
|
+
if (!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
|
|
2951
|
+
Janus.warn("No video track");
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
if (mid) {
|
|
2955
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "video");
|
|
2956
|
+
if (!transceiver) {
|
|
2957
|
+
Janus.warn("No video transceiver with mid " + mid);
|
|
2958
|
+
return true;
|
|
2959
|
+
}
|
|
2960
|
+
if (!transceiver.sender || !transceiver.sender.track) {
|
|
2961
|
+
Janus.warn("No video sender with mid " + mid);
|
|
2962
|
+
return true;
|
|
2963
|
+
}
|
|
2964
|
+
return !transceiver.sender.track.enabled;
|
|
2965
|
+
} else {
|
|
2966
|
+
return !config.myStream.getVideoTracks()[0].enabled;
|
|
2967
|
+
}
|
|
2968
|
+
} else {
|
|
2969
|
+
// Check audio track
|
|
2970
|
+
if (!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
|
|
2971
|
+
Janus.warn("No audio track");
|
|
2972
|
+
return true;
|
|
2973
|
+
}
|
|
2974
|
+
if (mid) {
|
|
2975
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "audio");
|
|
2976
|
+
if (!transceiver) {
|
|
2977
|
+
Janus.warn("No audio transceiver with mid " + mid);
|
|
2978
|
+
return true;
|
|
2979
|
+
}
|
|
2980
|
+
if (!transceiver.sender || !transceiver.sender.track) {
|
|
2981
|
+
Janus.warn("No audio sender with mid " + mid);
|
|
2982
|
+
return true;
|
|
2983
|
+
}
|
|
2984
|
+
return !transceiver.sender.track.enabled;
|
|
2985
|
+
} else {
|
|
2986
|
+
return !config.myStream.getAudioTracks()[0].enabled;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
function mute(handleId, mid, video, mute) {
|
|
2991
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
2992
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
2993
|
+
Janus.warn("Invalid handle");
|
|
2994
|
+
return false;
|
|
2995
|
+
}
|
|
2996
|
+
let config = pluginHandle.webrtcStuff;
|
|
2997
|
+
if (!config.pc) {
|
|
2998
|
+
Janus.warn("Invalid PeerConnection");
|
|
2999
|
+
return false;
|
|
3000
|
+
}
|
|
3001
|
+
if (!config.myStream) {
|
|
3002
|
+
Janus.warn("Invalid local MediaStream");
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
if (video) {
|
|
3006
|
+
// Mute/unmute video track
|
|
3007
|
+
if (!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {
|
|
3008
|
+
Janus.warn("No video track");
|
|
3009
|
+
return false;
|
|
3010
|
+
}
|
|
3011
|
+
if (mid) {
|
|
3012
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "video");
|
|
3013
|
+
if (!transceiver) {
|
|
3014
|
+
Janus.warn("No video transceiver with mid " + mid);
|
|
3015
|
+
return false;
|
|
3016
|
+
}
|
|
3017
|
+
if (!transceiver.sender || !transceiver.sender.track) {
|
|
3018
|
+
Janus.warn("No video sender with mid " + mid);
|
|
3019
|
+
return false;
|
|
3020
|
+
}
|
|
3021
|
+
transceiver.sender.track.enabled = mute ? false : true;
|
|
3022
|
+
} else {
|
|
3023
|
+
for (const videostream of config.myStream.getVideoTracks()) {
|
|
3024
|
+
videostream.enabled = !mute;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
} else {
|
|
3028
|
+
// Mute/unmute audio track
|
|
3029
|
+
if (!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {
|
|
3030
|
+
Janus.warn("No audio track");
|
|
3031
|
+
return false;
|
|
3032
|
+
}
|
|
3033
|
+
if (mid) {
|
|
3034
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "audio");
|
|
3035
|
+
if (!transceiver) {
|
|
3036
|
+
Janus.warn("No audio transceiver with mid " + mid);
|
|
3037
|
+
return false;
|
|
3038
|
+
}
|
|
3039
|
+
if (!transceiver.sender || !transceiver.sender.track) {
|
|
3040
|
+
Janus.warn("No audio sender with mid " + mid);
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
transceiver.sender.track.enabled = mute ? false : true;
|
|
3044
|
+
} else {
|
|
3045
|
+
for (const audiostream of config.myStream.getAudioTracks()) {
|
|
3046
|
+
audiostream.enabled = !mute;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
return true;
|
|
3051
|
+
}
|
|
3052
|
+
function getBitrate(handleId, mid) {
|
|
3053
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
3054
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
3055
|
+
Janus.warn("Invalid handle");
|
|
3056
|
+
return "Invalid handle";
|
|
3057
|
+
}
|
|
3058
|
+
let config = pluginHandle.webrtcStuff;
|
|
3059
|
+
if (!config.pc) return "Invalid PeerConnection";
|
|
3060
|
+
// Start getting the bitrate, if getStats is supported
|
|
3061
|
+
if (config.pc.getStats) {
|
|
3062
|
+
let query = config.pc;
|
|
3063
|
+
let target = mid ? mid : "default";
|
|
3064
|
+
if (mid) {
|
|
3065
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid && t.receiver.track.kind === "video");
|
|
3066
|
+
if (!transceiver) {
|
|
3067
|
+
Janus.warn("No video transceiver with mid " + mid);
|
|
3068
|
+
return "No video transceiver with mid " + mid;
|
|
3069
|
+
}
|
|
3070
|
+
if (!transceiver.receiver) {
|
|
3071
|
+
Janus.warn("No video receiver with mid " + mid);
|
|
3072
|
+
return "No video receiver with mid " + mid;
|
|
3073
|
+
}
|
|
3074
|
+
query = transceiver.receiver;
|
|
3075
|
+
}
|
|
3076
|
+
if (!config.bitrate[target]) {
|
|
3077
|
+
config.bitrate[target] = {
|
|
3078
|
+
timer: null,
|
|
3079
|
+
bsnow: null,
|
|
3080
|
+
bsbefore: null,
|
|
3081
|
+
tsnow: null,
|
|
3082
|
+
tsbefore: null,
|
|
3083
|
+
value: "0 kbits/sec"
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
if (!config.bitrate[target].timer) {
|
|
3087
|
+
Janus.log("Starting bitrate timer" + (mid ? " for mid " + mid : "") + " (via getStats)");
|
|
3088
|
+
config.bitrate[target].timer = setInterval(function () {
|
|
3089
|
+
query.getStats().then(function (stats) {
|
|
3090
|
+
stats.forEach(function (res) {
|
|
3091
|
+
if (!res) return;
|
|
3092
|
+
let inStats = false;
|
|
3093
|
+
// Check if these are statistics on incoming media
|
|
3094
|
+
if ((res.mediaType === "video" || res.kind === "video" || res.id.toLowerCase().indexOf("video") > -1) && res.type === "inbound-rtp" && res.id.indexOf("rtcp") < 0) {
|
|
3095
|
+
// New stats
|
|
3096
|
+
inStats = true;
|
|
3097
|
+
} else if (res.type == 'ssrc' && res.bytesReceived && (res.googCodecName === "VP8" || res.googCodecName === "")) {
|
|
3098
|
+
// Older Chromer versions
|
|
3099
|
+
inStats = true;
|
|
3100
|
+
}
|
|
3101
|
+
// Parse stats now
|
|
3102
|
+
if (inStats) {
|
|
3103
|
+
config.bitrate[target].bsnow = res.bytesReceived;
|
|
3104
|
+
config.bitrate[target].tsnow = res.timestamp;
|
|
3105
|
+
if (config.bitrate[target].bsbefore === null || config.bitrate[target].tsbefore === null) {
|
|
3106
|
+
// Skip this round
|
|
3107
|
+
config.bitrate[target].bsbefore = config.bitrate[target].bsnow;
|
|
3108
|
+
config.bitrate[target].tsbefore = config.bitrate[target].tsnow;
|
|
3109
|
+
} else {
|
|
3110
|
+
// Calculate bitrate
|
|
3111
|
+
let timePassed = config.bitrate[target].tsnow - config.bitrate[target].tsbefore;
|
|
3112
|
+
if (Janus.webRTCAdapter.browserDetails.browser === "safari") timePassed = timePassed / 1000; // Apparently the timestamp is in microseconds, in Safari
|
|
3113
|
+
let bitRate = Math.round((config.bitrate[target].bsnow - config.bitrate[target].bsbefore) * 8 / timePassed);
|
|
3114
|
+
if (Janus.webRTCAdapter.browserDetails.browser === "safari") bitRate = parseInt(bitRate / 1000);
|
|
3115
|
+
config.bitrate[target].value = bitRate + ' kbits/sec';
|
|
3116
|
+
//~ Janus.log("Estimated bitrate is " + config.bitrate.value);
|
|
3117
|
+
config.bitrate[target].bsbefore = config.bitrate[target].bsnow;
|
|
3118
|
+
config.bitrate[target].tsbefore = config.bitrate[target].tsnow;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
});
|
|
3122
|
+
});
|
|
3123
|
+
}, 1000);
|
|
3124
|
+
return "0 kbits/sec"; // We don't have a bitrate value yet
|
|
3125
|
+
}
|
|
3126
|
+
return config.bitrate[target].value;
|
|
3127
|
+
} else {
|
|
3128
|
+
Janus.warn("Getting the video bitrate unsupported by browser");
|
|
3129
|
+
return "Feature unsupported by browser";
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
function setBitrate(handleId, mid, bitrate) {
|
|
3133
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
3134
|
+
if (!pluginHandle || !pluginHandle.webrtcStuff) {
|
|
3135
|
+
Janus.warn('Invalid handle');
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
let config = pluginHandle.webrtcStuff;
|
|
3139
|
+
if (!config.pc) {
|
|
3140
|
+
Janus.warn('Invalid PeerConnection');
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
let transceiver = config.pc.getTransceivers().find(t => t.mid === mid);
|
|
3144
|
+
if (!transceiver) {
|
|
3145
|
+
Janus.warn('No transceiver with mid', mid);
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (!transceiver.sender) {
|
|
3149
|
+
Janus.warn('No sender for transceiver with mid', mid);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
let params = transceiver.sender.getParameters();
|
|
3153
|
+
if (!params || !params.encodings || params.encodings.length === 0) {
|
|
3154
|
+
Janus.warn('No parameters encodings');
|
|
3155
|
+
} else if (params.encodings.length > 1) {
|
|
3156
|
+
Janus.warn('Ignoring bitrate for simulcast track, use sendEncodings for that');
|
|
3157
|
+
} else if (isNaN(bitrate) || bitrate < 0) {
|
|
3158
|
+
Janus.warn('Invalid bitrate (must be a positive integer)');
|
|
3159
|
+
} else {
|
|
3160
|
+
params.encodings[0].maxBitrate = bitrate;
|
|
3161
|
+
transceiver.sender.setParameters(params);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function webrtcError(error) {
|
|
3165
|
+
Janus.error("WebRTC error:", error);
|
|
3166
|
+
}
|
|
3167
|
+
function cleanupWebrtc(handleId, hangupRequest) {
|
|
3168
|
+
Janus.log("Cleaning WebRTC stuff");
|
|
3169
|
+
let pluginHandle = pluginHandles.get(handleId);
|
|
3170
|
+
if (!pluginHandle) {
|
|
3171
|
+
// Nothing to clean
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
let config = pluginHandle.webrtcStuff;
|
|
3175
|
+
if (config) {
|
|
3176
|
+
if (hangupRequest === true) {
|
|
3177
|
+
// Send a hangup request (we don't really care about the response)
|
|
3178
|
+
let request = {
|
|
3179
|
+
"janus": "hangup",
|
|
3180
|
+
"transaction": Janus.randomString(12)
|
|
3181
|
+
};
|
|
3182
|
+
if (pluginHandle.token) request["token"] = pluginHandle.token;
|
|
3183
|
+
if (apisecret) request["apisecret"] = apisecret;
|
|
3184
|
+
Janus.debug("Sending hangup request (handle=" + handleId + "):");
|
|
3185
|
+
Janus.debug(request);
|
|
3186
|
+
if (websockets) {
|
|
3187
|
+
request["session_id"] = sessionId;
|
|
3188
|
+
request["handle_id"] = handleId;
|
|
3189
|
+
ws.send(JSON.stringify(request));
|
|
3190
|
+
} else {
|
|
3191
|
+
Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, {
|
|
3192
|
+
verb: 'POST',
|
|
3193
|
+
withCredentials: withCredentials,
|
|
3194
|
+
body: request
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
// Cleanup stack
|
|
3199
|
+
if (config.volume) {
|
|
3200
|
+
if (config.volume["local"] && config.volume["local"].timer) clearInterval(config.volume["local"].timer);
|
|
3201
|
+
if (config.volume["remote"] && config.volume["remote"].timer) clearInterval(config.volume["remote"].timer);
|
|
3202
|
+
}
|
|
3203
|
+
for (let i in config.bitrate) {
|
|
3204
|
+
if (config.bitrate[i].timer) clearInterval(config.bitrate[i].timer);
|
|
3205
|
+
}
|
|
3206
|
+
config.bitrate = {};
|
|
3207
|
+
if (!config.streamExternal && config.myStream) {
|
|
3208
|
+
Janus.log("Stopping local stream tracks");
|
|
3209
|
+
Janus.stopAllTracks(config.myStream);
|
|
3210
|
+
}
|
|
3211
|
+
config.streamExternal = false;
|
|
3212
|
+
config.myStream = null;
|
|
3213
|
+
// Close PeerConnection
|
|
3214
|
+
try {
|
|
3215
|
+
config.pc.close();
|
|
3216
|
+
// eslint-disable-next-line no-unused-vars
|
|
3217
|
+
} catch (e) {
|
|
3218
|
+
// Do nothing
|
|
3219
|
+
}
|
|
3220
|
+
config.pc = null;
|
|
3221
|
+
config.candidates = null;
|
|
3222
|
+
config.mySdp = null;
|
|
3223
|
+
config.remoteSdp = null;
|
|
3224
|
+
config.iceDone = false;
|
|
3225
|
+
config.dataChannel = {};
|
|
3226
|
+
config.dtmfSender = null;
|
|
3227
|
+
config.insertableStreams = false;
|
|
3228
|
+
config.externalEncryption = false;
|
|
3229
|
+
}
|
|
3230
|
+
pluginHandle.oncleanup();
|
|
3231
|
+
}
|
|
3232
|
+
function isTrickleEnabled(trickle) {
|
|
3233
|
+
Janus.debug("isTrickleEnabled:", trickle);
|
|
3234
|
+
return trickle === false ? false : true;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
return Janus;
|
|
3238
|
+
});
|
|
3239
|
+
|
|
3240
|
+
export { Janus as default };
|
|
3241
|
+
//# sourceMappingURL=janus.es.js.map
|