ovenlivekit 1.5.6 → 1.5.8

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 CHANGED
@@ -1,17 +1,17 @@
1
1
  # OvenLiveKit for Web
2
2
 
3
3
  ## What is OvenLiveKit for Web?
4
- OvenLiveKit for Web is a JavaScript-based Live Streaming Encoder that supports WebRTC optimized for [OvenMediaEngine](https://github.com/AirenSoft/OvenMediaEngine), Sub-Second Latency Streaming Server. OvenLiveKit for Web relies on the browser's WebRTC API and wraps it to make it easy for you to broadcast WebRTC streams to OvenMediaEngine.
4
+ OvenLiveKit for Web is a JavaScript-based Live Streaming Encoder that supports WebRTC optimized for [OvenMediaEngine](https://github.com/OvenMediaLabs/OvenMediaEngine), Sub-Second Latency Streaming Server. OvenLiveKit for Web relies on the browser's WebRTC API and wraps it to make it easy for you to broadcast WebRTC streams to OvenMediaEngine.
5
5
 
6
6
  ## Demo
7
7
  <img src="./assets/05_OvenSpace_230214.png" style="max-width: 100%; height: auto;">
8
8
 
9
- OvenSpace is a sub-second latency streaming demo service using [OvenMediaEngine](https://github.com/AirenSoft/OvenMediaEngine), [OvenPlayer](https://github.com/AirenSoft/OvenPlayer) and [OvenLiveKit](https://github.com/AirenSoft/OvenLiveKit-Web). You can experience OvenLiveKit in the **[OvenSpace Demo](https://space.ovenplayer.com/)** and see examples of how it can be applied in the [OvenSpace Repository](https://github.com/AirenSoft/OvenSpace).
9
+ OvenSpace is a sub-second latency streaming demo service using [OvenMediaEngine](https://github.com/OvenMediaLabs/OvenMediaEngine), [OvenPlayer](https://github.com/OvenMediaLabs/OvenPlayer) and [OvenLiveKit](https://github.com/OvenMediaLabs/OvenLiveKit-Web). You can experience OvenLiveKit in the **[OvenSpace Demo](https://space.ovenplayer.com/)** and see examples of how it can be applied in the [OvenSpace Repository](https://github.com/OvenMediaLabs/OvenSpace).
10
10
 
11
11
  ## Features
12
12
  * Support WebRTC-HTTP ingestion protocol (WHIP) -Since 1.3.0
13
13
  * Streaming to OvenMediaEngine's WebRTC Provider.
14
- * Implement [OvenMediaEngine's signaling protocol](https://airensoft.gitbook.io/ovenmediaengine/live-source/webrtc-beta#custom-webrtc-producer)
14
+ * Implement [OvenMediaEngine's signaling protocol](https://ovenmedialabs.com/docs/ome/live-source/webrtc#custom-webrtc-producer)
15
15
  * Built-in Device, Screen Capture.
16
16
  * Set the Quality of the Input Stream.
17
17
 
@@ -57,7 +57,7 @@ ovenLivekit.getDisplayMedia().then(function () {
57
57
  You can see a quick demo in action by cloning the repository.
58
58
  1. Clone repository
59
59
  ```
60
- $ git clone https://github.com/AirenSoft/OvenLiveKit-Web.git
60
+ $ git clone https://github.com/OvenMediaLabs/OvenLiveKit-Web.git
61
61
  ```
62
62
  ```
63
63
  $ cd OvenLiveKit-Web
@@ -316,7 +316,7 @@ ovenLivekit.getUserMedia().then(function () {
316
316
  ```
317
317
  #### `instance.startStreaming(connectionUrl, connectionConfig)`
318
318
  - parameters
319
- - connectionUrl: The connection URL to OvenMediaEngine is explained [here](https://airensoft.gitbook.io/ovenmediaengine/live-source/webrtc-beta#url-pattern).
319
+ - connectionUrl: The connection URL to OvenMediaEngine is explained [here](https://ovenmedialabs.com/docs/ome/live-source/webrtc#url-pattern).
320
320
  - connectionConfig: See [ConnectionConfig](#) details next.
321
321
  - When this API is called, the media stream starts to be streamed according to OvenMediaEngine's signaling protocol.
322
322
 
@@ -363,15 +363,15 @@ ovenLivekit.getUserMedia().then(function () {
363
363
  - Close peer connection and websocket associated with OvenMediaEngine.
364
364
 
365
365
  ## For more information
366
- * [AirenSoft Website](https://airensoft.com)
367
- * About OvenMediaEngine, OvenMediaEngine Enterprise, OvenVideo, AirenBlog and more
368
- * [OvenMediaEngine GitHub](https://github.com/AirenSoft/OvenMediaEngine)
366
+ * [OvenMediaLabs Website](https://ovenmedialabs.com)
367
+ * About OvenMediaEngine, OvenMediaEngine Enterprise, OvenVideo, OvenMediaLabs Blog and more
368
+ * [OvenMediaEngine GitHub](https://github.com/OvenMediaLabs/OvenMediaEngine)
369
369
  * Sub-Second Latency Streaming Server with LLHLS and WebRTC
370
- * [OvenMediaEngine Getting Started](https://airensoft.gitbook.io/ovenmediaengine/)
370
+ * [OvenMediaEngine Getting Started](https://ovenmedialabs.com/docs/ome/)
371
371
  * User guide for OvenMediaEngine Configuration, ABR, Clustering, and more
372
- * [OvenPlayer GitHub](https://github.com/AirenSoft/OvenPlayer)
372
+ * [OvenPlayer GitHub](https://github.com/OvenMediaLabs/OvenPlayer)
373
373
  * JavaScript-based Player with LLHLS and WebRTC
374
- * [OvenPlayer Getting Started](https://airensoft.gitbook.io/ovenplayer)
374
+ * [OvenPlayer Getting Started](https://ovenmedialabs.com/docs/ovenplayer)
375
375
  * User guide for OvenPlayer UI Customize, API Reference, Examples, and more
376
376
  * [OvenSpace Demo](https://space.ovenplayer.com/)
377
377
  * Sub-Second Latency Streaming Demo Service
@@ -379,11 +379,11 @@ ovenLivekit.getUserMedia().then(function () {
379
379
  ## License
380
380
  OvenLiveKit for Web is licensed under the [MIT](./LICENSE) license.
381
381
 
382
- ## About AirenSoft
383
- AirenSoft aims to make it easier for you to build a stable broadcasting/streaming service with Sub-Second Latency.
382
+ ## About OvenMediaLabs
383
+ OvenMediaLabs aims to make it easier for you to build a stable broadcasting/streaming service with Sub-Second Latency.
384
384
  Therefore, we will continue developing and providing the most optimized tools for smooth Sub-Second Latency Streaming.
385
385
 
386
386
  Would you please click on each link below for details:
387
- * ["JavaScript-based Live Streaming Encoder" **OvenLiveKit**](https://github.com/AirenSoft/OvenLiveKit-Web)
388
- * ["Sub-Second Latency Streaming Server with LLHLS and WebRTC" **OvenMediaEngine**](https://github.com/AirenSoft/OvenMediaEngine)
389
- * ["JavaScript-based Player with LLHLS and WebRTC" **OvenPlayer**](https://github.com/AirenSoft/OvenPlayer)
387
+ * ["JavaScript-based Live Streaming Encoder" **OvenLiveKit**](https://github.com/OvenMediaLabs/OvenLiveKit-Web)
388
+ * ["Sub-Second Latency Streaming Server with LLHLS and WebRTC" **OvenMediaEngine**](https://github.com/OvenMediaLabs/OvenMediaEngine)
389
+ * ["JavaScript-based Player with LLHLS and WebRTC" **OvenPlayer**](https://github.com/OvenMediaLabs/OvenPlayer)
@@ -26,7 +26,7 @@ return /******/ (() => { // webpackBootstrap
26
26
  \****************************/
27
27
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
28
28
 
29
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nconst OvenLiveKit = {};\n\nconst version = '1.5.6';\nconst logHeader = 'OvenLiveKit.js :';\nconst logEventHeader = 'OvenLiveKit.js ====';\n\n// private methods\nfunction getTransportQuery(endpointUrl) {\n\n try {\n const url = new URL(endpointUrl);\n const transport = url.searchParams.get('transport');\n\n if (transport === null) {\n return undefined;\n }\n\n const normalized = transport.toLowerCase();\n\n return normalized;\n } catch (e) {\n return undefined;\n }\n}\n\nfunction sendMessage(webSocket, message) {\n\n if (webSocket) {\n webSocket.send(JSON.stringify(message));\n }\n}\n\nfunction generateDomainFromUrl(url) {\n let result = '';\n let match;\n if (match = url.match(/^(?:wss?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n\\?\\=]+)/im)) {\n result = match[1];\n }\n\n return result;\n}\n\nfunction findIp(string) {\n\n let result = '';\n let match;\n\n if (match = string.match(new RegExp('\\\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\b', 'gi'))) {\n result = match[0];\n }\n\n return result;\n}\n\nfunction getFormatNumber(sdp, format) {\n\n const lines = sdp.split('\\r\\n');\n let formatNumber = -1;\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n lines[i] = lines[i].toLowerCase();\n\n if (lines[i].indexOf('a=rtpmap') === 0 && lines[i].indexOf(format.toLowerCase()) > -1) {\n // parsing \"a=rtpmap:100 H264/90000\" line\n // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters >]\n formatNumber = lines[i].split(' ')[0].split(':')[1];\n break;\n }\n }\n\n return formatNumber;\n}\n\nfunction setPreferredVideoFormat(sdp, formatName) {\n\n const formatNumber = getFormatNumber(sdp, formatName);\n\n if (formatNumber === -1) {\n return sdp;\n }\n\n let newLines = [];\n const lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n const line = lines[i];\n\n if (line.indexOf('m=video') === 0) {\n\n // m=<media> <port>/<number of ports> <transport> <fmt list>\n const others = line.split(' ').slice(0, 3);\n const formats = line.split(' ').slice(3);\n formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });\n newLines.push(others.concat(formats).join(' '));\n } else {\n newLines.push(line);\n }\n\n }\n\n return newLines.join('\\r\\n') + '\\r\\n';\n}\n\nfunction removeFormat(sdp, formatNumber) {\n let newLines = [];\n let lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n newLines.push(lines[i].replace(' ' + formatNumber + '', ''));\n } else if (lines[i].indexOf(formatNumber + '') > -1) {\n\n } else {\n newLines.push(lines[i]);\n }\n }\n\n return newLines.join('\\r\\n')\n}\n\nasync function getStreamForDeviceCheck(type) {\n\n // High resolution video constraints makes browser to get maximum resolution of video device.\n const constraints = {\n };\n\n if (type === 'both') {\n constraints.audio = true;\n constraints.video = true;\n } else if (type === 'audio') {\n constraints.audio = true;\n } else if (type === 'video') {\n constraints.video = true;\n }\n\n const permissionStream = await navigator.mediaDevices.getUserMedia(constraints);\n permissionStream.getTracks().forEach(track => track.stop());\n await new Promise(resolve => setTimeout(resolve, 500));\n\n return true;\n}\n\nasync function getDevices() {\n\n return await navigator.mediaDevices.enumerateDevices();\n}\n\nfunction gotDevices(deviceInfos) {\n\n let devices = {\n 'audioinput': [],\n 'audiooutput': [],\n 'videoinput': [],\n 'other': [],\n };\n\n for (let i = 0; i !== deviceInfos.length; ++i) {\n\n const deviceInfo = deviceInfos[i];\n\n let info = {};\n\n info.deviceId = deviceInfo.deviceId;\n\n if (deviceInfo.kind === 'audioinput') {\n\n info.label = deviceInfo.label || `microphone ${devices.audioinput.length + 1}`;\n devices.audioinput.push(info);\n } else if (deviceInfo.kind === 'audiooutput') {\n\n info.label = deviceInfo.label || `speaker ${devices.audiooutput.length + 1}`;\n devices.audiooutput.push(info);\n } else if (deviceInfo.kind === 'videoinput') {\n\n info.label = deviceInfo.label || `camera ${devices.videoinput.length + 1}`;\n devices.videoinput.push(info);\n } else {\n\n info.label = deviceInfo.label || `other ${devices.other.length + 1}`;\n devices.other.push(info);\n }\n }\n\n return devices;\n}\n\nfunction initConfig(instance, options) {\n\n // webrtc or whip\n instance.streamingMode = null;\n\n instance.inputStream = null;\n instance.webSocket = null;\n instance.peerConnection = null;\n instance.connectionConfig = {};\n\n instance.videoElement = null;\n instance.endpointUrl = null;\n instance.resourceUrl = null;\n\n if (options && options.callbacks) {\n\n instance.callbacks = options.callbacks;\n } else {\n instance.callbacks = {};\n }\n}\n\nfunction addMethod(instance) {\n\n function errorHandler(error) {\n\n if (instance.callbacks.error) {\n\n instance.callbacks.error(error);\n }\n }\n\n async function fetchWithRedirect(url, options) {\n let fetched = await fetch(url, options);\n\n while (fetched.redirected) {\n url = fetched.url;\n fetched = await fetch(url, options);\n }\n\n return fetched;\n }\n\n function getUserMedia(constraints) {\n\n if (!constraints) {\n\n constraints = {\n video: {\n deviceId: undefined\n },\n audio: {\n deviceId: undefined\n }\n };\n }\n\n console.info(logHeader, 'Request Stream To Input Devices With Constraints', constraints);\n\n return navigator.mediaDevices.getUserMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Input Device', stream);\n\n stream.getVideoTracks().forEach(function (track) {\n console.info(logHeader, 'Video Track Settings From Input Stream', track.getSettings());\n });\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Input Device', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function getDisplayMedia(constraints) {\n\n if (!constraints) {\n constraints = {};\n }\n\n console.info(logHeader, 'Request Stream To Display With Constraints', constraints);\n\n return navigator.mediaDevices.getDisplayMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Display', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Display', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function setMediaStream(stream) {\n // Check if a valid stream is provided\n if (!stream || !(stream instanceof MediaStream)) {\n\n const error = new Error(\"Invalid MediaStream provided\");\n console.error(logHeader, 'Invalid MediaStream', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n }\n\n console.info(logHeader, 'Received Media Stream', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n resolve(stream);\n });\n }\n\n // From https://webrtchacks.com/limit-webrtc-bandwidth-sdp/\n function setBitrateLimit(sdp, media, bitrate) {\n\n let lines = sdp.split('\\r\\n');\n let line = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].indexOf('m=' + media) === 0) {\n line = i;\n break;\n }\n }\n if (line === -1) {\n // Could not find the m line for media\n return sdp;\n }\n\n // Pass the m line\n line++;\n\n // Skip i and c lines\n while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {\n\n line++;\n }\n\n // If we're on a b line, replace it\n if (lines[line].indexOf('b') === 0) {\n\n lines[line] = 'b=AS:' + bitrate;\n\n return lines.join('\\r\\n');\n }\n\n // Add a new b line\n let newLines = lines.slice(0, line)\n\n newLines.push('b=AS:' + bitrate)\n newLines = newLines.concat(lines.slice(line, lines.length))\n\n return newLines.join('\\r\\n')\n }\n\n function normalizeFrameRate(frameRate) {\n if (Number.isInteger(frameRate)) {\n return frameRate;\n }\n return parseFloat(frameRate).toFixed(2);\n }\n\n function initWebSocket(endpointUrl) {\n\n if (!endpointUrl) {\n errorHandler('endpointUrl is required');\n return;\n }\n\n let webSocket = null;\n\n try {\n\n webSocket = new WebSocket(endpointUrl);\n } catch (error) {\n\n errorHandler(error);\n }\n\n\n instance.webSocket = webSocket;\n\n webSocket.onopen = function () {\n\n // Request offer at the first time.\n sendMessage(webSocket, {\n command: 'request_offer'\n });\n };\n\n webSocket.onmessage = function (e) {\n\n let message = JSON.parse(e.data);\n\n if (message.error) {\n console.error('webSocket.onmessage', message.error);\n errorHandler(message.error);\n }\n\n if (message.command === 'offer') {\n\n // OME returns offer. Start create peer connection.\n createPeerConnection(\n message.id,\n message.peer_id,\n message.sdp,\n message.candidates,\n message.ice_servers\n );\n }\n };\n\n webSocket.onerror = function (error) {\n\n console.error('webSocket.onerror', error);\n errorHandler(error);\n };\n\n webSocket.onclose = function (e) {\n\n if (!instance.webSocketClosedByUser) {\n\n if (instance.callbacks.connectionClosed) {\n instance.callbacks.connectionClosed('websocket', e);\n }\n }\n };\n\n }\n\n async function startWhip(endpointUrl) {\n\n if (instance.peerConnection) {\n console.error('Connection already established');\n errorHandler('Connection already established');\n return;\n }\n\n const peerConnectionConfig = {\n bundlePolicy: \"max-bundle\"\n };\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from local config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else {\n // last priority using default ice servers.\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n }\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n const peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n if (!instance.inputStream) {\n console.error('No input stream in OvenLiveKit');\n errorHandler('No input stream in OvenLiveKit');\n return;\n }\n\n for (const track of instance.inputStream.getTracks()) {\n console.log(logHeader, 'Adding track: ', track);\n\n const transceiverConfig = {\n direction: 'sendonly'\n };\n\n // Add simulcast layers if configured\n const simulcastConfig = instance.connectionConfig.simulcast;\n\n if (track.kind === 'video' && simulcastConfig && simulcastConfig.length > 0) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n for (let i = 0; i < simulcastConfig.length; i++) {\n\n const layer = {\n rid: i,\n active: true,\n ...simulcastConfig[i]\n };\n\n console.log(logHeader, `Adding simulcast layer to: ${track.kind}`, layer);\n\n transceiverConfig.sendEncodings.push(layer);\n }\n }\n\n if (track.kind === 'video' && instance.connectionConfig.maxVideoBitrate) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n transceiverConfig.sendEncodings.push({\n maxBitrate: instance.connectionConfig.maxVideoBitrate * 1000,\n });\n\n console.log(logHeader, `Setting max bitrate to: ${instance.connectionConfig.maxVideoBitrate} kbps`);\n }\n\n peerConnection.addTransceiver(track, transceiverConfig);\n }\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection failed', e);\n instance.callbacks.errorHandler(e);\n }\n }\n\n if (state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection disconnected or closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n\n const offer = await peerConnection.createOffer();\n console.log(logHeader, 'Offer SDP: ', offer.sdp);\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat);\n } else {\n // default to H264\n offer.sdp = setPreferredVideoFormat(offer.sdp, 'H264');\n }\n\n const headers = {\n \"Content-Type\": \"application/sdp\"\n };\n\n // Set Oven-Capabilities header if not over two simulcast layers.\n if (!instance.connectionConfig.simulcast || instance.connectionConfig.simulcast.length < 2) {\n\n const videoTracks = instance.inputStream.getVideoTracks();\n\n if (videoTracks && videoTracks.length === 1) {\n\n for (let i = 0; i < videoTracks.length; i++) {\n\n const track = videoTracks[i];\n const settings = track.getSettings();\n\n console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);\n\n const width = settings.width;\n const height = settings.height;\n const framerate = settings.frameRate;\n\n if (typeof width === 'number' && typeof height === 'number') {\n console.log(logHeader, `Setting Oven-Capabilities header: max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`);\n headers['Oven-Capabilities'] = `max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`;\n }\n }\n } else {\n\n if (!videoTracks || videoTracks.length === 0) {\n console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');\n }\n\n if (videoTracks && videoTracks.length > 1) {\n console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);\n }\n }\n } else {\n console.log(logHeader, 'Over two simulcast layers are set, skipping Oven-Capabilities header.');\n }\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n const fetched = await fetchWithRedirect(endpointUrl, {\n method: \"POST\",\n body: offer.sdp,\n headers\n });\n\n if (!fetched.ok) {\n console.error('Failed to fetch', fetched.status);\n errorHandler(`Failed to fetch ${fetched.status}`);\n closePeerConnection();\n return;\n }\n\n if (!fetched.headers.get(\"location\")) {\n console.error('No location header on answer response');\n errorHandler('No location header on answer response');\n return;\n }\n\n // update endpointUrl\n instance.endpointUrl = fetched.url;\n console.log(logHeader, 'Endpoint Url: ', instance.endpointUrl);\n\n const locationHeaderValue = fetched.headers.get(\"location\");\n console.log(logHeader, 'Location header ', locationHeaderValue);\n\n instance.resourceUrl = new URL(locationHeaderValue, fetched.url).toString();\n console.log(logHeader, 'Resource URL: ', instance.resourceUrl);\n\n\n if (fetched.headers.get(\"Link\")) {\n console.log(logHeader, 'Link Header Found. Update ');\n\n const links = fetched.headers.get(\"Link\").split(',').map(link => link.trim());\n\n // OvenMediaEngine returns turn servers in Link header with rel=\"ice-server\".\n // For example: <turn:119.196.229.2:3478?transport=tcp>; rel=\"ice-server\"; username=\"user\"; credential=\"cred\"; credential-type=\"password\"\n const turnIceServers = [];\n\n links.forEach(link => {\n\n console.log(logHeader, 'Link: ', link);\n const urlsMatch = link.match(/<([^>]+)>/);\n const relMatch = link.match(/rel=\"([^\"]+)\"/);\n const usernameMatch = link.match(/username=\"([^\"]+)\"/);\n const credentialMatch = link.match(/credential=\"([^\"]+)\"/);\n const credentialTypeMatch = link.match(/credential-type=\"([^\"]+)\"/);\n\n if (urlsMatch && relMatch && relMatch[1] === 'ice-server') {\n\n const iceServer = {\n urls: urlsMatch[1]\n };\n\n if (usernameMatch) {\n iceServer.username = usernameMatch[1];\n };\n\n if (credentialMatch) {\n iceServer.credential = credentialMatch[1];\n };\n\n if (credentialTypeMatch) {\n iceServer.credentialType = credentialTypeMatch[1];\n }\n\n turnIceServers.push(iceServer);\n }\n });\n\n console.log(logHeader, 'Parsed ICE Servers from Link header: ', turnIceServers);\n\n // update peer connection with new turn servers from OME.\n if (turnIceServers.length > 0) {\n const pcConfig = peerConnection.getConfiguration();\n\n // only update ice servers when local config does not have ice servers.\n if (!instance.connectionConfig.iceServers) {\n pcConfig.iceServers = turnIceServers;\n console.log(logHeader, 'Updated Peer Connection ICE Servers from Link header');\n }\n\n // set ice transport policy to relay if not set in local config.\n if (!instance.connectionConfig.iceTransportPolicy) {\n\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n pcConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n pcConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n\n peerConnection.setConfiguration(pcConfig);\n }\n }\n\n const answer = await fetched.text();\n console.log(logHeader, 'Response Answer SDP: ', answer);\n\n try {\n await peerConnection.setLocalDescription(offer);\n } catch (error) {\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n }\n\n try {\n await peerConnection.setRemoteDescription({\n type: \"answer\",\n sdp: answer\n });\n } catch (error) {\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n }\n }\n\n async function stopWhip() {\n\n if (!instance.peerConnection) {\n console.error('No connection to close');\n errorHandler('No connection to close');\n return;\n }\n\n closePeerConnection();\n\n if (instance.resourceUrl) {\n\n const headers = {\n };\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n await fetchWithRedirect(instance.resourceUrl, {\n method: \"DELETE\",\n headers\n });\n }\n }\n\n function appendFmtp(sdp) {\n\n const fmtpStr = instance.connectionConfig.sdp.appendFmtp;\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n let fmtpLineFound = false;\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {\n fmtpLineFound = true;\n lines[j] += ';' + fmtpStr;\n }\n }\n\n if (!fmtpLineFound) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=fmtp:' + payloads[i] + ' ' + fmtpStr;\n }\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function appendOrientation(sdp) {\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function createPeerConnection(id, peerId, offer, candidates, iceServers) {\n\n let peerConnectionConfig = {};\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from user config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else if (iceServers) {\n\n // second priority using ice servers from ome and force using TCP\n peerConnectionConfig.iceServers = [];\n\n for (let i = 0; i < iceServers.length; i++) {\n\n let iceServer = iceServers[i];\n\n let regIceServer = {};\n\n regIceServer.urls = iceServer.urls;\n\n let hasWebSocketUrl = false;\n let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);\n\n for (let j = 0; j < regIceServer.urls.length; j++) {\n\n let serverUrl = regIceServer.urls[j];\n\n if (serverUrl.indexOf(webSocketUrl) > -1) {\n hasWebSocketUrl = true;\n break;\n }\n }\n\n if (!hasWebSocketUrl) {\n\n if (regIceServer.urls.length > 0) {\n\n let cloneIceServer = regIceServer.urls[0];\n let ip = findIp(cloneIceServer);\n\n if (webSocketUrl && ip) {\n regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));\n }\n }\n }\n\n regIceServer.username = iceServer.user_name;\n regIceServer.credential = iceServer.credential;\n\n peerConnectionConfig.iceServers.push(regIceServer);\n }\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n } else {\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n peerConnectionConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n peerConnectionConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n }\n\n let advancedSetting = {\n optional: [\n {\n googHighStartBitrate: {\n exact: !0\n }\n },\n {\n googPayloadPadding: {\n exact: !0\n }\n },\n {\n googScreencastMinBitrate: {\n exact: 500\n }\n },\n {\n enableDscp: {\n exact: true\n }\n }\n ]\n };\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n let peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n // set local stream\n instance.inputStream.getTracks().forEach(function (track) {\n\n console.info(logHeader, 'Add Track To Peer Connection', track);\n peerConnection.addTrack(track, instance.inputStream);\n });\n\n if (instance.connectionConfig.maxVideoBitrate) {\n\n // if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome\n offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);\n }\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n\n // offer.sdp = appendOrientation(offer.sdp);\n console.info(logHeader, 'Modified offer sdp\\n\\n' + offer.sdp);\n\n peerConnection.setRemoteDescription(new RTCSessionDescription(offer))\n .then(function () {\n\n peerConnection.createAnswer()\n .then(function (answer) {\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n answer.sdp = appendFmtp(answer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n console.info(logHeader, 'Modified answer sdp\\n\\n' + answer.sdp);\n\n peerConnection.setLocalDescription(answer)\n .then(function () {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'answer',\n sdp: answer\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.createAnswer', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n });\n\n if (candidates) {\n\n addIceCandidate(peerConnection, candidates);\n }\n\n peerConnection.onicecandidate = function (e) {\n\n if (e.candidate && e.candidate.candidate) {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'candidate',\n candidates: [e.candidate]\n });\n }\n };\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed' || state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n }\n\n function addIceCandidate(peerConnection, candidates) {\n\n for (let i = 0; i < candidates.length; i++) {\n\n if (candidates[i] && candidates[i].candidate) {\n\n let basicCandidate = candidates[i];\n\n peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))\n .then(function () {\n\n })\n .catch(function (error) {\n\n console.error('peerConnection.addIceCandidate', error);\n errorHandler(error);\n });\n }\n }\n }\n\n function closePeerConnection() {\n if (instance.peerConnection) {\n\n // remove tracks from peer connection\n instance.peerConnection.getSenders().forEach(function (sender) {\n instance.peerConnection.removeTrack(sender);\n });\n\n instance.peerConnection.close();\n instance.peerConnection = null;\n delete instance.peerConnection;\n }\n }\n\n function closeWebSocket() {\n\n if (instance.webSocket) {\n\n instance.webSocket.close();\n instance.webSocket = null;\n delete instance.webSocket;\n }\n }\n\n function closeInputStream() {\n // release video, audio stream\n if (instance.inputStream) {\n\n instance.inputStream.getTracks().forEach(track => {\n\n track.stop();\n instance.inputStream.removeTrack(track);\n });\n\n if (instance.videoElement) {\n instance.videoElement.srcObject = null;\n }\n\n instance.inputStream = null;\n delete instance.inputStream;\n }\n }\n\n // instance methods\n instance.attachMedia = function (videoElement) {\n\n instance.videoElement = videoElement;\n };\n\n instance.getUserMedia = function (constraints) {\n\n return getUserMedia(constraints);\n };\n\n instance.getDisplayMedia = function (constraints) {\n\n return getDisplayMedia(constraints);\n };\n\n instance.setMediaStream = function (stream) {\n\n return setMediaStream(stream);\n };\n\n instance.startStreaming = function (endpointUrl, connectionConfig) {\n\n console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);\n\n if (!endpointUrl) {\n console.error('endpointUrl is required');\n errorHandler('endpointUrl is required');\n return;\n }\n\n instance.endpointUrl = endpointUrl;\n\n if (connectionConfig) {\n instance.connectionConfig = connectionConfig;\n }\n\n try {\n\n const protocol = new URL(endpointUrl).protocol;\n\n if (protocol === 'wss:' || protocol === 'ws:') {\n\n instance.streamingMode = 'webrtc';\n initWebSocket(endpointUrl);\n } else if (protocol === 'https:' || protocol === 'http:') {\n\n instance.streamingMode = 'whip';\n startWhip(endpointUrl);\n } else {\n console.error('Invalid protocol', error);\n errorHandler(error);\n }\n\n } catch (error) {\n console.error('Cannot parse connection URL', error);\n errorHandler(error);\n }\n };\n\n instance.stopStreaming = async function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n\n await stopWhip();\n }\n\n if (instance.callbacks.connectionClosed) {\n console.log(logHeader, 'Connection closed by user');\n instance.callbacks.connectionClosed('user', 'Connection closed by user');\n }\n };\n\n instance.remove = function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n stopWhip();\n }\n\n closeInputStream();\n\n console.info(logEventHeader, 'Removed');\n\n };\n}\n\nOvenLiveKit.getVersion = function () {\n return version;\n}\n\n// static methods\nOvenLiveKit.create = function (options) {\n\n console.info(logEventHeader, 'Create WebRTC Input ' + version);\n\n let instance = {};\n\n instance.webSocketClosedByUser = false;\n\n initConfig(instance, options);\n addMethod(instance);\n\n return instance;\n};\n\nOvenLiveKit.getDevices = async function (type = 'both') {\n\n try {\n // First check both audio and video sources are available.\n await getStreamForDeviceCheck(type);\n } catch (e) {\n\n console.warn(logHeader, 'Can not find Video and Audio devices.', e);\n\n let videoFound = null;\n let audioFound = null;\n\n try {\n videoFound = await getStreamForDeviceCheck('video');\n } catch (e) {\n console.warn(logHeader, 'Can not find Video devices.', e);\n }\n\n try {\n audioFound = await getStreamForDeviceCheck('audio');\n } catch (e) {\n console.warn(logHeader, 'Can not find Audio devices.', e);\n }\n\n if (!videoFound && !audioFound) {\n throw new Error('No input devices were found.');\n }\n }\n\n const deviceInfos = await getDevices();\n return gotDevices(deviceInfos)\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (OvenLiveKit);\n\n\n//# sourceURL=webpack://OvenLiveKit/./src/OvenLiveKit.js?");
29
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nconst OvenLiveKit = {};\n\nconst version = '1.5.8';\nconst logHeader = 'OvenLiveKit.js :';\nconst logEventHeader = 'OvenLiveKit.js ====';\n\n// private methods\nfunction getTransportQuery(endpointUrl) {\n\n try {\n const url = new URL(endpointUrl);\n const transport = url.searchParams.get('transport');\n\n if (transport === null) {\n return undefined;\n }\n\n const normalized = transport.toLowerCase();\n\n return normalized;\n } catch (e) {\n return undefined;\n }\n}\n\nfunction sendMessage(webSocket, message) {\n\n if (webSocket) {\n webSocket.send(JSON.stringify(message));\n }\n}\n\nfunction generateDomainFromUrl(url) {\n let result = '';\n let match;\n if (match = url.match(/^(?:wss?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n\\?\\=]+)/im)) {\n result = match[1];\n }\n\n return result;\n}\n\nfunction findIp(string) {\n\n let result = '';\n let match;\n\n if (match = string.match(new RegExp('\\\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\b', 'gi'))) {\n result = match[0];\n }\n\n return result;\n}\n\nfunction getFormatNumber(sdp, format) {\n\n const lines = sdp.split('\\r\\n');\n let formatNumber = -1;\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n lines[i] = lines[i].toLowerCase();\n\n if (lines[i].indexOf('a=rtpmap') === 0 && lines[i].indexOf(format.toLowerCase()) > -1) {\n // parsing \"a=rtpmap:100 H264/90000\" line\n // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters >]\n formatNumber = lines[i].split(' ')[0].split(':')[1];\n break;\n }\n }\n\n return formatNumber;\n}\n\nfunction setPreferredVideoFormat(sdp, formatName) {\n\n const formatNumber = getFormatNumber(sdp, formatName);\n\n if (formatNumber === -1) {\n return sdp;\n }\n\n let newLines = [];\n const lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n const line = lines[i];\n\n if (line.indexOf('m=video') === 0) {\n\n // m=<media> <port>/<number of ports> <transport> <fmt list>\n const others = line.split(' ').slice(0, 3);\n const formats = line.split(' ').slice(3);\n formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });\n newLines.push(others.concat(formats).join(' '));\n } else {\n newLines.push(line);\n }\n\n }\n\n return newLines.join('\\r\\n') + '\\r\\n';\n}\n\nfunction removeFormat(sdp, formatNumber) {\n let newLines = [];\n let lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n newLines.push(lines[i].replace(' ' + formatNumber + '', ''));\n } else if (lines[i].indexOf(formatNumber + '') > -1) {\n\n } else {\n newLines.push(lines[i]);\n }\n }\n\n return newLines.join('\\r\\n')\n}\n\nasync function getStreamForDeviceCheck(type) {\n\n // High resolution video constraints makes browser to get maximum resolution of video device.\n const constraints = {\n };\n\n if (type === 'both') {\n constraints.audio = true;\n constraints.video = true;\n } else if (type === 'audio') {\n constraints.audio = true;\n } else if (type === 'video') {\n constraints.video = true;\n }\n\n const permissionStream = await navigator.mediaDevices.getUserMedia(constraints);\n permissionStream.getTracks().forEach(track => track.stop());\n await new Promise(resolve => setTimeout(resolve, 500));\n\n return true;\n}\n\nasync function getDevices() {\n\n return await navigator.mediaDevices.enumerateDevices();\n}\n\nfunction gotDevices(deviceInfos) {\n\n let devices = {\n 'audioinput': [],\n 'audiooutput': [],\n 'videoinput': [],\n 'other': [],\n };\n\n for (let i = 0; i !== deviceInfos.length; ++i) {\n\n const deviceInfo = deviceInfos[i];\n\n let info = {};\n\n info.deviceId = deviceInfo.deviceId;\n\n if (deviceInfo.kind === 'audioinput') {\n\n info.label = deviceInfo.label || `microphone ${devices.audioinput.length + 1}`;\n devices.audioinput.push(info);\n } else if (deviceInfo.kind === 'audiooutput') {\n\n info.label = deviceInfo.label || `speaker ${devices.audiooutput.length + 1}`;\n devices.audiooutput.push(info);\n } else if (deviceInfo.kind === 'videoinput') {\n\n info.label = deviceInfo.label || `camera ${devices.videoinput.length + 1}`;\n devices.videoinput.push(info);\n } else {\n\n info.label = deviceInfo.label || `other ${devices.other.length + 1}`;\n devices.other.push(info);\n }\n }\n\n return devices;\n}\n\nfunction initConfig(instance, options) {\n\n // webrtc or whip\n instance.streamingMode = null;\n\n instance.inputStream = null;\n instance.webSocket = null;\n instance.peerConnection = null;\n instance.connectionConfig = {};\n\n instance.videoElement = null;\n instance.endpointUrl = null;\n instance.resourceUrl = null;\n\n if (options && options.callbacks) {\n\n instance.callbacks = options.callbacks;\n } else {\n instance.callbacks = {};\n }\n}\n\nfunction addMethod(instance) {\n\n function errorHandler(error) {\n\n if (instance.callbacks.error) {\n\n instance.callbacks.error(error);\n }\n }\n\n async function fetchWithRedirect(url, options) {\n let fetched = await fetch(url, options);\n\n while (fetched.redirected) {\n url = fetched.url;\n fetched = await fetch(url, options);\n }\n\n return fetched;\n }\n\n function getUserMedia(constraints) {\n\n if (!constraints) {\n\n constraints = {\n video: {\n deviceId: undefined\n },\n audio: {\n deviceId: undefined\n }\n };\n }\n\n console.info(logHeader, 'Request Stream To Input Devices With Constraints', constraints);\n\n return navigator.mediaDevices.getUserMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Input Device', stream);\n\n stream.getVideoTracks().forEach(function (track) {\n console.info(logHeader, 'Video Track Settings From Input Stream', track.getSettings());\n });\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Input Device', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function getDisplayMedia(constraints) {\n\n if (!constraints) {\n constraints = {};\n }\n\n console.info(logHeader, 'Request Stream To Display With Constraints', constraints);\n\n return navigator.mediaDevices.getDisplayMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Display', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Display', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function setMediaStream(stream) {\n // Check if a valid stream is provided\n if (!stream || !(stream instanceof MediaStream)) {\n\n const error = new Error(\"Invalid MediaStream provided\");\n console.error(logHeader, 'Invalid MediaStream', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n }\n\n console.info(logHeader, 'Received Media Stream', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n resolve(stream);\n });\n }\n\n // From https://webrtchacks.com/limit-webrtc-bandwidth-sdp/\n function setBitrateLimit(sdp, media, bitrate) {\n\n let lines = sdp.split('\\r\\n');\n let line = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].indexOf('m=' + media) === 0) {\n line = i;\n break;\n }\n }\n if (line === -1) {\n // Could not find the m line for media\n return sdp;\n }\n\n // Pass the m line\n line++;\n\n // Skip i and c lines\n while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {\n\n line++;\n }\n\n // If we're on a b line, replace it\n if (lines[line].indexOf('b') === 0) {\n\n lines[line] = 'b=AS:' + bitrate;\n\n return lines.join('\\r\\n');\n }\n\n // Add a new b line\n let newLines = lines.slice(0, line)\n\n newLines.push('b=AS:' + bitrate)\n newLines = newLines.concat(lines.slice(line, lines.length))\n\n return newLines.join('\\r\\n')\n }\n\n function normalizeFrameRate(frameRate) {\n if (Number.isInteger(frameRate)) {\n return frameRate;\n }\n return parseFloat(frameRate).toFixed(2);\n }\n\n function initWebSocket(endpointUrl) {\n\n if (!endpointUrl) {\n errorHandler('endpointUrl is required');\n return;\n }\n\n let webSocket = null;\n\n try {\n\n webSocket = new WebSocket(endpointUrl);\n } catch (error) {\n\n errorHandler(error);\n }\n\n\n instance.webSocket = webSocket;\n\n webSocket.onopen = function () {\n\n // Request offer at the first time.\n sendMessage(webSocket, {\n command: 'request_offer'\n });\n };\n\n webSocket.onmessage = function (e) {\n\n let message = JSON.parse(e.data);\n\n if (message.error) {\n console.error('webSocket.onmessage', message.error);\n errorHandler(message.error);\n }\n\n if (message.command === 'offer') {\n\n // OME returns offer. Start create peer connection.\n createPeerConnection(\n message.id,\n message.peer_id,\n message.sdp,\n message.candidates,\n message.ice_servers\n );\n }\n };\n\n webSocket.onerror = function (error) {\n\n console.error('webSocket.onerror', error);\n errorHandler(error);\n };\n\n webSocket.onclose = function (e) {\n\n if (!instance.webSocketClosedByUser) {\n\n if (instance.callbacks.connectionClosed) {\n instance.callbacks.connectionClosed('websocket', e);\n }\n }\n };\n\n }\n\n async function startWhip(endpointUrl) {\n\n if (instance.peerConnection) {\n console.error('Connection already established');\n errorHandler('Connection already established');\n return;\n }\n\n const peerConnectionConfig = {\n bundlePolicy: \"max-bundle\"\n };\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from local config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else {\n // last priority using default ice servers.\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n }\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n const peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n if (!instance.inputStream) {\n console.error('No input stream in OvenLiveKit');\n errorHandler('No input stream in OvenLiveKit');\n return;\n }\n\n for (const track of instance.inputStream.getTracks()) {\n console.log(logHeader, 'Adding track: ', track);\n\n const transceiverConfig = {\n direction: 'sendonly'\n };\n\n // Add simulcast layers if configured\n const simulcastConfig = instance.connectionConfig.simulcast;\n\n if (track.kind === 'video' && simulcastConfig && simulcastConfig.length > 0) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n for (let i = 0; i < simulcastConfig.length; i++) {\n\n const layer = {\n rid: i,\n active: true,\n // VP9/AV1 simulcast in Chrome requires an explicit scalabilityMode together with\n // scaleResolutionDownBy; without it, multiple encodings fall back to legacy SVC and\n // extra rids are dropped. Default to L1T1 (overridable via simulcastConfig[i]).\n // Multiple active encodings only allow L1T1/L1T2/L1T3 and all layers must share the same value.\n scalabilityMode: 'L1T1',\n ...simulcastConfig[i]\n };\n\n console.log(logHeader, `Adding simulcast layer to: ${track.kind}`, layer);\n\n transceiverConfig.sendEncodings.push(layer);\n }\n }\n\n if (track.kind === 'video' && instance.connectionConfig.maxVideoBitrate) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n transceiverConfig.sendEncodings.push({\n maxBitrate: instance.connectionConfig.maxVideoBitrate * 1000,\n });\n\n console.log(logHeader, `Setting max bitrate to: ${instance.connectionConfig.maxVideoBitrate} kbps`);\n }\n\n peerConnection.addTransceiver(track, transceiverConfig);\n }\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection failed', e);\n instance.callbacks.errorHandler(e);\n }\n }\n\n if (state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection disconnected or closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n\n const offer = await peerConnection.createOffer();\n console.log(logHeader, 'Offer SDP from browser: ', offer.sdp);\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat);\n } else {\n // default to H264\n offer.sdp = setPreferredVideoFormat(offer.sdp, 'H264');\n }\n\n const headers = {\n \"Content-Type\": \"application/sdp\"\n };\n\n // Set Oven-Capabilities header based on video track settings and simulcast layers.\n const videoTracks = instance.inputStream.getVideoTracks();\n\n if (videoTracks && videoTracks.length === 1) {\n\n const track = videoTracks[0];\n const settings = track.getSettings();\n\n console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);\n\n const width = settings.width;\n const height = settings.height;\n const frameRate = settings.frameRate;\n\n const simulcastConfig = instance.connectionConfig.simulcast;\n const layerCount = simulcastConfig ? simulcastConfig.length : 0;\n\n if (layerCount >= 2) {\n\n const parts = [];\n\n for (let i = 0; i < layerCount; i++) {\n\n const layer = simulcastConfig[i] || {};\n const rid = layer.rid !== undefined ? layer.rid : i;\n\n const scale = parseFloat(layer.scaleResolutionDownBy);\n\n if (!isNaN(scale) && scale > 0 && typeof width === 'number' && typeof height === 'number') {\n const scaledWidth = Math.floor(Math.floor(width / scale) / 2) * 2;\n const scaledHeight = Math.floor(Math.floor(height / scale) / 2) * 2;\n\n parts.push(`rid:${rid}:max_width=${scaledWidth}`);\n parts.push(`rid:${rid}:max_height=${scaledHeight}`);\n }\n\n if (frameRate) {\n parts.push(`rid:${rid}:max_fps=${normalizeFrameRate(frameRate)}`);\n }\n }\n\n if (parts.length > 0) {\n console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);\n headers['Oven-Capabilities'] = parts.join(', ');\n }\n } else {\n\n if (typeof width === 'number' && typeof height === 'number') {\n\n const parts = [`max_width=${width}`, `max_height=${height}`];\n\n if (frameRate) {\n parts.push(`max_fps=${normalizeFrameRate(frameRate)}`);\n }\n\n console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);\n headers['Oven-Capabilities'] = parts.join(', ');\n }\n }\n } else {\n\n if (!videoTracks || videoTracks.length === 0) {\n console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');\n }\n\n if (videoTracks && videoTracks.length > 1) {\n console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);\n }\n }\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n console.log(logHeader, 'Offer SDP modified by OvenLiveKit: ', offer.sdp);\n\n const fetched = await fetchWithRedirect(endpointUrl, {\n method: \"POST\",\n body: offer.sdp,\n headers\n });\n\n if (!fetched.ok) {\n console.error('Failed to fetch', fetched.status);\n errorHandler(`Failed to fetch ${fetched.status}`);\n closePeerConnection();\n return;\n }\n\n if (!fetched.headers.get(\"location\")) {\n console.error('No location header on answer response');\n errorHandler('No location header on answer response');\n return;\n }\n\n // update endpointUrl\n instance.endpointUrl = fetched.url;\n console.log(logHeader, 'Endpoint Url: ', instance.endpointUrl);\n\n const locationHeaderValue = fetched.headers.get(\"location\");\n console.log(logHeader, 'Location header ', locationHeaderValue);\n\n instance.resourceUrl = new URL(locationHeaderValue, fetched.url).toString();\n console.log(logHeader, 'Resource URL: ', instance.resourceUrl);\n\n\n if (fetched.headers.get(\"Link\")) {\n console.log(logHeader, 'Link Header Found. Update ');\n\n const links = fetched.headers.get(\"Link\").split(',').map(link => link.trim());\n\n // OvenMediaEngine returns turn servers in Link header with rel=\"ice-server\".\n // For example: <turn:119.196.229.2:3478?transport=tcp>; rel=\"ice-server\"; username=\"user\"; credential=\"cred\"; credential-type=\"password\"\n const turnIceServers = [];\n\n links.forEach(link => {\n\n console.log(logHeader, 'Link: ', link);\n const urlsMatch = link.match(/<([^>]+)>/);\n const relMatch = link.match(/rel=\"([^\"]+)\"/);\n const usernameMatch = link.match(/username=\"([^\"]+)\"/);\n const credentialMatch = link.match(/credential=\"([^\"]+)\"/);\n const credentialTypeMatch = link.match(/credential-type=\"([^\"]+)\"/);\n\n if (urlsMatch && relMatch && relMatch[1] === 'ice-server') {\n\n const iceServer = {\n urls: urlsMatch[1]\n };\n\n if (usernameMatch) {\n iceServer.username = usernameMatch[1];\n };\n\n if (credentialMatch) {\n iceServer.credential = credentialMatch[1];\n };\n\n if (credentialTypeMatch) {\n iceServer.credentialType = credentialTypeMatch[1];\n }\n\n turnIceServers.push(iceServer);\n }\n });\n\n console.log(logHeader, 'Parsed ICE Servers from Link header: ', turnIceServers);\n\n // update peer connection with new turn servers from OME.\n if (turnIceServers.length > 0) {\n const pcConfig = peerConnection.getConfiguration();\n\n // only update ice servers when local config does not have ice servers.\n if (!instance.connectionConfig.iceServers) {\n pcConfig.iceServers = turnIceServers;\n console.log(logHeader, 'Updated Peer Connection ICE Servers from Link header');\n }\n\n // set ice transport policy to relay if not set in local config.\n if (!instance.connectionConfig.iceTransportPolicy) {\n\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n pcConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n pcConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n\n peerConnection.setConfiguration(pcConfig);\n }\n }\n\n const answer = await fetched.text();\n console.log(logHeader, 'Response Answer SDP: ', answer);\n\n try {\n await peerConnection.setLocalDescription(offer);\n } catch (error) {\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n }\n\n try {\n await peerConnection.setRemoteDescription({\n type: \"answer\",\n sdp: answer\n });\n } catch (error) {\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n }\n }\n\n async function stopWhip() {\n\n if (!instance.peerConnection) {\n console.error('No connection to close');\n errorHandler('No connection to close');\n return;\n }\n\n closePeerConnection();\n\n if (instance.resourceUrl) {\n\n const headers = {\n };\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n await fetchWithRedirect(instance.resourceUrl, {\n method: \"DELETE\",\n headers\n });\n }\n }\n\n function appendFmtp(sdp) {\n\n const fmtpStr = instance.connectionConfig.sdp.appendFmtp;\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n let fmtpLineFound = false;\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {\n fmtpLineFound = true;\n lines[j] += ';' + fmtpStr;\n }\n }\n\n if (!fmtpLineFound) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=fmtp:' + payloads[i] + ' ' + fmtpStr;\n }\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function appendOrientation(sdp) {\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function createPeerConnection(id, peerId, offer, candidates, iceServers) {\n\n let peerConnectionConfig = {};\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from user config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else if (iceServers) {\n\n // second priority using ice servers from ome and force using TCP\n peerConnectionConfig.iceServers = [];\n\n for (let i = 0; i < iceServers.length; i++) {\n\n let iceServer = iceServers[i];\n\n let regIceServer = {};\n\n regIceServer.urls = iceServer.urls;\n\n let hasWebSocketUrl = false;\n let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);\n\n for (let j = 0; j < regIceServer.urls.length; j++) {\n\n let serverUrl = regIceServer.urls[j];\n\n if (serverUrl.indexOf(webSocketUrl) > -1) {\n hasWebSocketUrl = true;\n break;\n }\n }\n\n if (!hasWebSocketUrl) {\n\n if (regIceServer.urls.length > 0) {\n\n let cloneIceServer = regIceServer.urls[0];\n let ip = findIp(cloneIceServer);\n\n if (webSocketUrl && ip) {\n regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));\n }\n }\n }\n\n regIceServer.username = iceServer.user_name;\n regIceServer.credential = iceServer.credential;\n\n peerConnectionConfig.iceServers.push(regIceServer);\n }\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n } else {\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n peerConnectionConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n peerConnectionConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n }\n\n let advancedSetting = {\n optional: [\n {\n googHighStartBitrate: {\n exact: !0\n }\n },\n {\n googPayloadPadding: {\n exact: !0\n }\n },\n {\n googScreencastMinBitrate: {\n exact: 500\n }\n },\n {\n enableDscp: {\n exact: true\n }\n }\n ]\n };\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n let peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n // set local stream\n instance.inputStream.getTracks().forEach(function (track) {\n\n console.info(logHeader, 'Add Track To Peer Connection', track);\n peerConnection.addTrack(track, instance.inputStream);\n });\n\n if (instance.connectionConfig.maxVideoBitrate) {\n\n // if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome\n offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);\n }\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n\n // offer.sdp = appendOrientation(offer.sdp);\n console.info(logHeader, 'Modified offer sdp\\n\\n' + offer.sdp);\n\n peerConnection.setRemoteDescription(new RTCSessionDescription(offer))\n .then(function () {\n\n peerConnection.createAnswer()\n .then(function (answer) {\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n answer.sdp = appendFmtp(answer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n console.info(logHeader, 'Modified answer sdp\\n\\n' + answer.sdp);\n\n peerConnection.setLocalDescription(answer)\n .then(function () {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'answer',\n sdp: answer\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.createAnswer', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n });\n\n if (candidates) {\n\n addIceCandidate(peerConnection, candidates);\n }\n\n peerConnection.onicecandidate = function (e) {\n\n if (e.candidate && e.candidate.candidate) {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'candidate',\n candidates: [e.candidate]\n });\n }\n };\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed' || state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n }\n\n function addIceCandidate(peerConnection, candidates) {\n\n for (let i = 0; i < candidates.length; i++) {\n\n if (candidates[i] && candidates[i].candidate) {\n\n let basicCandidate = candidates[i];\n\n peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))\n .then(function () {\n\n })\n .catch(function (error) {\n\n console.error('peerConnection.addIceCandidate', error);\n errorHandler(error);\n });\n }\n }\n }\n\n function closePeerConnection() {\n if (instance.peerConnection) {\n\n // remove tracks from peer connection\n instance.peerConnection.getSenders().forEach(function (sender) {\n instance.peerConnection.removeTrack(sender);\n });\n\n instance.peerConnection.close();\n instance.peerConnection = null;\n delete instance.peerConnection;\n }\n }\n\n function closeWebSocket() {\n\n if (instance.webSocket) {\n\n instance.webSocket.close();\n instance.webSocket = null;\n delete instance.webSocket;\n }\n }\n\n function closeInputStream() {\n // release video, audio stream\n if (instance.inputStream) {\n\n instance.inputStream.getTracks().forEach(track => {\n\n track.stop();\n instance.inputStream.removeTrack(track);\n });\n\n if (instance.videoElement) {\n instance.videoElement.srcObject = null;\n }\n\n instance.inputStream = null;\n delete instance.inputStream;\n }\n }\n\n // instance methods\n instance.attachMedia = function (videoElement) {\n\n instance.videoElement = videoElement;\n };\n\n instance.getUserMedia = function (constraints) {\n\n return getUserMedia(constraints);\n };\n\n instance.getDisplayMedia = function (constraints) {\n\n return getDisplayMedia(constraints);\n };\n\n instance.setMediaStream = function (stream) {\n\n return setMediaStream(stream);\n };\n\n instance.startStreaming = function (endpointUrl, connectionConfig) {\n\n console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);\n\n if (!endpointUrl) {\n console.error('endpointUrl is required');\n errorHandler('endpointUrl is required');\n return;\n }\n\n instance.endpointUrl = endpointUrl;\n\n if (connectionConfig) {\n instance.connectionConfig = connectionConfig;\n }\n\n try {\n\n const protocol = new URL(endpointUrl).protocol;\n\n if (protocol === 'wss:' || protocol === 'ws:') {\n\n instance.streamingMode = 'webrtc';\n initWebSocket(endpointUrl);\n } else if (protocol === 'https:' || protocol === 'http:') {\n\n instance.streamingMode = 'whip';\n startWhip(endpointUrl);\n } else {\n console.error('Invalid protocol', error);\n errorHandler(error);\n }\n\n } catch (error) {\n console.error('Cannot parse connection URL', error);\n errorHandler(error);\n }\n };\n\n instance.stopStreaming = async function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n\n await stopWhip();\n }\n\n if (instance.callbacks.connectionClosed) {\n console.log(logHeader, 'Connection closed by user');\n instance.callbacks.connectionClosed('user', 'Connection closed by user');\n }\n };\n\n instance.remove = function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n stopWhip();\n }\n\n closeInputStream();\n\n console.info(logEventHeader, 'Removed');\n\n };\n}\n\nOvenLiveKit.getVersion = function () {\n return version;\n}\n\n// static methods\nOvenLiveKit.create = function (options) {\n\n console.info(logEventHeader, 'Create WebRTC Input ' + version);\n\n let instance = {};\n\n instance.webSocketClosedByUser = false;\n\n initConfig(instance, options);\n addMethod(instance);\n\n return instance;\n};\n\nOvenLiveKit.getDevices = async function (type = 'both') {\n\n try {\n // First check both audio and video sources are available.\n await getStreamForDeviceCheck(type);\n } catch (e) {\n\n console.warn(logHeader, 'Can not find Video and Audio devices.', e);\n\n let videoFound = null;\n let audioFound = null;\n\n try {\n videoFound = await getStreamForDeviceCheck('video');\n } catch (e) {\n console.warn(logHeader, 'Can not find Video devices.', e);\n }\n\n try {\n audioFound = await getStreamForDeviceCheck('audio');\n } catch (e) {\n console.warn(logHeader, 'Can not find Audio devices.', e);\n }\n\n if (!videoFound && !audioFound) {\n throw new Error('No input devices were found.');\n }\n }\n\n const deviceInfos = await getDevices();\n return gotDevices(deviceInfos)\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (OvenLiveKit);\n\n\n//# sourceURL=webpack://OvenLiveKit/./src/OvenLiveKit.js?");
30
30
 
31
31
  /***/ })
32
32
 
@@ -1,2 +1,2 @@
1
- !function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.OvenLiveKit=n():e.OvenLiveKit=n()}(self,(()=>(()=>{"use strict";var e={d:(n,o)=>{for(var t in o)e.o(o,t)&&!e.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:o[t]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n)},n={};e.d(n,{default:()=>u});const o={},t="1.5.6",i="OvenLiveKit.js :",c="OvenLiveKit.js ====";function r(e){try{const n=new URL(e).searchParams.get("transport");if(null===n)return;return n.toLowerCase()}catch(e){return}}function a(e,n){e&&e.send(JSON.stringify(n))}function s(e){let n,o="";return(n=e.match(/^(?:wss?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im))&&(o=n[1]),o}function l(e){let n,o="";return(n=e.match(new RegExp("\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b","gi")))&&(o=n[0]),o}function d(e,n){const o=function(e,n){const o=e.split("\r\n");let t=-1;for(let e=0;e<o.length-1;e++)if(o[e]=o[e].toLowerCase(),0===o[e].indexOf("a=rtpmap")&&o[e].indexOf(n.toLowerCase())>-1){t=o[e].split(" ")[0].split(":")[1];break}return t}(e,n);if(-1===o)return e;let t=[];const i=e.split("\r\n");for(let e=0;e<i.length-1;e++){const n=i[e];if(0===n.indexOf("m=video")){const e=n.split(" ").slice(0,3),i=n.split(" ").slice(3);i.sort((function(e,n){return e==o?-1:n==o?1:0})),t.push(e.concat(i).join(" "))}else t.push(n)}return t.join("\r\n")+"\r\n"}async function f(e){const n={};return"both"===e?(n.audio=!0,n.video=!0):"audio"===e?n.audio=!0:"video"===e&&(n.video=!0),(await navigator.mediaDevices.getUserMedia(n)).getTracks().forEach((e=>e.stop())),await new Promise((e=>setTimeout(e,500))),!0}function p(e){function n(n){e.callbacks.error&&e.callbacks.error(n)}async function o(e,n){let o=await fetch(e,n);for(;o.redirected;)e=o.url,o=await fetch(e,n);return o}function t(e){return Number.isInteger(e)?e:parseFloat(e).toFixed(2)}async function f(){if(!e.peerConnection)return console.error("No connection to close"),void n("No connection to close");if(u(),e.resourceUrl){const n={};e.connectionConfig.httpHeaders&&Object.assign(n,e.connectionConfig.httpHeaders),await o(e.resourceUrl,{method:"DELETE",headers:n})}}function p(n){const o=e.connectionConfig.sdp.appendFmtp,t=n.split("\r\n"),i=[];for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){let n=t[e].split(" ");for(let e=3;e<n.length;e++)i.push(n[e]);break}for(let e=0;e<i.length;e++){let n=!1;for(let c=0;c<t.length;c++)0===t[c].indexOf("a=fmtp:"+i[e])&&(n=!0,t[c]+=";"+o);if(!n)for(let n=0;n<t.length;n++)0===t[n].indexOf("a=rtpmap:"+i[e])&&(t[n]+="\r\na=fmtp:"+i[e]+" "+o)}return t.join("\r\n")}function u(){e.peerConnection&&(e.peerConnection.getSenders().forEach((function(n){e.peerConnection.removeTrack(n)})),e.peerConnection.close(),e.peerConnection=null,delete e.peerConnection)}function g(){e.webSocket&&(e.webSocket.close(),e.webSocket=null,delete e.webSocket)}e.attachMedia=function(n){e.videoElement=n},e.getUserMedia=function(o){return function(o){return o||(o={video:{deviceId:void 0},audio:{deviceId:void 0}}),console.info(i,"Request Stream To Input Devices With Constraints",o),navigator.mediaDevices.getUserMedia(o).then((function(n){console.info(i,"Received Media Stream From Input Device",n),n.getVideoTracks().forEach((function(e){console.info(i,"Video Track Settings From Input Stream",e.getSettings())})),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Input Device",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.getDisplayMedia=function(o){return function(o){return o||(o={}),console.info(i,"Request Stream To Display With Constraints",o),navigator.mediaDevices.getDisplayMedia(o).then((function(n){console.info(i,"Received Media Stream From Display",n),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Display",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.setMediaStream=function(o){return function(o){if(!(o&&o instanceof MediaStream)){const e=new Error("Invalid MediaStream provided");return console.error(i,"Invalid MediaStream",e),n(e),new Promise((function(n,o){o(e)}))}console.info(i,"Received Media Stream",o),e.inputStream=o;let t=e.videoElement;return t&&(t.srcObject=o,t.onloadedmetadata=function(e){t.play()}),new Promise((function(e){e(o)}))}(o)},e.startStreaming=function(f,g){if(console.info(c,`Start Streaming to ${f} with connectionConfig`,g),!f)return console.error("endpointUrl is required"),void n("endpointUrl is required");e.endpointUrl=f,g&&(e.connectionConfig=g);try{const c=new URL(f).protocol;"wss:"===c||"ws:"===c?(e.streamingMode="webrtc",function(o){if(!o)return void n("endpointUrl is required");let t=null;try{t=new WebSocket(o)}catch(e){n(e)}e.webSocket=t,t.onopen=function(){a(t,{command:"request_offer"})},t.onmessage=function(o){let t=JSON.parse(o.data);t.error&&(console.error("webSocket.onmessage",t.error),n(t.error)),"offer"===t.command&&function(o,t,c,f,u){let g={};if(e.connectionConfig.iceServers)g.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy);else if(u){g.iceServers=[];for(let n=0;n<u.length;n++){let o=u[n],t={};t.urls=o.urls;let i=!1,c=s(e.endpointUrl);for(let e=0;e<t.urls.length;e++)if(t.urls[e].indexOf(c)>-1){i=!0;break}if(!i&&t.urls.length>0){let e=t.urls[0],n=l(e);c&&n&&t.urls.push(e.replace(n,c))}t.username=o.user_name,t.credential=o.credential,g.iceServers.push(t)}e.connectionConfig.iceTransportPolicy?g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy:"all"==r(e.endpointUrl)?(g.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(g.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))}console.info(i,"Create Peer Connection With Config",g);let m=new RTCPeerConnection(g);e.peerConnection=m,e.inputStream.getTracks().forEach((function(n){console.info(i,"Add Track To Peer Connection",n),m.addTrack(n,e.inputStream)})),e.connectionConfig.maxVideoBitrate&&(c.sdp=function(e,n,o){let t=e.split("\r\n"),i=-1;for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){i=e;break}if(-1===i)return e;for(i++;0===t[i].indexOf("i=")||0===t[i].indexOf("c=");)i++;if(0===t[i].indexOf("b"))return t[i]="b=AS:"+o,t.join("\r\n");let c=t.slice(0,i);return c.push("b=AS:"+o),c=c.concat(t.slice(i,t.length)),c.join("\r\n")}(c.sdp,0,e.connectionConfig.maxVideoBitrate)),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified offer sdp\n\n"+c.sdp),m.setRemoteDescription(new RTCSessionDescription(c)).then((function(){m.createAnswer().then((function(c){e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified answer sdp\n\n"+c.sdp),m.setLocalDescription(c).then((function(){a(e.webSocket,{id:o,peer_id:t,command:"answer",sdp:c})})).catch((function(e){console.error("peerConnection.setLocalDescription",e),n(e)}))})).catch((function(e){console.error("peerConnection.createAnswer",e),n(e)}))})).catch((function(e){console.error("peerConnection.setRemoteDescription",e),n(e)})),f&&function(e,o){for(let t=0;t<o.length;t++)if(o[t]&&o[t].candidate){let i=o[t];e.addIceCandidate(new RTCIceCandidate(i)).then((function(){})).catch((function(e){console.error("peerConnection.addIceCandidate",e),n(e)}))}}(m,f),m.onicecandidate=function(n){n.candidate&&n.candidate.candidate&&a(e.webSocket,{id:o,peer_id:t,command:"candidate",candidates:[n.candidate]})},m.oniceconnectionstatechange=function(n){let o=m.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"!==o&&"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection closed",n),e.callbacks.connectionClosed("ice",n))}}(t.id,t.peer_id,t.sdp,t.candidates,t.ice_servers)},t.onerror=function(e){console.error("webSocket.onerror",e),n(e)},t.onclose=function(n){e.webSocketClosedByUser||e.callbacks.connectionClosed&&e.callbacks.connectionClosed("websocket",n)}}(f)):"https:"===c||"http:"===c?(e.streamingMode="whip",async function(c){if(e.peerConnection)return console.error("Connection already established"),void n("Connection already established");const a={bundlePolicy:"max-bundle"};e.connectionConfig.iceServers?(a.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy)):e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy),console.info(i,"Create Peer Connection With Config",a);const s=new RTCPeerConnection(a);if(e.peerConnection=s,!e.inputStream)return console.error("No input stream in OvenLiveKit"),void n("No input stream in OvenLiveKit");for(const n of e.inputStream.getTracks()){console.log(i,"Adding track: ",n);const o={direction:"sendonly"},t=e.connectionConfig.simulcast;if("video"===n.kind&&t&&t.length>0){o.sendEncodings||(o.sendEncodings=[]);for(let e=0;e<t.length;e++){const c={rid:e,active:!0,...t[e]};console.log(i,`Adding simulcast layer to: ${n.kind}`,c),o.sendEncodings.push(c)}}"video"===n.kind&&e.connectionConfig.maxVideoBitrate&&(o.sendEncodings||(o.sendEncodings=[]),o.sendEncodings.push({maxBitrate:1e3*e.connectionConfig.maxVideoBitrate}),console.log(i,`Setting max bitrate to: ${e.connectionConfig.maxVideoBitrate} kbps`)),s.addTransceiver(n,o)}s.oniceconnectionstatechange=function(n){let o=s.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"===o&&e.callbacks.connectionClosed&&(console.error(i,"Ice connection failed",n),e.callbacks.errorHandler(n)),"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection disconnected or closed",n),e.callbacks.connectionClosed("ice",n))};const l=await s.createOffer();console.log(i,"Offer SDP: ",l.sdp),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(l.sdp=p(l.sdp)),e.connectionConfig.preferredVideoFormat?l.sdp=d(l.sdp,e.connectionConfig.preferredVideoFormat):l.sdp=d(l.sdp,"H264");const f={"Content-Type":"application/sdp"};if(!e.connectionConfig.simulcast||e.connectionConfig.simulcast.length<2){const n=e.inputStream.getVideoTracks();if(n&&1===n.length)for(let e=0;e<n.length;e++){const o=n[e].getSettings();console.log(i,"Video track settings for Oven-Capabilities:",o);const c=o.width,r=o.height,a=o.frameRate;"number"==typeof c&&"number"==typeof r&&(console.log(i,`Setting Oven-Capabilities header: max_width=${c}, max_height=${r}, max_fps=${t(a)}`),f["Oven-Capabilities"]=`max_width=${c}, max_height=${r}, max_fps=${t(a)}`)}else n&&0!==n.length||console.log(i,"No video tracks found, skipping Oven-Capabilities header."),n&&n.length>1&&console.log(i,`Multiple (${n.length}) video tracks found, skipping Oven-Capabilities header.`)}else console.log(i,"Over two simulcast layers are set, skipping Oven-Capabilities header.");e.connectionConfig.httpHeaders&&Object.assign(f,e.connectionConfig.httpHeaders);const g=await o(c,{method:"POST",body:l.sdp,headers:f});if(!g.ok)return console.error("Failed to fetch",g.status),n(`Failed to fetch ${g.status}`),void u();if(!g.headers.get("location"))return console.error("No location header on answer response"),void n("No location header on answer response");e.endpointUrl=g.url,console.log(i,"Endpoint Url: ",e.endpointUrl);const m=g.headers.get("location");if(console.log(i,"Location header ",m),e.resourceUrl=new URL(m,g.url).toString(),console.log(i,"Resource URL: ",e.resourceUrl),g.headers.get("Link")){console.log(i,"Link Header Found. Update ");const n=g.headers.get("Link").split(",").map((e=>e.trim())),o=[];if(n.forEach((e=>{console.log(i,"Link: ",e);const n=e.match(/<([^>]+)>/),t=e.match(/rel="([^"]+)"/),c=e.match(/username="([^"]+)"/),r=e.match(/credential="([^"]+)"/),a=e.match(/credential-type="([^"]+)"/);if(n&&t&&"ice-server"===t[1]){const e={urls:n[1]};c&&(e.username=c[1]),r&&(e.credential=r[1]),a&&(e.credentialType=a[1]),o.push(e)}})),console.log(i,"Parsed ICE Servers from Link header: ",o),o.length>0){const n=s.getConfiguration();e.connectionConfig.iceServers||(n.iceServers=o,console.log(i,"Updated Peer Connection ICE Servers from Link header")),e.connectionConfig.iceTransportPolicy||("all"==r(e.endpointUrl)?(n.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(n.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))),s.setConfiguration(n)}}const h=await g.text();console.log(i,"Response Answer SDP: ",h);try{await s.setLocalDescription(l)}catch(e){console.error("peerConnection.setLocalDescription",e),n(e)}try{await s.setRemoteDescription({type:"answer",sdp:h})}catch(e){console.error("peerConnection.setRemoteDescription",e),n(e)}}(f)):(console.error("Invalid protocol",error),n(error))}catch(e){console.error("Cannot parse connection URL",e),n(e)}},e.stopStreaming=async function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&await f(),e.callbacks.connectionClosed&&(console.log(i,"Connection closed by user"),e.callbacks.connectionClosed("user","Connection closed by user"))},e.remove=function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&f(),e.inputStream&&(e.inputStream.getTracks().forEach((n=>{n.stop(),e.inputStream.removeTrack(n)})),e.videoElement&&(e.videoElement.srcObject=null),e.inputStream=null,delete e.inputStream),console.info(c,"Removed")}}o.getVersion=function(){return t},o.create=function(e){console.info(c,"Create WebRTC Input "+t);let n={webSocketClosedByUser:!1};return function(e,n){e.streamingMode=null,e.inputStream=null,e.webSocket=null,e.peerConnection=null,e.connectionConfig={},e.videoElement=null,e.endpointUrl=null,e.resourceUrl=null,n&&n.callbacks?e.callbacks=n.callbacks:e.callbacks={}}(n,e),p(n),n},o.getDevices=async function(e="both"){try{await f(e)}catch(e){console.warn(i,"Can not find Video and Audio devices.",e);let n=null,o=null;try{n=await f("video")}catch(e){console.warn(i,"Can not find Video devices.",e)}try{o=await f("audio")}catch(e){console.warn(i,"Can not find Audio devices.",e)}if(!n&&!o)throw new Error("No input devices were found.")}return function(e){let n={audioinput:[],audiooutput:[],videoinput:[],other:[]};for(let o=0;o!==e.length;++o){const t=e[o];let i={};i.deviceId=t.deviceId,"audioinput"===t.kind?(i.label=t.label||`microphone ${n.audioinput.length+1}`,n.audioinput.push(i)):"audiooutput"===t.kind?(i.label=t.label||`speaker ${n.audiooutput.length+1}`,n.audiooutput.push(i)):"videoinput"===t.kind?(i.label=t.label||`camera ${n.videoinput.length+1}`,n.videoinput.push(i)):(i.label=t.label||`other ${n.other.length+1}`,n.other.push(i))}return n}(await async function(){return await navigator.mediaDevices.enumerateDevices()}())};const u=o;return n.default})()));
1
+ !function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.OvenLiveKit=n():e.OvenLiveKit=n()}(self,(()=>(()=>{"use strict";var e={d:(n,o)=>{for(var t in o)e.o(o,t)&&!e.o(n,t)&&Object.defineProperty(n,t,{enumerable:!0,get:o[t]})},o:(e,n)=>Object.prototype.hasOwnProperty.call(e,n)},n={};e.d(n,{default:()=>u});const o={},t="1.5.8",i="OvenLiveKit.js :",c="OvenLiveKit.js ====";function r(e){try{const n=new URL(e).searchParams.get("transport");if(null===n)return;return n.toLowerCase()}catch(e){return}}function a(e,n){e&&e.send(JSON.stringify(n))}function s(e){let n,o="";return(n=e.match(/^(?:wss?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im))&&(o=n[1]),o}function l(e){let n,o="";return(n=e.match(new RegExp("\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b","gi")))&&(o=n[0]),o}function d(e,n){const o=function(e,n){const o=e.split("\r\n");let t=-1;for(let e=0;e<o.length-1;e++)if(o[e]=o[e].toLowerCase(),0===o[e].indexOf("a=rtpmap")&&o[e].indexOf(n.toLowerCase())>-1){t=o[e].split(" ")[0].split(":")[1];break}return t}(e,n);if(-1===o)return e;let t=[];const i=e.split("\r\n");for(let e=0;e<i.length-1;e++){const n=i[e];if(0===n.indexOf("m=video")){const e=n.split(" ").slice(0,3),i=n.split(" ").slice(3);i.sort((function(e,n){return e==o?-1:n==o?1:0})),t.push(e.concat(i).join(" "))}else t.push(n)}return t.join("\r\n")+"\r\n"}async function f(e){const n={};return"both"===e?(n.audio=!0,n.video=!0):"audio"===e?n.audio=!0:"video"===e&&(n.video=!0),(await navigator.mediaDevices.getUserMedia(n)).getTracks().forEach((e=>e.stop())),await new Promise((e=>setTimeout(e,500))),!0}function p(e){function n(n){e.callbacks.error&&e.callbacks.error(n)}async function o(e,n){let o=await fetch(e,n);for(;o.redirected;)e=o.url,o=await fetch(e,n);return o}function t(e){return Number.isInteger(e)?e:parseFloat(e).toFixed(2)}async function f(){if(!e.peerConnection)return console.error("No connection to close"),void n("No connection to close");if(u(),e.resourceUrl){const n={};e.connectionConfig.httpHeaders&&Object.assign(n,e.connectionConfig.httpHeaders),await o(e.resourceUrl,{method:"DELETE",headers:n})}}function p(n){const o=e.connectionConfig.sdp.appendFmtp,t=n.split("\r\n"),i=[];for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){let n=t[e].split(" ");for(let e=3;e<n.length;e++)i.push(n[e]);break}for(let e=0;e<i.length;e++){let n=!1;for(let c=0;c<t.length;c++)0===t[c].indexOf("a=fmtp:"+i[e])&&(n=!0,t[c]+=";"+o);if(!n)for(let n=0;n<t.length;n++)0===t[n].indexOf("a=rtpmap:"+i[e])&&(t[n]+="\r\na=fmtp:"+i[e]+" "+o)}return t.join("\r\n")}function u(){e.peerConnection&&(e.peerConnection.getSenders().forEach((function(n){e.peerConnection.removeTrack(n)})),e.peerConnection.close(),e.peerConnection=null,delete e.peerConnection)}function g(){e.webSocket&&(e.webSocket.close(),e.webSocket=null,delete e.webSocket)}e.attachMedia=function(n){e.videoElement=n},e.getUserMedia=function(o){return function(o){return o||(o={video:{deviceId:void 0},audio:{deviceId:void 0}}),console.info(i,"Request Stream To Input Devices With Constraints",o),navigator.mediaDevices.getUserMedia(o).then((function(n){console.info(i,"Received Media Stream From Input Device",n),n.getVideoTracks().forEach((function(e){console.info(i,"Video Track Settings From Input Stream",e.getSettings())})),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Input Device",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.getDisplayMedia=function(o){return function(o){return o||(o={}),console.info(i,"Request Stream To Display With Constraints",o),navigator.mediaDevices.getDisplayMedia(o).then((function(n){console.info(i,"Received Media Stream From Display",n),e.inputStream=n;let o=e.videoElement;return o&&(o.srcObject=n,o.onloadedmetadata=function(e){o.play()}),new Promise((function(e){e(n)}))})).catch((function(e){return console.error(i,"Can't Get Media Stream From Display",e),n(e),new Promise((function(n,o){o(e)}))}))}(o)},e.setMediaStream=function(o){return function(o){if(!(o&&o instanceof MediaStream)){const e=new Error("Invalid MediaStream provided");return console.error(i,"Invalid MediaStream",e),n(e),new Promise((function(n,o){o(e)}))}console.info(i,"Received Media Stream",o),e.inputStream=o;let t=e.videoElement;return t&&(t.srcObject=o,t.onloadedmetadata=function(e){t.play()}),new Promise((function(e){e(o)}))}(o)},e.startStreaming=function(f,g){if(console.info(c,`Start Streaming to ${f} with connectionConfig`,g),!f)return console.error("endpointUrl is required"),void n("endpointUrl is required");e.endpointUrl=f,g&&(e.connectionConfig=g);try{const c=new URL(f).protocol;"wss:"===c||"ws:"===c?(e.streamingMode="webrtc",function(o){if(!o)return void n("endpointUrl is required");let t=null;try{t=new WebSocket(o)}catch(e){n(e)}e.webSocket=t,t.onopen=function(){a(t,{command:"request_offer"})},t.onmessage=function(o){let t=JSON.parse(o.data);t.error&&(console.error("webSocket.onmessage",t.error),n(t.error)),"offer"===t.command&&function(o,t,c,f,u){let g={};if(e.connectionConfig.iceServers)g.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy);else if(u){g.iceServers=[];for(let n=0;n<u.length;n++){let o=u[n],t={};t.urls=o.urls;let i=!1,c=s(e.endpointUrl);for(let e=0;e<t.urls.length;e++)if(t.urls[e].indexOf(c)>-1){i=!0;break}if(!i&&t.urls.length>0){let e=t.urls[0],n=l(e);c&&n&&t.urls.push(e.replace(n,c))}t.username=o.user_name,t.credential=o.credential,g.iceServers.push(t)}e.connectionConfig.iceTransportPolicy?g.iceTransportPolicy=e.connectionConfig.iceTransportPolicy:"all"==r(e.endpointUrl)?(g.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(g.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))}console.info(i,"Create Peer Connection With Config",g);let m=new RTCPeerConnection(g);e.peerConnection=m,e.inputStream.getTracks().forEach((function(n){console.info(i,"Add Track To Peer Connection",n),m.addTrack(n,e.inputStream)})),e.connectionConfig.maxVideoBitrate&&(c.sdp=function(e,n,o){let t=e.split("\r\n"),i=-1;for(let e=0;e<t.length;e++)if(0===t[e].indexOf("m=video")){i=e;break}if(-1===i)return e;for(i++;0===t[i].indexOf("i=")||0===t[i].indexOf("c=");)i++;if(0===t[i].indexOf("b"))return t[i]="b=AS:"+o,t.join("\r\n");let c=t.slice(0,i);return c.push("b=AS:"+o),c=c.concat(t.slice(i,t.length)),c.join("\r\n")}(c.sdp,0,e.connectionConfig.maxVideoBitrate)),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified offer sdp\n\n"+c.sdp),m.setRemoteDescription(new RTCSessionDescription(c)).then((function(){m.createAnswer().then((function(c){e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(c.sdp=p(c.sdp)),e.connectionConfig.preferredVideoFormat&&(c.sdp=d(c.sdp,e.connectionConfig.preferredVideoFormat)),console.info(i,"Modified answer sdp\n\n"+c.sdp),m.setLocalDescription(c).then((function(){a(e.webSocket,{id:o,peer_id:t,command:"answer",sdp:c})})).catch((function(e){console.error("peerConnection.setLocalDescription",e),n(e)}))})).catch((function(e){console.error("peerConnection.createAnswer",e),n(e)}))})).catch((function(e){console.error("peerConnection.setRemoteDescription",e),n(e)})),f&&function(e,o){for(let t=0;t<o.length;t++)if(o[t]&&o[t].candidate){let i=o[t];e.addIceCandidate(new RTCIceCandidate(i)).then((function(){})).catch((function(e){console.error("peerConnection.addIceCandidate",e),n(e)}))}}(m,f),m.onicecandidate=function(n){n.candidate&&n.candidate.candidate&&a(e.webSocket,{id:o,peer_id:t,command:"candidate",candidates:[n.candidate]})},m.oniceconnectionstatechange=function(n){let o=m.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"!==o&&"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection closed",n),e.callbacks.connectionClosed("ice",n))}}(t.id,t.peer_id,t.sdp,t.candidates,t.ice_servers)},t.onerror=function(e){console.error("webSocket.onerror",e),n(e)},t.onclose=function(n){e.webSocketClosedByUser||e.callbacks.connectionClosed&&e.callbacks.connectionClosed("websocket",n)}}(f)):"https:"===c||"http:"===c?(e.streamingMode="whip",async function(c){if(e.peerConnection)return console.error("Connection already established"),void n("Connection already established");const a={bundlePolicy:"max-bundle"};e.connectionConfig.iceServers?(a.iceServers=e.connectionConfig.iceServers,e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy)):e.connectionConfig.iceTransportPolicy&&(a.iceTransportPolicy=e.connectionConfig.iceTransportPolicy),console.info(i,"Create Peer Connection With Config",a);const s=new RTCPeerConnection(a);if(e.peerConnection=s,!e.inputStream)return console.error("No input stream in OvenLiveKit"),void n("No input stream in OvenLiveKit");for(const n of e.inputStream.getTracks()){console.log(i,"Adding track: ",n);const o={direction:"sendonly"},t=e.connectionConfig.simulcast;if("video"===n.kind&&t&&t.length>0){o.sendEncodings||(o.sendEncodings=[]);for(let e=0;e<t.length;e++){const c={rid:e,active:!0,scalabilityMode:"L1T1",...t[e]};console.log(i,`Adding simulcast layer to: ${n.kind}`,c),o.sendEncodings.push(c)}}"video"===n.kind&&e.connectionConfig.maxVideoBitrate&&(o.sendEncodings||(o.sendEncodings=[]),o.sendEncodings.push({maxBitrate:1e3*e.connectionConfig.maxVideoBitrate}),console.log(i,`Setting max bitrate to: ${e.connectionConfig.maxVideoBitrate} kbps`)),s.addTransceiver(n,o)}s.oniceconnectionstatechange=function(n){let o=s.iceConnectionState;e.callbacks.iceStateChange&&(console.info(i,"ICE State","["+o+"]"),e.callbacks.iceStateChange(o)),"connected"===o&&e.callbacks.connected&&e.callbacks.connected(n),"failed"===o&&e.callbacks.connectionClosed&&(console.error(i,"Ice connection failed",n),e.callbacks.errorHandler(n)),"disconnected"!==o&&"closed"!==o||e.callbacks.connectionClosed&&(console.error(i,"Ice connection disconnected or closed",n),e.callbacks.connectionClosed("ice",n))};const l=await s.createOffer();console.log(i,"Offer SDP from browser: ",l.sdp),e.connectionConfig.sdp&&e.connectionConfig.sdp.appendFmtp&&(l.sdp=p(l.sdp)),e.connectionConfig.preferredVideoFormat?l.sdp=d(l.sdp,e.connectionConfig.preferredVideoFormat):l.sdp=d(l.sdp,"H264");const f={"Content-Type":"application/sdp"},g=e.inputStream.getVideoTracks();if(g&&1===g.length){const n=g[0].getSettings();console.log(i,"Video track settings for Oven-Capabilities:",n);const o=n.width,c=n.height,r=n.frameRate,a=e.connectionConfig.simulcast,s=a?a.length:0;if(s>=2){const e=[];for(let n=0;n<s;n++){const i=a[n]||{},s=void 0!==i.rid?i.rid:n,l=parseFloat(i.scaleResolutionDownBy);if(!isNaN(l)&&l>0&&"number"==typeof o&&"number"==typeof c){const n=2*Math.floor(Math.floor(o/l)/2),t=2*Math.floor(Math.floor(c/l)/2);e.push(`rid:${s}:max_width=${n}`),e.push(`rid:${s}:max_height=${t}`)}r&&e.push(`rid:${s}:max_fps=${t(r)}`)}e.length>0&&(console.log(i,`Setting Oven-Capabilities header: ${e.join(", ")}`),f["Oven-Capabilities"]=e.join(", "))}else if("number"==typeof o&&"number"==typeof c){const e=[`max_width=${o}`,`max_height=${c}`];r&&e.push(`max_fps=${t(r)}`),console.log(i,`Setting Oven-Capabilities header: ${e.join(", ")}`),f["Oven-Capabilities"]=e.join(", ")}}else g&&0!==g.length||console.log(i,"No video tracks found, skipping Oven-Capabilities header."),g&&g.length>1&&console.log(i,`Multiple (${g.length}) video tracks found, skipping Oven-Capabilities header.`);e.connectionConfig.httpHeaders&&Object.assign(f,e.connectionConfig.httpHeaders),console.log(i,"Offer SDP modified by OvenLiveKit: ",l.sdp);const m=await o(c,{method:"POST",body:l.sdp,headers:f});if(!m.ok)return console.error("Failed to fetch",m.status),n(`Failed to fetch ${m.status}`),void u();if(!m.headers.get("location"))return console.error("No location header on answer response"),void n("No location header on answer response");e.endpointUrl=m.url,console.log(i,"Endpoint Url: ",e.endpointUrl);const h=m.headers.get("location");if(console.log(i,"Location header ",h),e.resourceUrl=new URL(h,m.url).toString(),console.log(i,"Resource URL: ",e.resourceUrl),m.headers.get("Link")){console.log(i,"Link Header Found. Update ");const n=m.headers.get("Link").split(",").map((e=>e.trim())),o=[];if(n.forEach((e=>{console.log(i,"Link: ",e);const n=e.match(/<([^>]+)>/),t=e.match(/rel="([^"]+)"/),c=e.match(/username="([^"]+)"/),r=e.match(/credential="([^"]+)"/),a=e.match(/credential-type="([^"]+)"/);if(n&&t&&"ice-server"===t[1]){const e={urls:n[1]};c&&(e.username=c[1]),r&&(e.credential=r[1]),a&&(e.credentialType=a[1]),o.push(e)}})),console.log(i,"Parsed ICE Servers from Link header: ",o),o.length>0){const n=s.getConfiguration();e.connectionConfig.iceServers||(n.iceServers=o,console.log(i,"Updated Peer Connection ICE Servers from Link header")),e.connectionConfig.iceTransportPolicy||("all"==r(e.endpointUrl)?(n.iceTransportPolicy="all",console.log(i,"Set Peer Connection iceTransportPolicy to all")):(n.iceTransportPolicy="relay",console.log(i,"Set Peer Connection iceTransportPolicy to relay"))),s.setConfiguration(n)}}const C=await m.text();console.log(i,"Response Answer SDP: ",C);try{await s.setLocalDescription(l)}catch(e){console.error("peerConnection.setLocalDescription",e),n(e)}try{await s.setRemoteDescription({type:"answer",sdp:C})}catch(e){console.error("peerConnection.setRemoteDescription",e),n(e)}}(f)):(console.error("Invalid protocol",error),n(error))}catch(e){console.error("Cannot parse connection URL",e),n(e)}},e.stopStreaming=async function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&await f(),e.callbacks.connectionClosed&&(console.log(i,"Connection closed by user"),e.callbacks.connectionClosed("user","Connection closed by user"))},e.remove=function(){"webrtc"===e.streamingMode?(e.webSocketClosedByUser=!0,g(),u()):"whip"===e.streamingMode&&f(),e.inputStream&&(e.inputStream.getTracks().forEach((n=>{n.stop(),e.inputStream.removeTrack(n)})),e.videoElement&&(e.videoElement.srcObject=null),e.inputStream=null,delete e.inputStream),console.info(c,"Removed")}}o.getVersion=function(){return t},o.create=function(e){console.info(c,"Create WebRTC Input "+t);let n={webSocketClosedByUser:!1};return function(e,n){e.streamingMode=null,e.inputStream=null,e.webSocket=null,e.peerConnection=null,e.connectionConfig={},e.videoElement=null,e.endpointUrl=null,e.resourceUrl=null,n&&n.callbacks?e.callbacks=n.callbacks:e.callbacks={}}(n,e),p(n),n},o.getDevices=async function(e="both"){try{await f(e)}catch(e){console.warn(i,"Can not find Video and Audio devices.",e);let n=null,o=null;try{n=await f("video")}catch(e){console.warn(i,"Can not find Video devices.",e)}try{o=await f("audio")}catch(e){console.warn(i,"Can not find Audio devices.",e)}if(!n&&!o)throw new Error("No input devices were found.")}return function(e){let n={audioinput:[],audiooutput:[],videoinput:[],other:[]};for(let o=0;o!==e.length;++o){const t=e[o];let i={};i.deviceId=t.deviceId,"audioinput"===t.kind?(i.label=t.label||`microphone ${n.audioinput.length+1}`,n.audioinput.push(i)):"audiooutput"===t.kind?(i.label=t.label||`speaker ${n.audiooutput.length+1}`,n.audiooutput.push(i)):"videoinput"===t.kind?(i.label=t.label||`camera ${n.videoinput.length+1}`,n.videoinput.push(i)):(i.label=t.label||`other ${n.other.length+1}`,n.other.push(i))}return n}(await async function(){return await navigator.mediaDevices.enumerateDevices()}())};const u=o;return n.default})()));
2
2
  //# sourceMappingURL=OvenLiveKit.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"OvenLiveKit.min.js","mappings":"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","sources":["webpack://OvenLiveKit/webpack/universalModuleDefinition","webpack://OvenLiveKit/webpack/bootstrap","webpack://OvenLiveKit/webpack/runtime/define property getters","webpack://OvenLiveKit/webpack/runtime/hasOwnProperty shorthand","webpack://OvenLiveKit/./src/OvenLiveKit.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"OvenLiveKit\"] = factory();\n\telse\n\t\troot[\"OvenLiveKit\"] = factory();\n})(self, () => {\nreturn ","// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const OvenLiveKit = {};\n\nconst version = '1.5.6';\nconst logHeader = 'OvenLiveKit.js :';\nconst logEventHeader = 'OvenLiveKit.js ====';\n\n// private methods\nfunction getTransportQuery(endpointUrl) {\n\n try {\n const url = new URL(endpointUrl);\n const transport = url.searchParams.get('transport');\n\n if (transport === null) {\n return undefined;\n }\n\n const normalized = transport.toLowerCase();\n\n return normalized;\n } catch (e) {\n return undefined;\n }\n}\n\nfunction sendMessage(webSocket, message) {\n\n if (webSocket) {\n webSocket.send(JSON.stringify(message));\n }\n}\n\nfunction generateDomainFromUrl(url) {\n let result = '';\n let match;\n if (match = url.match(/^(?:wss?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n\\?\\=]+)/im)) {\n result = match[1];\n }\n\n return result;\n}\n\nfunction findIp(string) {\n\n let result = '';\n let match;\n\n if (match = string.match(new RegExp('\\\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\b', 'gi'))) {\n result = match[0];\n }\n\n return result;\n}\n\nfunction getFormatNumber(sdp, format) {\n\n const lines = sdp.split('\\r\\n');\n let formatNumber = -1;\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n lines[i] = lines[i].toLowerCase();\n\n if (lines[i].indexOf('a=rtpmap') === 0 && lines[i].indexOf(format.toLowerCase()) > -1) {\n // parsing \"a=rtpmap:100 H264/90000\" line\n // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters >]\n formatNumber = lines[i].split(' ')[0].split(':')[1];\n break;\n }\n }\n\n return formatNumber;\n}\n\nfunction setPreferredVideoFormat(sdp, formatName) {\n\n const formatNumber = getFormatNumber(sdp, formatName);\n\n if (formatNumber === -1) {\n return sdp;\n }\n\n let newLines = [];\n const lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n const line = lines[i];\n\n if (line.indexOf('m=video') === 0) {\n\n // m=<media> <port>/<number of ports> <transport> <fmt list>\n const others = line.split(' ').slice(0, 3);\n const formats = line.split(' ').slice(3);\n formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });\n newLines.push(others.concat(formats).join(' '));\n } else {\n newLines.push(line);\n }\n\n }\n\n return newLines.join('\\r\\n') + '\\r\\n';\n}\n\nfunction removeFormat(sdp, formatNumber) {\n let newLines = [];\n let lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n newLines.push(lines[i].replace(' ' + formatNumber + '', ''));\n } else if (lines[i].indexOf(formatNumber + '') > -1) {\n\n } else {\n newLines.push(lines[i]);\n }\n }\n\n return newLines.join('\\r\\n')\n}\n\nasync function getStreamForDeviceCheck(type) {\n\n // High resolution video constraints makes browser to get maximum resolution of video device.\n const constraints = {\n };\n\n if (type === 'both') {\n constraints.audio = true;\n constraints.video = true;\n } else if (type === 'audio') {\n constraints.audio = true;\n } else if (type === 'video') {\n constraints.video = true;\n }\n\n const permissionStream = await navigator.mediaDevices.getUserMedia(constraints);\n permissionStream.getTracks().forEach(track => track.stop());\n await new Promise(resolve => setTimeout(resolve, 500));\n\n return true;\n}\n\nasync function getDevices() {\n\n return await navigator.mediaDevices.enumerateDevices();\n}\n\nfunction gotDevices(deviceInfos) {\n\n let devices = {\n 'audioinput': [],\n 'audiooutput': [],\n 'videoinput': [],\n 'other': [],\n };\n\n for (let i = 0; i !== deviceInfos.length; ++i) {\n\n const deviceInfo = deviceInfos[i];\n\n let info = {};\n\n info.deviceId = deviceInfo.deviceId;\n\n if (deviceInfo.kind === 'audioinput') {\n\n info.label = deviceInfo.label || `microphone ${devices.audioinput.length + 1}`;\n devices.audioinput.push(info);\n } else if (deviceInfo.kind === 'audiooutput') {\n\n info.label = deviceInfo.label || `speaker ${devices.audiooutput.length + 1}`;\n devices.audiooutput.push(info);\n } else if (deviceInfo.kind === 'videoinput') {\n\n info.label = deviceInfo.label || `camera ${devices.videoinput.length + 1}`;\n devices.videoinput.push(info);\n } else {\n\n info.label = deviceInfo.label || `other ${devices.other.length + 1}`;\n devices.other.push(info);\n }\n }\n\n return devices;\n}\n\nfunction initConfig(instance, options) {\n\n // webrtc or whip\n instance.streamingMode = null;\n\n instance.inputStream = null;\n instance.webSocket = null;\n instance.peerConnection = null;\n instance.connectionConfig = {};\n\n instance.videoElement = null;\n instance.endpointUrl = null;\n instance.resourceUrl = null;\n\n if (options && options.callbacks) {\n\n instance.callbacks = options.callbacks;\n } else {\n instance.callbacks = {};\n }\n}\n\nfunction addMethod(instance) {\n\n function errorHandler(error) {\n\n if (instance.callbacks.error) {\n\n instance.callbacks.error(error);\n }\n }\n\n async function fetchWithRedirect(url, options) {\n let fetched = await fetch(url, options);\n\n while (fetched.redirected) {\n url = fetched.url;\n fetched = await fetch(url, options);\n }\n\n return fetched;\n }\n\n function getUserMedia(constraints) {\n\n if (!constraints) {\n\n constraints = {\n video: {\n deviceId: undefined\n },\n audio: {\n deviceId: undefined\n }\n };\n }\n\n console.info(logHeader, 'Request Stream To Input Devices With Constraints', constraints);\n\n return navigator.mediaDevices.getUserMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Input Device', stream);\n\n stream.getVideoTracks().forEach(function (track) {\n console.info(logHeader, 'Video Track Settings From Input Stream', track.getSettings());\n });\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Input Device', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function getDisplayMedia(constraints) {\n\n if (!constraints) {\n constraints = {};\n }\n\n console.info(logHeader, 'Request Stream To Display With Constraints', constraints);\n\n return navigator.mediaDevices.getDisplayMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Display', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Display', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function setMediaStream(stream) {\n // Check if a valid stream is provided\n if (!stream || !(stream instanceof MediaStream)) {\n\n const error = new Error(\"Invalid MediaStream provided\");\n console.error(logHeader, 'Invalid MediaStream', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n }\n\n console.info(logHeader, 'Received Media Stream', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n resolve(stream);\n });\n }\n\n // From https://webrtchacks.com/limit-webrtc-bandwidth-sdp/\n function setBitrateLimit(sdp, media, bitrate) {\n\n let lines = sdp.split('\\r\\n');\n let line = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].indexOf('m=' + media) === 0) {\n line = i;\n break;\n }\n }\n if (line === -1) {\n // Could not find the m line for media\n return sdp;\n }\n\n // Pass the m line\n line++;\n\n // Skip i and c lines\n while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {\n\n line++;\n }\n\n // If we're on a b line, replace it\n if (lines[line].indexOf('b') === 0) {\n\n lines[line] = 'b=AS:' + bitrate;\n\n return lines.join('\\r\\n');\n }\n\n // Add a new b line\n let newLines = lines.slice(0, line)\n\n newLines.push('b=AS:' + bitrate)\n newLines = newLines.concat(lines.slice(line, lines.length))\n\n return newLines.join('\\r\\n')\n }\n\n function normalizeFrameRate(frameRate) {\n if (Number.isInteger(frameRate)) {\n return frameRate;\n }\n return parseFloat(frameRate).toFixed(2);\n }\n\n function initWebSocket(endpointUrl) {\n\n if (!endpointUrl) {\n errorHandler('endpointUrl is required');\n return;\n }\n\n let webSocket = null;\n\n try {\n\n webSocket = new WebSocket(endpointUrl);\n } catch (error) {\n\n errorHandler(error);\n }\n\n\n instance.webSocket = webSocket;\n\n webSocket.onopen = function () {\n\n // Request offer at the first time.\n sendMessage(webSocket, {\n command: 'request_offer'\n });\n };\n\n webSocket.onmessage = function (e) {\n\n let message = JSON.parse(e.data);\n\n if (message.error) {\n console.error('webSocket.onmessage', message.error);\n errorHandler(message.error);\n }\n\n if (message.command === 'offer') {\n\n // OME returns offer. Start create peer connection.\n createPeerConnection(\n message.id,\n message.peer_id,\n message.sdp,\n message.candidates,\n message.ice_servers\n );\n }\n };\n\n webSocket.onerror = function (error) {\n\n console.error('webSocket.onerror', error);\n errorHandler(error);\n };\n\n webSocket.onclose = function (e) {\n\n if (!instance.webSocketClosedByUser) {\n\n if (instance.callbacks.connectionClosed) {\n instance.callbacks.connectionClosed('websocket', e);\n }\n }\n };\n\n }\n\n async function startWhip(endpointUrl) {\n\n if (instance.peerConnection) {\n console.error('Connection already established');\n errorHandler('Connection already established');\n return;\n }\n\n const peerConnectionConfig = {\n bundlePolicy: \"max-bundle\"\n };\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from local config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else {\n // last priority using default ice servers.\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n }\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n const peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n if (!instance.inputStream) {\n console.error('No input stream in OvenLiveKit');\n errorHandler('No input stream in OvenLiveKit');\n return;\n }\n\n for (const track of instance.inputStream.getTracks()) {\n console.log(logHeader, 'Adding track: ', track);\n\n const transceiverConfig = {\n direction: 'sendonly'\n };\n\n // Add simulcast layers if configured\n const simulcastConfig = instance.connectionConfig.simulcast;\n\n if (track.kind === 'video' && simulcastConfig && simulcastConfig.length > 0) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n for (let i = 0; i < simulcastConfig.length; i++) {\n\n const layer = {\n rid: i,\n active: true,\n ...simulcastConfig[i]\n };\n\n console.log(logHeader, `Adding simulcast layer to: ${track.kind}`, layer);\n\n transceiverConfig.sendEncodings.push(layer);\n }\n }\n\n if (track.kind === 'video' && instance.connectionConfig.maxVideoBitrate) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n transceiverConfig.sendEncodings.push({\n maxBitrate: instance.connectionConfig.maxVideoBitrate * 1000,\n });\n\n console.log(logHeader, `Setting max bitrate to: ${instance.connectionConfig.maxVideoBitrate} kbps`);\n }\n\n peerConnection.addTransceiver(track, transceiverConfig);\n }\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection failed', e);\n instance.callbacks.errorHandler(e);\n }\n }\n\n if (state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection disconnected or closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n\n const offer = await peerConnection.createOffer();\n console.log(logHeader, 'Offer SDP: ', offer.sdp);\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat);\n } else {\n // default to H264\n offer.sdp = setPreferredVideoFormat(offer.sdp, 'H264');\n }\n\n const headers = {\n \"Content-Type\": \"application/sdp\"\n };\n\n // Set Oven-Capabilities header if not over two simulcast layers.\n if (!instance.connectionConfig.simulcast || instance.connectionConfig.simulcast.length < 2) {\n\n const videoTracks = instance.inputStream.getVideoTracks();\n\n if (videoTracks && videoTracks.length === 1) {\n\n for (let i = 0; i < videoTracks.length; i++) {\n\n const track = videoTracks[i];\n const settings = track.getSettings();\n\n console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);\n\n const width = settings.width;\n const height = settings.height;\n const framerate = settings.frameRate;\n\n if (typeof width === 'number' && typeof height === 'number') {\n console.log(logHeader, `Setting Oven-Capabilities header: max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`);\n headers['Oven-Capabilities'] = `max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`;\n }\n }\n } else {\n\n if (!videoTracks || videoTracks.length === 0) {\n console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');\n }\n\n if (videoTracks && videoTracks.length > 1) {\n console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);\n }\n }\n } else {\n console.log(logHeader, 'Over two simulcast layers are set, skipping Oven-Capabilities header.');\n }\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n const fetched = await fetchWithRedirect(endpointUrl, {\n method: \"POST\",\n body: offer.sdp,\n headers\n });\n\n if (!fetched.ok) {\n console.error('Failed to fetch', fetched.status);\n errorHandler(`Failed to fetch ${fetched.status}`);\n closePeerConnection();\n return;\n }\n\n if (!fetched.headers.get(\"location\")) {\n console.error('No location header on answer response');\n errorHandler('No location header on answer response');\n return;\n }\n\n // update endpointUrl\n instance.endpointUrl = fetched.url;\n console.log(logHeader, 'Endpoint Url: ', instance.endpointUrl);\n\n const locationHeaderValue = fetched.headers.get(\"location\");\n console.log(logHeader, 'Location header ', locationHeaderValue);\n\n instance.resourceUrl = new URL(locationHeaderValue, fetched.url).toString();\n console.log(logHeader, 'Resource URL: ', instance.resourceUrl);\n\n\n if (fetched.headers.get(\"Link\")) {\n console.log(logHeader, 'Link Header Found. Update ');\n\n const links = fetched.headers.get(\"Link\").split(',').map(link => link.trim());\n\n // OvenMediaEngine returns turn servers in Link header with rel=\"ice-server\".\n // For example: <turn:119.196.229.2:3478?transport=tcp>; rel=\"ice-server\"; username=\"user\"; credential=\"cred\"; credential-type=\"password\"\n const turnIceServers = [];\n\n links.forEach(link => {\n\n console.log(logHeader, 'Link: ', link);\n const urlsMatch = link.match(/<([^>]+)>/);\n const relMatch = link.match(/rel=\"([^\"]+)\"/);\n const usernameMatch = link.match(/username=\"([^\"]+)\"/);\n const credentialMatch = link.match(/credential=\"([^\"]+)\"/);\n const credentialTypeMatch = link.match(/credential-type=\"([^\"]+)\"/);\n\n if (urlsMatch && relMatch && relMatch[1] === 'ice-server') {\n\n const iceServer = {\n urls: urlsMatch[1]\n };\n\n if (usernameMatch) {\n iceServer.username = usernameMatch[1];\n };\n\n if (credentialMatch) {\n iceServer.credential = credentialMatch[1];\n };\n\n if (credentialTypeMatch) {\n iceServer.credentialType = credentialTypeMatch[1];\n }\n\n turnIceServers.push(iceServer);\n }\n });\n\n console.log(logHeader, 'Parsed ICE Servers from Link header: ', turnIceServers);\n\n // update peer connection with new turn servers from OME.\n if (turnIceServers.length > 0) {\n const pcConfig = peerConnection.getConfiguration();\n\n // only update ice servers when local config does not have ice servers.\n if (!instance.connectionConfig.iceServers) {\n pcConfig.iceServers = turnIceServers;\n console.log(logHeader, 'Updated Peer Connection ICE Servers from Link header');\n }\n\n // set ice transport policy to relay if not set in local config.\n if (!instance.connectionConfig.iceTransportPolicy) {\n\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n pcConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n pcConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n\n peerConnection.setConfiguration(pcConfig);\n }\n }\n\n const answer = await fetched.text();\n console.log(logHeader, 'Response Answer SDP: ', answer);\n\n try {\n await peerConnection.setLocalDescription(offer);\n } catch (error) {\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n }\n\n try {\n await peerConnection.setRemoteDescription({\n type: \"answer\",\n sdp: answer\n });\n } catch (error) {\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n }\n }\n\n async function stopWhip() {\n\n if (!instance.peerConnection) {\n console.error('No connection to close');\n errorHandler('No connection to close');\n return;\n }\n\n closePeerConnection();\n\n if (instance.resourceUrl) {\n\n const headers = {\n };\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n await fetchWithRedirect(instance.resourceUrl, {\n method: \"DELETE\",\n headers\n });\n }\n }\n\n function appendFmtp(sdp) {\n\n const fmtpStr = instance.connectionConfig.sdp.appendFmtp;\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n let fmtpLineFound = false;\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {\n fmtpLineFound = true;\n lines[j] += ';' + fmtpStr;\n }\n }\n\n if (!fmtpLineFound) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=fmtp:' + payloads[i] + ' ' + fmtpStr;\n }\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function appendOrientation(sdp) {\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function createPeerConnection(id, peerId, offer, candidates, iceServers) {\n\n let peerConnectionConfig = {};\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from user config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else if (iceServers) {\n\n // second priority using ice servers from ome and force using TCP\n peerConnectionConfig.iceServers = [];\n\n for (let i = 0; i < iceServers.length; i++) {\n\n let iceServer = iceServers[i];\n\n let regIceServer = {};\n\n regIceServer.urls = iceServer.urls;\n\n let hasWebSocketUrl = false;\n let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);\n\n for (let j = 0; j < regIceServer.urls.length; j++) {\n\n let serverUrl = regIceServer.urls[j];\n\n if (serverUrl.indexOf(webSocketUrl) > -1) {\n hasWebSocketUrl = true;\n break;\n }\n }\n\n if (!hasWebSocketUrl) {\n\n if (regIceServer.urls.length > 0) {\n\n let cloneIceServer = regIceServer.urls[0];\n let ip = findIp(cloneIceServer);\n\n if (webSocketUrl && ip) {\n regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));\n }\n }\n }\n\n regIceServer.username = iceServer.user_name;\n regIceServer.credential = iceServer.credential;\n\n peerConnectionConfig.iceServers.push(regIceServer);\n }\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n } else {\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n peerConnectionConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n peerConnectionConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n }\n\n let advancedSetting = {\n optional: [\n {\n googHighStartBitrate: {\n exact: !0\n }\n },\n {\n googPayloadPadding: {\n exact: !0\n }\n },\n {\n googScreencastMinBitrate: {\n exact: 500\n }\n },\n {\n enableDscp: {\n exact: true\n }\n }\n ]\n };\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n let peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n // set local stream\n instance.inputStream.getTracks().forEach(function (track) {\n\n console.info(logHeader, 'Add Track To Peer Connection', track);\n peerConnection.addTrack(track, instance.inputStream);\n });\n\n if (instance.connectionConfig.maxVideoBitrate) {\n\n // if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome\n offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);\n }\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n\n // offer.sdp = appendOrientation(offer.sdp);\n console.info(logHeader, 'Modified offer sdp\\n\\n' + offer.sdp);\n\n peerConnection.setRemoteDescription(new RTCSessionDescription(offer))\n .then(function () {\n\n peerConnection.createAnswer()\n .then(function (answer) {\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n answer.sdp = appendFmtp(answer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n console.info(logHeader, 'Modified answer sdp\\n\\n' + answer.sdp);\n\n peerConnection.setLocalDescription(answer)\n .then(function () {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'answer',\n sdp: answer\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.createAnswer', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n });\n\n if (candidates) {\n\n addIceCandidate(peerConnection, candidates);\n }\n\n peerConnection.onicecandidate = function (e) {\n\n if (e.candidate && e.candidate.candidate) {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'candidate',\n candidates: [e.candidate]\n });\n }\n };\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed' || state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n }\n\n function addIceCandidate(peerConnection, candidates) {\n\n for (let i = 0; i < candidates.length; i++) {\n\n if (candidates[i] && candidates[i].candidate) {\n\n let basicCandidate = candidates[i];\n\n peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))\n .then(function () {\n\n })\n .catch(function (error) {\n\n console.error('peerConnection.addIceCandidate', error);\n errorHandler(error);\n });\n }\n }\n }\n\n function closePeerConnection() {\n if (instance.peerConnection) {\n\n // remove tracks from peer connection\n instance.peerConnection.getSenders().forEach(function (sender) {\n instance.peerConnection.removeTrack(sender);\n });\n\n instance.peerConnection.close();\n instance.peerConnection = null;\n delete instance.peerConnection;\n }\n }\n\n function closeWebSocket() {\n\n if (instance.webSocket) {\n\n instance.webSocket.close();\n instance.webSocket = null;\n delete instance.webSocket;\n }\n }\n\n function closeInputStream() {\n // release video, audio stream\n if (instance.inputStream) {\n\n instance.inputStream.getTracks().forEach(track => {\n\n track.stop();\n instance.inputStream.removeTrack(track);\n });\n\n if (instance.videoElement) {\n instance.videoElement.srcObject = null;\n }\n\n instance.inputStream = null;\n delete instance.inputStream;\n }\n }\n\n // instance methods\n instance.attachMedia = function (videoElement) {\n\n instance.videoElement = videoElement;\n };\n\n instance.getUserMedia = function (constraints) {\n\n return getUserMedia(constraints);\n };\n\n instance.getDisplayMedia = function (constraints) {\n\n return getDisplayMedia(constraints);\n };\n\n instance.setMediaStream = function (stream) {\n\n return setMediaStream(stream);\n };\n\n instance.startStreaming = function (endpointUrl, connectionConfig) {\n\n console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);\n\n if (!endpointUrl) {\n console.error('endpointUrl is required');\n errorHandler('endpointUrl is required');\n return;\n }\n\n instance.endpointUrl = endpointUrl;\n\n if (connectionConfig) {\n instance.connectionConfig = connectionConfig;\n }\n\n try {\n\n const protocol = new URL(endpointUrl).protocol;\n\n if (protocol === 'wss:' || protocol === 'ws:') {\n\n instance.streamingMode = 'webrtc';\n initWebSocket(endpointUrl);\n } else if (protocol === 'https:' || protocol === 'http:') {\n\n instance.streamingMode = 'whip';\n startWhip(endpointUrl);\n } else {\n console.error('Invalid protocol', error);\n errorHandler(error);\n }\n\n } catch (error) {\n console.error('Cannot parse connection URL', error);\n errorHandler(error);\n }\n };\n\n instance.stopStreaming = async function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n\n await stopWhip();\n }\n\n if (instance.callbacks.connectionClosed) {\n console.log(logHeader, 'Connection closed by user');\n instance.callbacks.connectionClosed('user', 'Connection closed by user');\n }\n };\n\n instance.remove = function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n stopWhip();\n }\n\n closeInputStream();\n\n console.info(logEventHeader, 'Removed');\n\n };\n}\n\nOvenLiveKit.getVersion = function () {\n return version;\n}\n\n// static methods\nOvenLiveKit.create = function (options) {\n\n console.info(logEventHeader, 'Create WebRTC Input ' + version);\n\n let instance = {};\n\n instance.webSocketClosedByUser = false;\n\n initConfig(instance, options);\n addMethod(instance);\n\n return instance;\n};\n\nOvenLiveKit.getDevices = async function (type = 'both') {\n\n try {\n // First check both audio and video sources are available.\n await getStreamForDeviceCheck(type);\n } catch (e) {\n\n console.warn(logHeader, 'Can not find Video and Audio devices.', e);\n\n let videoFound = null;\n let audioFound = null;\n\n try {\n videoFound = await getStreamForDeviceCheck('video');\n } catch (e) {\n console.warn(logHeader, 'Can not find Video devices.', e);\n }\n\n try {\n audioFound = await getStreamForDeviceCheck('audio');\n } catch (e) {\n console.warn(logHeader, 'Can not find Audio devices.', e);\n }\n\n if (!videoFound && !audioFound) {\n throw new Error('No input devices were found.');\n }\n }\n\n const deviceInfos = await getDevices();\n return gotDevices(deviceInfos)\n};\n\nexport default OvenLiveKit;\n"],"names":["root","factory","exports","module","define","amd","self","__webpack_require__","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","OvenLiveKit","version","logHeader","logEventHeader","getTransportQuery","endpointUrl","transport","URL","searchParams","toLowerCase","e","sendMessage","webSocket","message","send","JSON","stringify","generateDomainFromUrl","url","match","result","findIp","string","RegExp","setPreferredVideoFormat","sdp","formatName","formatNumber","format","lines","split","i","length","indexOf","getFormatNumber","newLines","line","others","slice","formats","sort","x","y","push","concat","join","async","getStreamForDeviceCheck","type","constraints","audio","video","navigator","mediaDevices","getUserMedia","getTracks","forEach","track","stop","Promise","resolve","setTimeout","addMethod","instance","errorHandler","error","callbacks","fetchWithRedirect","options","fetched","fetch","redirected","normalizeFrameRate","frameRate","Number","isInteger","parseFloat","toFixed","stopWhip","peerConnection","console","closePeerConnection","resourceUrl","headers","connectionConfig","httpHeaders","assign","method","appendFmtp","fmtpStr","payloads","tokens","j","fmtpLineFound","getSenders","sender","removeTrack","close","closeWebSocket","attachMedia","videoElement","deviceId","undefined","info","then","stream","getVideoTracks","getSettings","inputStream","elem","srcObject","onloadedmetadata","play","catch","reject","getDisplayMedia","setMediaStream","MediaStream","Error","startStreaming","protocol","streamingMode","WebSocket","onopen","command","onmessage","parse","data","id","peerId","offer","candidates","iceServers","peerConnectionConfig","iceTransportPolicy","iceServer","regIceServer","urls","hasWebSocketUrl","webSocketUrl","cloneIceServer","ip","replace","username","user_name","credential","log","RTCPeerConnection","addTrack","maxVideoBitrate","media","bitrate","setBitrateLimit","preferredVideoFormat","setRemoteDescription","RTCSessionDescription","createAnswer","answer","setLocalDescription","peer_id","candidate","basicCandidate","addIceCandidate","RTCIceCandidate","onicecandidate","oniceconnectionstatechange","state","iceConnectionState","iceStateChange","connected","connectionClosed","createPeerConnection","ice_servers","onerror","onclose","webSocketClosedByUser","initWebSocket","bundlePolicy","transceiverConfig","direction","simulcastConfig","simulcast","kind","sendEncodings","layer","rid","active","maxBitrate","addTransceiver","createOffer","videoTracks","settings","width","height","framerate","body","ok","status","locationHeaderValue","toString","links","map","link","trim","turnIceServers","urlsMatch","relMatch","usernameMatch","credentialMatch","credentialTypeMatch","credentialType","pcConfig","getConfiguration","setConfiguration","text","startWhip","stopStreaming","remove","getVersion","create","initConfig","getDevices","warn","videoFound","audioFound","deviceInfos","devices","deviceInfo","label","audioinput","audiooutput","videoinput","other","gotDevices","enumerateDevices"],"sourceRoot":""}
1
+ {"version":3,"file":"OvenLiveKit.min.js","mappings":"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","sources":["webpack://OvenLiveKit/webpack/universalModuleDefinition","webpack://OvenLiveKit/webpack/bootstrap","webpack://OvenLiveKit/webpack/runtime/define property getters","webpack://OvenLiveKit/webpack/runtime/hasOwnProperty shorthand","webpack://OvenLiveKit/./src/OvenLiveKit.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"OvenLiveKit\"] = factory();\n\telse\n\t\troot[\"OvenLiveKit\"] = factory();\n})(self, () => {\nreturn ","// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const OvenLiveKit = {};\n\nconst version = '1.5.8';\nconst logHeader = 'OvenLiveKit.js :';\nconst logEventHeader = 'OvenLiveKit.js ====';\n\n// private methods\nfunction getTransportQuery(endpointUrl) {\n\n try {\n const url = new URL(endpointUrl);\n const transport = url.searchParams.get('transport');\n\n if (transport === null) {\n return undefined;\n }\n\n const normalized = transport.toLowerCase();\n\n return normalized;\n } catch (e) {\n return undefined;\n }\n}\n\nfunction sendMessage(webSocket, message) {\n\n if (webSocket) {\n webSocket.send(JSON.stringify(message));\n }\n}\n\nfunction generateDomainFromUrl(url) {\n let result = '';\n let match;\n if (match = url.match(/^(?:wss?:\\/\\/)?(?:[^@\\n]+@)?(?:www\\.)?([^:\\/\\n\\?\\=]+)/im)) {\n result = match[1];\n }\n\n return result;\n}\n\nfunction findIp(string) {\n\n let result = '';\n let match;\n\n if (match = string.match(new RegExp('\\\\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\b', 'gi'))) {\n result = match[0];\n }\n\n return result;\n}\n\nfunction getFormatNumber(sdp, format) {\n\n const lines = sdp.split('\\r\\n');\n let formatNumber = -1;\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n lines[i] = lines[i].toLowerCase();\n\n if (lines[i].indexOf('a=rtpmap') === 0 && lines[i].indexOf(format.toLowerCase()) > -1) {\n // parsing \"a=rtpmap:100 H264/90000\" line\n // a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding parameters >]\n formatNumber = lines[i].split(' ')[0].split(':')[1];\n break;\n }\n }\n\n return formatNumber;\n}\n\nfunction setPreferredVideoFormat(sdp, formatName) {\n\n const formatNumber = getFormatNumber(sdp, formatName);\n\n if (formatNumber === -1) {\n return sdp;\n }\n\n let newLines = [];\n const lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length - 1; i++) {\n\n const line = lines[i];\n\n if (line.indexOf('m=video') === 0) {\n\n // m=<media> <port>/<number of ports> <transport> <fmt list>\n const others = line.split(' ').slice(0, 3);\n const formats = line.split(' ').slice(3);\n formats.sort(function (x, y) { return x == formatNumber ? -1 : y == formatNumber ? 1 : 0; });\n newLines.push(others.concat(formats).join(' '));\n } else {\n newLines.push(line);\n }\n\n }\n\n return newLines.join('\\r\\n') + '\\r\\n';\n}\n\nfunction removeFormat(sdp, formatNumber) {\n let newLines = [];\n let lines = sdp.split('\\r\\n');\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n newLines.push(lines[i].replace(' ' + formatNumber + '', ''));\n } else if (lines[i].indexOf(formatNumber + '') > -1) {\n\n } else {\n newLines.push(lines[i]);\n }\n }\n\n return newLines.join('\\r\\n')\n}\n\nasync function getStreamForDeviceCheck(type) {\n\n // High resolution video constraints makes browser to get maximum resolution of video device.\n const constraints = {\n };\n\n if (type === 'both') {\n constraints.audio = true;\n constraints.video = true;\n } else if (type === 'audio') {\n constraints.audio = true;\n } else if (type === 'video') {\n constraints.video = true;\n }\n\n const permissionStream = await navigator.mediaDevices.getUserMedia(constraints);\n permissionStream.getTracks().forEach(track => track.stop());\n await new Promise(resolve => setTimeout(resolve, 500));\n\n return true;\n}\n\nasync function getDevices() {\n\n return await navigator.mediaDevices.enumerateDevices();\n}\n\nfunction gotDevices(deviceInfos) {\n\n let devices = {\n 'audioinput': [],\n 'audiooutput': [],\n 'videoinput': [],\n 'other': [],\n };\n\n for (let i = 0; i !== deviceInfos.length; ++i) {\n\n const deviceInfo = deviceInfos[i];\n\n let info = {};\n\n info.deviceId = deviceInfo.deviceId;\n\n if (deviceInfo.kind === 'audioinput') {\n\n info.label = deviceInfo.label || `microphone ${devices.audioinput.length + 1}`;\n devices.audioinput.push(info);\n } else if (deviceInfo.kind === 'audiooutput') {\n\n info.label = deviceInfo.label || `speaker ${devices.audiooutput.length + 1}`;\n devices.audiooutput.push(info);\n } else if (deviceInfo.kind === 'videoinput') {\n\n info.label = deviceInfo.label || `camera ${devices.videoinput.length + 1}`;\n devices.videoinput.push(info);\n } else {\n\n info.label = deviceInfo.label || `other ${devices.other.length + 1}`;\n devices.other.push(info);\n }\n }\n\n return devices;\n}\n\nfunction initConfig(instance, options) {\n\n // webrtc or whip\n instance.streamingMode = null;\n\n instance.inputStream = null;\n instance.webSocket = null;\n instance.peerConnection = null;\n instance.connectionConfig = {};\n\n instance.videoElement = null;\n instance.endpointUrl = null;\n instance.resourceUrl = null;\n\n if (options && options.callbacks) {\n\n instance.callbacks = options.callbacks;\n } else {\n instance.callbacks = {};\n }\n}\n\nfunction addMethod(instance) {\n\n function errorHandler(error) {\n\n if (instance.callbacks.error) {\n\n instance.callbacks.error(error);\n }\n }\n\n async function fetchWithRedirect(url, options) {\n let fetched = await fetch(url, options);\n\n while (fetched.redirected) {\n url = fetched.url;\n fetched = await fetch(url, options);\n }\n\n return fetched;\n }\n\n function getUserMedia(constraints) {\n\n if (!constraints) {\n\n constraints = {\n video: {\n deviceId: undefined\n },\n audio: {\n deviceId: undefined\n }\n };\n }\n\n console.info(logHeader, 'Request Stream To Input Devices With Constraints', constraints);\n\n return navigator.mediaDevices.getUserMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Input Device', stream);\n\n stream.getVideoTracks().forEach(function (track) {\n console.info(logHeader, 'Video Track Settings From Input Stream', track.getSettings());\n });\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Input Device', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function getDisplayMedia(constraints) {\n\n if (!constraints) {\n constraints = {};\n }\n\n console.info(logHeader, 'Request Stream To Display With Constraints', constraints);\n\n return navigator.mediaDevices.getDisplayMedia(constraints)\n .then(function (stream) {\n\n console.info(logHeader, 'Received Media Stream From Display', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n\n resolve(stream);\n });\n })\n .catch(function (error) {\n\n console.error(logHeader, 'Can\\'t Get Media Stream From Display', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n });\n }\n\n function setMediaStream(stream) {\n // Check if a valid stream is provided\n if (!stream || !(stream instanceof MediaStream)) {\n\n const error = new Error(\"Invalid MediaStream provided\");\n console.error(logHeader, 'Invalid MediaStream', error);\n errorHandler(error);\n\n return new Promise(function (resolve, reject) {\n reject(error);\n });\n }\n\n console.info(logHeader, 'Received Media Stream', stream);\n\n instance.inputStream = stream;\n\n let elem = instance.videoElement;\n\n // Attach stream to video element when video element is provided.\n if (elem) {\n elem.srcObject = stream;\n\n elem.onloadedmetadata = function (e) {\n elem.play();\n };\n }\n\n return new Promise(function (resolve) {\n resolve(stream);\n });\n }\n\n // From https://webrtchacks.com/limit-webrtc-bandwidth-sdp/\n function setBitrateLimit(sdp, media, bitrate) {\n\n let lines = sdp.split('\\r\\n');\n let line = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].indexOf('m=' + media) === 0) {\n line = i;\n break;\n }\n }\n if (line === -1) {\n // Could not find the m line for media\n return sdp;\n }\n\n // Pass the m line\n line++;\n\n // Skip i and c lines\n while (lines[line].indexOf('i=') === 0 || lines[line].indexOf('c=') === 0) {\n\n line++;\n }\n\n // If we're on a b line, replace it\n if (lines[line].indexOf('b') === 0) {\n\n lines[line] = 'b=AS:' + bitrate;\n\n return lines.join('\\r\\n');\n }\n\n // Add a new b line\n let newLines = lines.slice(0, line)\n\n newLines.push('b=AS:' + bitrate)\n newLines = newLines.concat(lines.slice(line, lines.length))\n\n return newLines.join('\\r\\n')\n }\n\n function normalizeFrameRate(frameRate) {\n if (Number.isInteger(frameRate)) {\n return frameRate;\n }\n return parseFloat(frameRate).toFixed(2);\n }\n\n function initWebSocket(endpointUrl) {\n\n if (!endpointUrl) {\n errorHandler('endpointUrl is required');\n return;\n }\n\n let webSocket = null;\n\n try {\n\n webSocket = new WebSocket(endpointUrl);\n } catch (error) {\n\n errorHandler(error);\n }\n\n\n instance.webSocket = webSocket;\n\n webSocket.onopen = function () {\n\n // Request offer at the first time.\n sendMessage(webSocket, {\n command: 'request_offer'\n });\n };\n\n webSocket.onmessage = function (e) {\n\n let message = JSON.parse(e.data);\n\n if (message.error) {\n console.error('webSocket.onmessage', message.error);\n errorHandler(message.error);\n }\n\n if (message.command === 'offer') {\n\n // OME returns offer. Start create peer connection.\n createPeerConnection(\n message.id,\n message.peer_id,\n message.sdp,\n message.candidates,\n message.ice_servers\n );\n }\n };\n\n webSocket.onerror = function (error) {\n\n console.error('webSocket.onerror', error);\n errorHandler(error);\n };\n\n webSocket.onclose = function (e) {\n\n if (!instance.webSocketClosedByUser) {\n\n if (instance.callbacks.connectionClosed) {\n instance.callbacks.connectionClosed('websocket', e);\n }\n }\n };\n\n }\n\n async function startWhip(endpointUrl) {\n\n if (instance.peerConnection) {\n console.error('Connection already established');\n errorHandler('Connection already established');\n return;\n }\n\n const peerConnectionConfig = {\n bundlePolicy: \"max-bundle\"\n };\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from local config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else {\n // last priority using default ice servers.\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n }\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n const peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n if (!instance.inputStream) {\n console.error('No input stream in OvenLiveKit');\n errorHandler('No input stream in OvenLiveKit');\n return;\n }\n\n for (const track of instance.inputStream.getTracks()) {\n console.log(logHeader, 'Adding track: ', track);\n\n const transceiverConfig = {\n direction: 'sendonly'\n };\n\n // Add simulcast layers if configured\n const simulcastConfig = instance.connectionConfig.simulcast;\n\n if (track.kind === 'video' && simulcastConfig && simulcastConfig.length > 0) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n for (let i = 0; i < simulcastConfig.length; i++) {\n\n const layer = {\n rid: i,\n active: true,\n // VP9/AV1 simulcast in Chrome requires an explicit scalabilityMode together with\n // scaleResolutionDownBy; without it, multiple encodings fall back to legacy SVC and\n // extra rids are dropped. Default to L1T1 (overridable via simulcastConfig[i]).\n // Multiple active encodings only allow L1T1/L1T2/L1T3 and all layers must share the same value.\n scalabilityMode: 'L1T1',\n ...simulcastConfig[i]\n };\n\n console.log(logHeader, `Adding simulcast layer to: ${track.kind}`, layer);\n\n transceiverConfig.sendEncodings.push(layer);\n }\n }\n\n if (track.kind === 'video' && instance.connectionConfig.maxVideoBitrate) {\n\n if (!transceiverConfig.sendEncodings) {\n transceiverConfig.sendEncodings = [];\n }\n\n transceiverConfig.sendEncodings.push({\n maxBitrate: instance.connectionConfig.maxVideoBitrate * 1000,\n });\n\n console.log(logHeader, `Setting max bitrate to: ${instance.connectionConfig.maxVideoBitrate} kbps`);\n }\n\n peerConnection.addTransceiver(track, transceiverConfig);\n }\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection failed', e);\n instance.callbacks.errorHandler(e);\n }\n }\n\n if (state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection disconnected or closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n\n const offer = await peerConnection.createOffer();\n console.log(logHeader, 'Offer SDP from browser: ', offer.sdp);\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat);\n } else {\n // default to H264\n offer.sdp = setPreferredVideoFormat(offer.sdp, 'H264');\n }\n\n const headers = {\n \"Content-Type\": \"application/sdp\"\n };\n\n // Set Oven-Capabilities header based on video track settings and simulcast layers.\n const videoTracks = instance.inputStream.getVideoTracks();\n\n if (videoTracks && videoTracks.length === 1) {\n\n const track = videoTracks[0];\n const settings = track.getSettings();\n\n console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);\n\n const width = settings.width;\n const height = settings.height;\n const frameRate = settings.frameRate;\n\n const simulcastConfig = instance.connectionConfig.simulcast;\n const layerCount = simulcastConfig ? simulcastConfig.length : 0;\n\n if (layerCount >= 2) {\n\n const parts = [];\n\n for (let i = 0; i < layerCount; i++) {\n\n const layer = simulcastConfig[i] || {};\n const rid = layer.rid !== undefined ? layer.rid : i;\n\n const scale = parseFloat(layer.scaleResolutionDownBy);\n\n if (!isNaN(scale) && scale > 0 && typeof width === 'number' && typeof height === 'number') {\n const scaledWidth = Math.floor(Math.floor(width / scale) / 2) * 2;\n const scaledHeight = Math.floor(Math.floor(height / scale) / 2) * 2;\n\n parts.push(`rid:${rid}:max_width=${scaledWidth}`);\n parts.push(`rid:${rid}:max_height=${scaledHeight}`);\n }\n\n if (frameRate) {\n parts.push(`rid:${rid}:max_fps=${normalizeFrameRate(frameRate)}`);\n }\n }\n\n if (parts.length > 0) {\n console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);\n headers['Oven-Capabilities'] = parts.join(', ');\n }\n } else {\n\n if (typeof width === 'number' && typeof height === 'number') {\n\n const parts = [`max_width=${width}`, `max_height=${height}`];\n\n if (frameRate) {\n parts.push(`max_fps=${normalizeFrameRate(frameRate)}`);\n }\n\n console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);\n headers['Oven-Capabilities'] = parts.join(', ');\n }\n }\n } else {\n\n if (!videoTracks || videoTracks.length === 0) {\n console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');\n }\n\n if (videoTracks && videoTracks.length > 1) {\n console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);\n }\n }\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n console.log(logHeader, 'Offer SDP modified by OvenLiveKit: ', offer.sdp);\n\n const fetched = await fetchWithRedirect(endpointUrl, {\n method: \"POST\",\n body: offer.sdp,\n headers\n });\n\n if (!fetched.ok) {\n console.error('Failed to fetch', fetched.status);\n errorHandler(`Failed to fetch ${fetched.status}`);\n closePeerConnection();\n return;\n }\n\n if (!fetched.headers.get(\"location\")) {\n console.error('No location header on answer response');\n errorHandler('No location header on answer response');\n return;\n }\n\n // update endpointUrl\n instance.endpointUrl = fetched.url;\n console.log(logHeader, 'Endpoint Url: ', instance.endpointUrl);\n\n const locationHeaderValue = fetched.headers.get(\"location\");\n console.log(logHeader, 'Location header ', locationHeaderValue);\n\n instance.resourceUrl = new URL(locationHeaderValue, fetched.url).toString();\n console.log(logHeader, 'Resource URL: ', instance.resourceUrl);\n\n\n if (fetched.headers.get(\"Link\")) {\n console.log(logHeader, 'Link Header Found. Update ');\n\n const links = fetched.headers.get(\"Link\").split(',').map(link => link.trim());\n\n // OvenMediaEngine returns turn servers in Link header with rel=\"ice-server\".\n // For example: <turn:119.196.229.2:3478?transport=tcp>; rel=\"ice-server\"; username=\"user\"; credential=\"cred\"; credential-type=\"password\"\n const turnIceServers = [];\n\n links.forEach(link => {\n\n console.log(logHeader, 'Link: ', link);\n const urlsMatch = link.match(/<([^>]+)>/);\n const relMatch = link.match(/rel=\"([^\"]+)\"/);\n const usernameMatch = link.match(/username=\"([^\"]+)\"/);\n const credentialMatch = link.match(/credential=\"([^\"]+)\"/);\n const credentialTypeMatch = link.match(/credential-type=\"([^\"]+)\"/);\n\n if (urlsMatch && relMatch && relMatch[1] === 'ice-server') {\n\n const iceServer = {\n urls: urlsMatch[1]\n };\n\n if (usernameMatch) {\n iceServer.username = usernameMatch[1];\n };\n\n if (credentialMatch) {\n iceServer.credential = credentialMatch[1];\n };\n\n if (credentialTypeMatch) {\n iceServer.credentialType = credentialTypeMatch[1];\n }\n\n turnIceServers.push(iceServer);\n }\n });\n\n console.log(logHeader, 'Parsed ICE Servers from Link header: ', turnIceServers);\n\n // update peer connection with new turn servers from OME.\n if (turnIceServers.length > 0) {\n const pcConfig = peerConnection.getConfiguration();\n\n // only update ice servers when local config does not have ice servers.\n if (!instance.connectionConfig.iceServers) {\n pcConfig.iceServers = turnIceServers;\n console.log(logHeader, 'Updated Peer Connection ICE Servers from Link header');\n }\n\n // set ice transport policy to relay if not set in local config.\n if (!instance.connectionConfig.iceTransportPolicy) {\n\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n pcConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n pcConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n\n peerConnection.setConfiguration(pcConfig);\n }\n }\n\n const answer = await fetched.text();\n console.log(logHeader, 'Response Answer SDP: ', answer);\n\n try {\n await peerConnection.setLocalDescription(offer);\n } catch (error) {\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n }\n\n try {\n await peerConnection.setRemoteDescription({\n type: \"answer\",\n sdp: answer\n });\n } catch (error) {\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n }\n }\n\n async function stopWhip() {\n\n if (!instance.peerConnection) {\n console.error('No connection to close');\n errorHandler('No connection to close');\n return;\n }\n\n closePeerConnection();\n\n if (instance.resourceUrl) {\n\n const headers = {\n };\n\n if (instance.connectionConfig.httpHeaders) {\n Object.assign(headers, instance.connectionConfig.httpHeaders);\n }\n\n await fetchWithRedirect(instance.resourceUrl, {\n method: \"DELETE\",\n headers\n });\n }\n }\n\n function appendFmtp(sdp) {\n\n const fmtpStr = instance.connectionConfig.sdp.appendFmtp;\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n let fmtpLineFound = false;\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=fmtp:' + payloads[i]) === 0) {\n fmtpLineFound = true;\n lines[j] += ';' + fmtpStr;\n }\n }\n\n if (!fmtpLineFound) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=fmtp:' + payloads[i] + ' ' + fmtpStr;\n }\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function appendOrientation(sdp) {\n\n const lines = sdp.split('\\r\\n');\n const payloads = [];\n\n for (let i = 0; i < lines.length; i++) {\n\n if (lines[i].indexOf('m=video') === 0) {\n\n let tokens = lines[i].split(' ')\n\n for (let j = 3; j < tokens.length; j++) {\n\n payloads.push(tokens[j]);\n }\n\n break;\n }\n }\n\n for (let i = 0; i < payloads.length; i++) {\n\n for (let j = 0; j < lines.length; j++) {\n\n if (lines[j].indexOf('a=rtpmap:' + payloads[i]) === 0) {\n\n lines[j] += '\\r\\na=extmap:' + payloads[i] + ' urn:3gpp:video-orientation';\n }\n }\n }\n\n return lines.join('\\r\\n')\n }\n\n function createPeerConnection(id, peerId, offer, candidates, iceServers) {\n\n let peerConnectionConfig = {};\n\n if (instance.connectionConfig.iceServers) {\n\n // first priority using ice servers from user config.\n peerConnectionConfig.iceServers = instance.connectionConfig.iceServers;\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n }\n } else if (iceServers) {\n\n // second priority using ice servers from ome and force using TCP\n peerConnectionConfig.iceServers = [];\n\n for (let i = 0; i < iceServers.length; i++) {\n\n let iceServer = iceServers[i];\n\n let regIceServer = {};\n\n regIceServer.urls = iceServer.urls;\n\n let hasWebSocketUrl = false;\n let webSocketUrl = generateDomainFromUrl(instance.endpointUrl);\n\n for (let j = 0; j < regIceServer.urls.length; j++) {\n\n let serverUrl = regIceServer.urls[j];\n\n if (serverUrl.indexOf(webSocketUrl) > -1) {\n hasWebSocketUrl = true;\n break;\n }\n }\n\n if (!hasWebSocketUrl) {\n\n if (regIceServer.urls.length > 0) {\n\n let cloneIceServer = regIceServer.urls[0];\n let ip = findIp(cloneIceServer);\n\n if (webSocketUrl && ip) {\n regIceServer.urls.push(cloneIceServer.replace(ip, webSocketUrl));\n }\n }\n }\n\n regIceServer.username = iceServer.user_name;\n regIceServer.credential = iceServer.credential;\n\n peerConnectionConfig.iceServers.push(regIceServer);\n }\n\n if (instance.connectionConfig.iceTransportPolicy) {\n\n peerConnectionConfig.iceTransportPolicy = instance.connectionConfig.iceTransportPolicy;\n } else {\n if (getTransportQuery(instance.endpointUrl) == 'all') {\n peerConnectionConfig.iceTransportPolicy = 'all';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to all');\n } else {\n peerConnectionConfig.iceTransportPolicy = 'relay';\n console.log(logHeader, 'Set Peer Connection iceTransportPolicy to relay');\n }\n }\n }\n\n let advancedSetting = {\n optional: [\n {\n googHighStartBitrate: {\n exact: !0\n }\n },\n {\n googPayloadPadding: {\n exact: !0\n }\n },\n {\n googScreencastMinBitrate: {\n exact: 500\n }\n },\n {\n enableDscp: {\n exact: true\n }\n }\n ]\n };\n\n console.info(logHeader, 'Create Peer Connection With Config', peerConnectionConfig);\n\n let peerConnection = new RTCPeerConnection(peerConnectionConfig);\n\n instance.peerConnection = peerConnection;\n\n // set local stream\n instance.inputStream.getTracks().forEach(function (track) {\n\n console.info(logHeader, 'Add Track To Peer Connection', track);\n peerConnection.addTrack(track, instance.inputStream);\n });\n\n if (instance.connectionConfig.maxVideoBitrate) {\n\n // if bandwith limit is set. modify sdp from ome to limit acceptable bandwidth of ome\n offer.sdp = setBitrateLimit(offer.sdp, 'video', instance.connectionConfig.maxVideoBitrate);\n }\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n offer.sdp = appendFmtp(offer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n offer.sdp = setPreferredVideoFormat(offer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n\n // offer.sdp = appendOrientation(offer.sdp);\n console.info(logHeader, 'Modified offer sdp\\n\\n' + offer.sdp);\n\n peerConnection.setRemoteDescription(new RTCSessionDescription(offer))\n .then(function () {\n\n peerConnection.createAnswer()\n .then(function (answer) {\n\n if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {\n\n answer.sdp = appendFmtp(answer.sdp);\n }\n\n if (instance.connectionConfig.preferredVideoFormat) {\n answer.sdp = setPreferredVideoFormat(answer.sdp, instance.connectionConfig.preferredVideoFormat)\n }\n\n console.info(logHeader, 'Modified answer sdp\\n\\n' + answer.sdp);\n\n peerConnection.setLocalDescription(answer)\n .then(function () {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'answer',\n sdp: answer\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setLocalDescription', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.createAnswer', error);\n errorHandler(error);\n });\n })\n .catch(function (error) {\n\n console.error('peerConnection.setRemoteDescription', error);\n errorHandler(error);\n });\n\n if (candidates) {\n\n addIceCandidate(peerConnection, candidates);\n }\n\n peerConnection.onicecandidate = function (e) {\n\n if (e.candidate && e.candidate.candidate) {\n\n sendMessage(instance.webSocket, {\n id: id,\n peer_id: peerId,\n command: 'candidate',\n candidates: [e.candidate]\n });\n }\n };\n\n peerConnection.oniceconnectionstatechange = function (e) {\n\n let state = peerConnection.iceConnectionState;\n\n if (instance.callbacks.iceStateChange) {\n\n console.info(logHeader, 'ICE State', '[' + state + ']');\n instance.callbacks.iceStateChange(state);\n }\n\n if (state === 'connected') {\n\n if (instance.callbacks.connected) {\n instance.callbacks.connected(e);\n }\n }\n\n if (state === 'failed' || state === 'disconnected' || state === 'closed') {\n\n if (instance.callbacks.connectionClosed) {\n console.error(logHeader, 'Ice connection closed', e);\n instance.callbacks.connectionClosed('ice', e);\n }\n }\n }\n }\n\n function addIceCandidate(peerConnection, candidates) {\n\n for (let i = 0; i < candidates.length; i++) {\n\n if (candidates[i] && candidates[i].candidate) {\n\n let basicCandidate = candidates[i];\n\n peerConnection.addIceCandidate(new RTCIceCandidate(basicCandidate))\n .then(function () {\n\n })\n .catch(function (error) {\n\n console.error('peerConnection.addIceCandidate', error);\n errorHandler(error);\n });\n }\n }\n }\n\n function closePeerConnection() {\n if (instance.peerConnection) {\n\n // remove tracks from peer connection\n instance.peerConnection.getSenders().forEach(function (sender) {\n instance.peerConnection.removeTrack(sender);\n });\n\n instance.peerConnection.close();\n instance.peerConnection = null;\n delete instance.peerConnection;\n }\n }\n\n function closeWebSocket() {\n\n if (instance.webSocket) {\n\n instance.webSocket.close();\n instance.webSocket = null;\n delete instance.webSocket;\n }\n }\n\n function closeInputStream() {\n // release video, audio stream\n if (instance.inputStream) {\n\n instance.inputStream.getTracks().forEach(track => {\n\n track.stop();\n instance.inputStream.removeTrack(track);\n });\n\n if (instance.videoElement) {\n instance.videoElement.srcObject = null;\n }\n\n instance.inputStream = null;\n delete instance.inputStream;\n }\n }\n\n // instance methods\n instance.attachMedia = function (videoElement) {\n\n instance.videoElement = videoElement;\n };\n\n instance.getUserMedia = function (constraints) {\n\n return getUserMedia(constraints);\n };\n\n instance.getDisplayMedia = function (constraints) {\n\n return getDisplayMedia(constraints);\n };\n\n instance.setMediaStream = function (stream) {\n\n return setMediaStream(stream);\n };\n\n instance.startStreaming = function (endpointUrl, connectionConfig) {\n\n console.info(logEventHeader, `Start Streaming to ${endpointUrl} with connectionConfig`, connectionConfig);\n\n if (!endpointUrl) {\n console.error('endpointUrl is required');\n errorHandler('endpointUrl is required');\n return;\n }\n\n instance.endpointUrl = endpointUrl;\n\n if (connectionConfig) {\n instance.connectionConfig = connectionConfig;\n }\n\n try {\n\n const protocol = new URL(endpointUrl).protocol;\n\n if (protocol === 'wss:' || protocol === 'ws:') {\n\n instance.streamingMode = 'webrtc';\n initWebSocket(endpointUrl);\n } else if (protocol === 'https:' || protocol === 'http:') {\n\n instance.streamingMode = 'whip';\n startWhip(endpointUrl);\n } else {\n console.error('Invalid protocol', error);\n errorHandler(error);\n }\n\n } catch (error) {\n console.error('Cannot parse connection URL', error);\n errorHandler(error);\n }\n };\n\n instance.stopStreaming = async function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n\n await stopWhip();\n }\n\n if (instance.callbacks.connectionClosed) {\n console.log(logHeader, 'Connection closed by user');\n instance.callbacks.connectionClosed('user', 'Connection closed by user');\n }\n };\n\n instance.remove = function () {\n\n if (instance.streamingMode === 'webrtc') {\n\n instance.webSocketClosedByUser = true;\n\n closeWebSocket();\n closePeerConnection();\n } else if (instance.streamingMode === 'whip') {\n stopWhip();\n }\n\n closeInputStream();\n\n console.info(logEventHeader, 'Removed');\n\n };\n}\n\nOvenLiveKit.getVersion = function () {\n return version;\n}\n\n// static methods\nOvenLiveKit.create = function (options) {\n\n console.info(logEventHeader, 'Create WebRTC Input ' + version);\n\n let instance = {};\n\n instance.webSocketClosedByUser = false;\n\n initConfig(instance, options);\n addMethod(instance);\n\n return instance;\n};\n\nOvenLiveKit.getDevices = async function (type = 'both') {\n\n try {\n // First check both audio and video sources are available.\n await getStreamForDeviceCheck(type);\n } catch (e) {\n\n console.warn(logHeader, 'Can not find Video and Audio devices.', e);\n\n let videoFound = null;\n let audioFound = null;\n\n try {\n videoFound = await getStreamForDeviceCheck('video');\n } catch (e) {\n console.warn(logHeader, 'Can not find Video devices.', e);\n }\n\n try {\n audioFound = await getStreamForDeviceCheck('audio');\n } catch (e) {\n console.warn(logHeader, 'Can not find Audio devices.', e);\n }\n\n if (!videoFound && !audioFound) {\n throw new Error('No input devices were found.');\n }\n }\n\n const deviceInfos = await getDevices();\n return gotDevices(deviceInfos)\n};\n\nexport default OvenLiveKit;\n"],"names":["root","factory","exports","module","define","amd","self","__webpack_require__","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","OvenLiveKit","version","logHeader","logEventHeader","getTransportQuery","endpointUrl","transport","URL","searchParams","toLowerCase","e","sendMessage","webSocket","message","send","JSON","stringify","generateDomainFromUrl","url","match","result","findIp","string","RegExp","setPreferredVideoFormat","sdp","formatName","formatNumber","format","lines","split","i","length","indexOf","getFormatNumber","newLines","line","others","slice","formats","sort","x","y","push","concat","join","async","getStreamForDeviceCheck","type","constraints","audio","video","navigator","mediaDevices","getUserMedia","getTracks","forEach","track","stop","Promise","resolve","setTimeout","addMethod","instance","errorHandler","error","callbacks","fetchWithRedirect","options","fetched","fetch","redirected","normalizeFrameRate","frameRate","Number","isInteger","parseFloat","toFixed","stopWhip","peerConnection","console","closePeerConnection","resourceUrl","headers","connectionConfig","httpHeaders","assign","method","appendFmtp","fmtpStr","payloads","tokens","j","fmtpLineFound","getSenders","sender","removeTrack","close","closeWebSocket","attachMedia","videoElement","deviceId","undefined","info","then","stream","getVideoTracks","getSettings","inputStream","elem","srcObject","onloadedmetadata","play","catch","reject","getDisplayMedia","setMediaStream","MediaStream","Error","startStreaming","protocol","streamingMode","WebSocket","onopen","command","onmessage","parse","data","id","peerId","offer","candidates","iceServers","peerConnectionConfig","iceTransportPolicy","iceServer","regIceServer","urls","hasWebSocketUrl","webSocketUrl","cloneIceServer","ip","replace","username","user_name","credential","log","RTCPeerConnection","addTrack","maxVideoBitrate","media","bitrate","setBitrateLimit","preferredVideoFormat","setRemoteDescription","RTCSessionDescription","createAnswer","answer","setLocalDescription","peer_id","candidate","basicCandidate","addIceCandidate","RTCIceCandidate","onicecandidate","oniceconnectionstatechange","state","iceConnectionState","iceStateChange","connected","connectionClosed","createPeerConnection","ice_servers","onerror","onclose","webSocketClosedByUser","initWebSocket","bundlePolicy","transceiverConfig","direction","simulcastConfig","simulcast","kind","sendEncodings","layer","rid","active","scalabilityMode","maxBitrate","addTransceiver","createOffer","videoTracks","settings","width","height","layerCount","parts","scale","scaleResolutionDownBy","isNaN","scaledWidth","Math","floor","scaledHeight","body","ok","status","locationHeaderValue","toString","links","map","link","trim","turnIceServers","urlsMatch","relMatch","usernameMatch","credentialMatch","credentialTypeMatch","credentialType","pcConfig","getConfiguration","setConfiguration","text","startWhip","stopStreaming","remove","getVersion","create","initConfig","getDevices","warn","videoFound","audioFound","deviceInfos","devices","deviceInfo","label","audioinput","audiooutput","videoinput","other","gotDevices","enumerateDevices"],"sourceRoot":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ovenlivekit",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "OvenLiveKit for Web is an open source JavaScript SDK suite for live streaming from web browsers to OvenMediaEngine.",
5
5
  "main": "dist/OvenLiveKit.min.js",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  const OvenLiveKit = {};
2
2
 
3
- const version = '1.5.6';
3
+ const version = '1.5.8';
4
4
  const logHeader = 'OvenLiveKit.js :';
5
5
  const logEventHeader = 'OvenLiveKit.js ====';
6
6
 
@@ -544,6 +544,11 @@ function addMethod(instance) {
544
544
  const layer = {
545
545
  rid: i,
546
546
  active: true,
547
+ // VP9/AV1 simulcast in Chrome requires an explicit scalabilityMode together with
548
+ // scaleResolutionDownBy; without it, multiple encodings fall back to legacy SVC and
549
+ // extra rids are dropped. Default to L1T1 (overridable via simulcastConfig[i]).
550
+ // Multiple active encodings only allow L1T1/L1T2/L1T3 and all layers must share the same value.
551
+ scalabilityMode: 'L1T1',
547
552
  ...simulcastConfig[i]
548
553
  };
549
554
 
@@ -604,7 +609,7 @@ function addMethod(instance) {
604
609
  }
605
610
 
606
611
  const offer = await peerConnection.createOffer();
607
- console.log(logHeader, 'Offer SDP: ', offer.sdp);
612
+ console.log(logHeader, 'Offer SDP from browser: ', offer.sdp);
608
613
 
609
614
  if (instance.connectionConfig.sdp && instance.connectionConfig.sdp.appendFmtp) {
610
615
  offer.sdp = appendFmtp(offer.sdp);
@@ -621,47 +626,82 @@ function addMethod(instance) {
621
626
  "Content-Type": "application/sdp"
622
627
  };
623
628
 
624
- // Set Oven-Capabilities header if not over two simulcast layers.
625
- if (!instance.connectionConfig.simulcast || instance.connectionConfig.simulcast.length < 2) {
629
+ // Set Oven-Capabilities header based on video track settings and simulcast layers.
630
+ const videoTracks = instance.inputStream.getVideoTracks();
626
631
 
627
- const videoTracks = instance.inputStream.getVideoTracks();
632
+ if (videoTracks && videoTracks.length === 1) {
628
633
 
629
- if (videoTracks && videoTracks.length === 1) {
634
+ const track = videoTracks[0];
635
+ const settings = track.getSettings();
630
636
 
631
- for (let i = 0; i < videoTracks.length; i++) {
637
+ console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);
632
638
 
633
- const track = videoTracks[i];
634
- const settings = track.getSettings();
639
+ const width = settings.width;
640
+ const height = settings.height;
641
+ const frameRate = settings.frameRate;
635
642
 
636
- console.log(logHeader, 'Video track settings for Oven-Capabilities:', settings);
643
+ const simulcastConfig = instance.connectionConfig.simulcast;
644
+ const layerCount = simulcastConfig ? simulcastConfig.length : 0;
645
+
646
+ if (layerCount >= 2) {
647
+
648
+ const parts = [];
649
+
650
+ for (let i = 0; i < layerCount; i++) {
651
+
652
+ const layer = simulcastConfig[i] || {};
653
+ const rid = layer.rid !== undefined ? layer.rid : i;
637
654
 
638
- const width = settings.width;
639
- const height = settings.height;
640
- const framerate = settings.frameRate;
655
+ const scale = parseFloat(layer.scaleResolutionDownBy);
641
656
 
642
- if (typeof width === 'number' && typeof height === 'number') {
643
- console.log(logHeader, `Setting Oven-Capabilities header: max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`);
644
- headers['Oven-Capabilities'] = `max_width=${width}, max_height=${height}, max_fps=${normalizeFrameRate(framerate)}`;
657
+ if (!isNaN(scale) && scale > 0 && typeof width === 'number' && typeof height === 'number') {
658
+ const scaledWidth = Math.floor(Math.floor(width / scale) / 2) * 2;
659
+ const scaledHeight = Math.floor(Math.floor(height / scale) / 2) * 2;
660
+
661
+ parts.push(`rid:${rid}:max_width=${scaledWidth}`);
662
+ parts.push(`rid:${rid}:max_height=${scaledHeight}`);
663
+ }
664
+
665
+ if (frameRate) {
666
+ parts.push(`rid:${rid}:max_fps=${normalizeFrameRate(frameRate)}`);
645
667
  }
646
668
  }
647
- } else {
648
669
 
649
- if (!videoTracks || videoTracks.length === 0) {
650
- console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');
670
+ if (parts.length > 0) {
671
+ console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);
672
+ headers['Oven-Capabilities'] = parts.join(', ');
651
673
  }
674
+ } else {
652
675
 
653
- if (videoTracks && videoTracks.length > 1) {
654
- console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);
676
+ if (typeof width === 'number' && typeof height === 'number') {
677
+
678
+ const parts = [`max_width=${width}`, `max_height=${height}`];
679
+
680
+ if (frameRate) {
681
+ parts.push(`max_fps=${normalizeFrameRate(frameRate)}`);
682
+ }
683
+
684
+ console.log(logHeader, `Setting Oven-Capabilities header: ${parts.join(', ')}`);
685
+ headers['Oven-Capabilities'] = parts.join(', ');
655
686
  }
656
687
  }
657
688
  } else {
658
- console.log(logHeader, 'Over two simulcast layers are set, skipping Oven-Capabilities header.');
689
+
690
+ if (!videoTracks || videoTracks.length === 0) {
691
+ console.log(logHeader, 'No video tracks found, skipping Oven-Capabilities header.');
692
+ }
693
+
694
+ if (videoTracks && videoTracks.length > 1) {
695
+ console.log(logHeader, `Multiple (${videoTracks.length}) video tracks found, skipping Oven-Capabilities header.`);
696
+ }
659
697
  }
660
698
 
661
699
  if (instance.connectionConfig.httpHeaders) {
662
700
  Object.assign(headers, instance.connectionConfig.httpHeaders);
663
701
  }
664
702
 
703
+ console.log(logHeader, 'Offer SDP modified by OvenLiveKit: ', offer.sdp);
704
+
665
705
  const fetched = await fetchWithRedirect(endpointUrl, {
666
706
  method: "POST",
667
707
  body: offer.sdp,