From 39cc4610e3f6acc982504b2fad18fdb3c74b29c6 Mon Sep 17 00:00:00 2001 From: Alexey Khit Date: Fri, 7 Jul 2023 22:06:03 +0300 Subject: [PATCH] Add ESLinter and fix JS lint problems --- package.json | 40 +++++++++ www/README.md | 12 ++- www/add.html | 148 ++++++++++++++++----------------- www/codecs.html | 32 +++---- www/editor.html | 8 +- www/index.html | 52 ++++++------ www/links.html | 94 ++++++++++----------- www/stream.html | 16 ++-- www/video-rtc.js | 193 ++++++++++++++++++++++--------------------- www/video-stream.js | 50 +++++------ www/webrtc-sync.html | 34 ++++---- www/webrtc.html | 70 ++++++++-------- 12 files changed, 405 insertions(+), 344 deletions(-) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..649791f6 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "devDependencies": { + "eslint": "^8.44.0", + "eslint-plugin-html": "^7.1.0" + }, + "eslintConfig": { + "env": { + "browser": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" + }, + "rules": { + "no-var": "error", + "no-undef": "error", + "no-unused-vars": "warn", + "prefer-const": "error", + "quotes": [ + "error", + "single" + ], + "semi": "error" + }, + "plugins": [ + "html" + ], + "overrides": [ + { + "files": [ + "*.html" + ], + "parserOptions": { + "sourceType": "script" + } + } + ] + } +} \ No newline at end of file diff --git a/www/README.md b/www/README.md index fa28938c..355a9af3 100644 --- a/www/README.md +++ b/www/README.md @@ -1,4 +1,14 @@ -# HTML5 +## Browser support + +[ECMAScript 2019 (ES10)](https://caniuse.com/?search=es10) supported by [iOS 12](https://en.wikipedia.org/wiki/IOS_12) (iPhone 5S, iPad Air, iPad Mini 2, etc.). + +But [ECMAScript 2017 (ES8)](https://caniuse.com/?search=es8) almost fine (`es6 + async`) and recommended for [React+TypeScript](https://github.com/typescript-cheatsheets/react). + +## Known problems + +- Autoplay doesn't work for WebRTC in Safari [read more](https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari/). + +## HTML5 **1. Autoplay video tag** diff --git a/www/add.html b/www/add.html index b2583cab..1be3e04d 100644 --- a/www/add.html +++ b/www/add.html @@ -1,5 +1,5 @@ - + Add Stream @@ -59,20 +59,20 @@ @@ -87,19 +87,19 @@ @@ -124,49 +124,49 @@ @@ -177,9 +177,9 @@ @@ -190,9 +190,9 @@ @@ -203,9 +203,9 @@ @@ -223,18 +223,18 @@ @@ -244,9 +244,9 @@ @@ -260,18 +260,18 @@ @@ -287,15 +287,15 @@ @@ -305,9 +305,9 @@ diff --git a/www/codecs.html b/www/codecs.html index 288d3feb..cee77b9a 100644 --- a/www/codecs.html +++ b/www/codecs.html @@ -18,21 +18,21 @@
\ No newline at end of file diff --git a/www/editor.html b/www/editor.html index 2a34492d..020a3adc 100644 --- a/www/editor.html +++ b/www/editor.html @@ -1,5 +1,5 @@ - + File Editor @@ -32,8 +32,8 @@
diff --git a/www/index.html b/www/index.html index 5125c50f..69403e98 100644 --- a/www/index.html +++ b/www/index.html @@ -1,5 +1,5 @@ - + @@ -84,55 +84,55 @@ 'delete', ]; - document.querySelector(".controls > button") - .addEventListener("click", () => { - const url = new URL("stream.html", location.href); + document.querySelector('.controls > button') + .addEventListener('click', () => { + const url = new URL('stream.html', location.href); - const streams = document.querySelectorAll("#streams input"); + const streams = document.querySelectorAll('#streams input'); streams.forEach(i => { - if (i.checked) url.searchParams.append("src", i.name); + if (i.checked) url.searchParams.append('src', i.name); }); - if (!url.searchParams.has("src")) return; + if (!url.searchParams.has('src')) return; - let mode = document.querySelectorAll(".controls input"); - mode = Array.from(mode).filter(i => i.checked).map(i => i.name).join(","); + let mode = document.querySelectorAll('.controls input'); + mode = Array.from(mode).filter(i => i.checked).map(i => i.name).join(','); window.location.href = `${url}&mode=${mode}`; }); - const tbody = document.getElementById("streams"); - tbody.addEventListener("click", ev => { - if (ev.target.innerText !== "delete") return; + const tbody = document.getElementById('streams'); + tbody.addEventListener('click', ev => { + if (ev.target.innerText !== 'delete') return; ev.preventDefault(); - const url = new URL("api/streams", location.href); + const url = new URL('api/streams', location.href); const src = decodeURIComponent(ev.target.dataset.name); - url.searchParams.set("src", src); - fetch(url, {method: "DELETE"}).then(reload); + url.searchParams.set('src', src); + fetch(url, {method: 'DELETE'}).then(reload); }); document.getElementById('selectall').addEventListener('change', ev => { document.querySelectorAll('#streams input').forEach(el => { - el.checked = ev.target.checked - }) - }) + el.checked = ev.target.checked; + }); + }); function reload() { - const url = new URL("api/streams", location.href); + const url = new URL('api/streams', location.href); fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => { - tbody.innerHTML = ""; + tbody.innerHTML = ''; for (const [name, value] of Object.entries(data)) { const online = value && value.consumers ? value.consumers.length : 0; const src = encodeURIComponent(name); const links = templates.map(link => { - return link.replace("{name}", src); - }).join(" "); + return link.replace('{name}', src); + }).join(' '); - const tr = document.createElement("tr"); - tr.dataset["id"] = name; + const tr = document.createElement('tr'); + tr.dataset['id'] = name; tr.innerHTML = `` + `${online} / info` + @@ -142,9 +142,9 @@ }); } - const url = new URL("api", location.href); + const url = new URL('api', location.href); fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => { - const info = document.querySelector(".info"); + const info = document.querySelector('.info'); info.innerText = `Version: ${data.version}, Config: ${data.config_path}`; }); diff --git a/www/links.html b/www/links.html index fab598a6..8bf04c16 100644 --- a/www/links.html +++ b/www/links.html @@ -1,5 +1,5 @@ - + go2rtc - links @@ -42,21 +42,21 @@
@@ -92,12 +92,12 @@
@@ -119,62 +119,62 @@
diff --git a/www/stream.html b/www/stream.html index bbcedf1c..9886f1c3 100644 --- a/www/stream.html +++ b/www/stream.html @@ -30,9 +30,9 @@ const params = new URLSearchParams(location.search); // support multiple streams and multiple modes - const streams = params.getAll("src"); - const modes = params.getAll("mode"); - if (modes.length === 0) modes.push(""); + const streams = params.getAll('src'); + const modes = params.getAll('mode'); + if (modes.length === 0) modes.push(''); while (modes.length > streams.length) { streams.push(streams[0]); @@ -42,19 +42,19 @@ } if (streams.length > 1) { - document.body.className = "flex"; + document.body.className = 'flex'; } - const background = params.get("background") !== "false"; - const width = "1 0 " + (params.get("width") || "320px"); + const background = params.get('background') !== 'false'; + const width = '1 0 ' + (params.get('width') || '320px'); for (let i = 0; i < streams.length; i++) { /** @type {VideoStream} */ - const video = document.createElement("video-stream"); + const video = document.createElement('video-stream'); video.background = background; video.mode = modes[i] || video.mode; video.style.flex = width; - video.src = new URL("api/ws?src=" + encodeURIComponent(streams[i]), location.href); + video.src = new URL('api/ws?src=' + encodeURIComponent(streams[i]), location.href); document.body.appendChild(video); } diff --git a/www/video-rtc.js b/www/video-rtc.js index 25109eac..9bbbfd65 100644 --- a/www/video-rtc.js +++ b/www/video-rtc.js @@ -21,21 +21,22 @@ export class VideoRTC extends HTMLElement { this.RECONNECT_TIMEOUT = 30000; this.CODECS = [ - "avc1.640029", // H.264 high 4.1 (Chromecast 1st and 2nd Gen) - "avc1.64002A", // H.264 high 4.2 (Chromecast 3rd Gen) - "avc1.640033", // H.264 high 5.1 (Chromecast with Google TV) - "hvc1.1.6.L153.B0", // H.265 main 5.1 (Chromecast Ultra) - "mp4a.40.2", // AAC LC - "mp4a.40.5", // AAC HE - "flac", // FLAC (PCM compatible) - "opus", // OPUS Chrome, Firefox + 'avc1.640029', // H.264 high 4.1 (Chromecast 1st and 2nd Gen) + 'avc1.64002A', // H.264 high 4.2 (Chromecast 3rd Gen) + 'avc1.640033', // H.264 high 5.1 (Chromecast with Google TV) + 'hvc1.1.6.L153.B0', // H.265 main 5.1 (Chromecast Ultra) + 'mp4a.40.2', // AAC LC + 'mp4a.40.5', // AAC HE + 'null', // for detecting liars (old iOS 12) + 'flac', // FLAC (PCM compatible) + 'opus', // OPUS Chrome, Firefox ]; /** * [config] Supported modes (webrtc, webrtc/tcp, mse, hls, mp4, mjpeg). * @type {string} */ - this.mode = "webrtc,mse,hls,mjpeg"; + this.mode = 'webrtc,mse,hls,mjpeg'; /** * [config] Run stream when not displayed on the screen. Default `false`. @@ -92,7 +93,7 @@ export class VideoRTC extends HTMLElement { /** * @type {string|URL} */ - this.wsURL = ""; + this.wsURL = ''; /** * @type {RTCPeerConnection} @@ -107,7 +108,7 @@ export class VideoRTC extends HTMLElement { /** * @type {string} */ - this.mseCodecs = ""; + this.mseCodecs = ''; /** * [internal] Disconnect TimeoutID. @@ -139,11 +140,11 @@ export class VideoRTC extends HTMLElement { * @param {string|URL} value */ set src(value) { - if (typeof value !== "string") value = value.toString(); - if (value.startsWith("http")) { - value = "ws" + value.substring(4); - } else if (value.startsWith("/")) { - value = "ws" + location.origin.substring(4) + value; + if (typeof value !== 'string') value = value.toString(); + if (value.startsWith('http')) { + value = 'ws' + value.substring(4); + } else if (value.startsWith('/')) { + value = 'ws' + location.origin.substring(4) + value; } this.wsURL = value; @@ -173,7 +174,7 @@ export class VideoRTC extends HTMLElement { } codecs(type) { - const test = type === "mse" + const test = type === 'mse' ? codec => MediaSource.isTypeSupported(`video/mp4; codecs="${codec}"`) : codec => this.video.canPlayType(`video/mp4; codecs="${codec}"`); return this.CODECS.filter(test).join(); @@ -227,30 +228,30 @@ export class VideoRTC extends HTMLElement { * Creates child DOM elements. Called automatically once on `connectedCallback`. */ oninit() { - this.video = document.createElement("video"); + this.video = document.createElement('video'); this.video.controls = true; this.video.playsInline = true; - this.video.preload = "auto"; + this.video.preload = 'auto'; - this.video.style.display = "block"; // fix bottom margin 4px - this.video.style.width = "100%"; - this.video.style.height = "100%" + this.video.style.display = 'block'; // fix bottom margin 4px + this.video.style.width = '100%'; + this.video.style.height = '100%'; this.appendChild(this.video); if (this.background) return; - if ("hidden" in document && this.visibilityCheck) { - document.addEventListener("visibilitychange", () => { + if ('hidden' in document && this.visibilityCheck) { + document.addEventListener('visibilitychange', () => { if (document.hidden) { this.disconnectedCallback(); } else if (this.isConnected) { this.connectedCallback(); } - }) + }); } - if ("IntersectionObserver" in window && this.visibilityThreshold) { + if ('IntersectionObserver' in window && this.visibilityThreshold) { const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (!entry.isIntersecting) { @@ -277,9 +278,9 @@ export class VideoRTC extends HTMLElement { this.connectTS = Date.now(); this.ws = new WebSocket(this.wsURL); - this.ws.binaryType = "arraybuffer"; - this.ws.addEventListener("open", ev => this.onopen(ev)); - this.ws.addEventListener("close", ev => this.onclose(ev)); + this.ws.binaryType = 'arraybuffer'; + this.ws.addEventListener('open', () => this.onopen()); + this.ws.addEventListener('close', () => this.onclose()); return true; } @@ -305,8 +306,8 @@ export class VideoRTC extends HTMLElement { // CONNECTING => OPEN this.wsState = WebSocket.OPEN; - this.ws.addEventListener("message", ev => { - if (typeof ev.data === "string") { + this.ws.addEventListener('message', ev => { + if (typeof ev.data === 'string') { const msg = JSON.parse(ev.data); for (const mode in this.onmessage) { this.onmessage[mode](msg); @@ -321,30 +322,30 @@ export class VideoRTC extends HTMLElement { const modes = []; - if (this.mode.indexOf("mse") >= 0 && "MediaSource" in window) { // iPhone - modes.push("mse"); + if (this.mode.indexOf('mse') >= 0 && 'MediaSource' in window) { // iPhone + modes.push('mse'); this.onmse(); - } else if (this.mode.indexOf("hls") >= 0 && this.video.canPlayType("application/vnd.apple.mpegurl")) { - modes.push("hls"); + } else if (this.mode.indexOf('hls') >= 0 && this.video.canPlayType('application/vnd.apple.mpegurl')) { + modes.push('hls'); this.onhls(); - } else if (this.mode.indexOf("mp4") >= 0) { - modes.push("mp4"); + } else if (this.mode.indexOf('mp4') >= 0) { + modes.push('mp4'); this.onmp4(); } - if (this.mode.indexOf("webrtc") >= 0 && "RTCPeerConnection" in window) { // macOS Desktop app - modes.push("webrtc"); + if (this.mode.indexOf('webrtc') >= 0 && 'RTCPeerConnection' in window) { // macOS Desktop app + modes.push('webrtc'); this.onwebrtc(); } - if (this.mode.indexOf("mjpeg") >= 0) { + if (this.mode.indexOf('mjpeg') >= 0) { if (modes.length) { - this.onmessage["mjpeg"] = msg => { - if (msg.type !== "error" || msg.value.indexOf(modes[0]) !== 0) return; + this.onmessage['mjpeg'] = msg => { + if (msg.type !== 'error' || msg.value.indexOf(modes[0]) !== 0) return; this.onmjpeg(); - } + }; } else { - modes.push("mjpeg"); + modes.push('mjpeg'); this.onmjpeg(); } } @@ -375,25 +376,25 @@ export class VideoRTC extends HTMLElement { onmse() { const ms = new MediaSource(); - ms.addEventListener("sourceopen", () => { + ms.addEventListener('sourceopen', () => { URL.revokeObjectURL(this.video.src); - this.send({type: "mse", value: this.codecs("mse")}); + this.send({type: 'mse', value: this.codecs('mse')}); }, {once: true}); this.video.src = URL.createObjectURL(ms); this.video.srcObject = null; this.play(); - this.mseCodecs = ""; + this.mseCodecs = ''; - this.onmessage["mse"] = msg => { - if (msg.type !== "mse") return; + this.onmessage['mse'] = msg => { + if (msg.type !== 'mse') return; this.mseCodecs = msg.value; const sb = ms.addSourceBuffer(msg.value); - sb.mode = "segments"; // segments or sequence - sb.addEventListener("updateend", () => { + sb.mode = 'segments'; // segments or sequence + sb.addEventListener('updateend', () => { if (sb.updating) return; try { @@ -431,25 +432,25 @@ export class VideoRTC extends HTMLElement { // console.debug(e); } } - } - } + }; + }; } onwebrtc() { const pc = new RTCPeerConnection(this.pcConfig); /** @type {HTMLVideoElement} */ - const video2 = document.createElement("video"); - video2.addEventListener("loadeddata", ev => this.onpcvideo(ev), {once: true}); + const video2 = document.createElement('video'); + video2.addEventListener('loadeddata', ev => this.onpcvideo(ev), {once: true}); - pc.addEventListener("icecandidate", ev => { - if (ev.candidate && this.mode.indexOf("webrtc/tcp") >= 0 && ev.candidate.protocol === "udp") return; + pc.addEventListener('icecandidate', ev => { + if (ev.candidate && this.mode.indexOf('webrtc/tcp') >= 0 && ev.candidate.protocol === 'udp') return; - const candidate = ev.candidate ? ev.candidate.toJSON().candidate : ""; - this.send({type: "webrtc/candidate", value: candidate}); + const candidate = ev.candidate ? ev.candidate.toJSON().candidate : ''; + this.send({type: 'webrtc/candidate', value: candidate}); }); - pc.addEventListener("track", ev => { + pc.addEventListener('track', ev => { // when stream already init if (video2.srcObject !== null) return; @@ -462,8 +463,8 @@ export class VideoRTC extends HTMLElement { video2.srcObject = ev.streams[0]; }); - pc.addEventListener("connectionstatechange", () => { - if (pc.connectionState === "failed" || pc.connectionState === "disconnected") { + pc.addEventListener('connectionstatechange', () => { + if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') { pc.close(); // stop next events this.pcState = WebSocket.CLOSED; @@ -473,29 +474,33 @@ export class VideoRTC extends HTMLElement { } }); - this.onmessage["webrtc"] = msg => { + this.onmessage['webrtc'] = msg => { switch (msg.type) { - case "webrtc/candidate": - if (this.mode.indexOf("webrtc/tcp") >= 0 && msg.value.indexOf(" udp ") > 0) return; + case 'webrtc/candidate': + if (this.mode.indexOf('webrtc/tcp') >= 0 && msg.value.indexOf(' udp ') > 0) return; - pc.addIceCandidate({candidate: msg.value, sdpMid: "0"}).catch(() => console.debug); + pc.addIceCandidate({candidate: msg.value, sdpMid: '0'}).catch(er => { + console.warn(er); + }); break; - case "webrtc/answer": - pc.setRemoteDescription({type: "answer", sdp: msg.value}).catch(() => console.debug); + case 'webrtc/answer': + pc.setRemoteDescription({type: 'answer', sdp: msg.value}).catch(er => { + console.warn(er); + }); break; - case "error": - if (msg.value.indexOf("webrtc/offer") < 0) return; + case 'error': + if (msg.value.indexOf('webrtc/offer') < 0) return; pc.close(); } }; // Safari doesn't support "offerToReceiveVideo" - pc.addTransceiver("video", {direction: "recvonly"}); - pc.addTransceiver("audio", {direction: "recvonly"}); + pc.addTransceiver('video', {direction: 'recvonly'}); + pc.addTransceiver('audio', {direction: 'recvonly'}); pc.createOffer().then(offer => { pc.setLocalDescription(offer).then(() => { - this.send({type: "webrtc/offer", value: offer.sdp}); + this.send({type: 'webrtc/offer', value: offer.sdp}); }); }); @@ -514,7 +519,7 @@ export class VideoRTC extends HTMLElement { const state = this.pc.connectionState; // Firefox doesn't support pc.connectionState - if (state === "connected" || state === "connecting" || !state) { + if (state === 'connected' || state === 'connecting' || !state) { // Video+Audio > Video, H265 > H264, Video > Audio, WebRTC > MSE let rtcPriority = 0, msePriority = 0; @@ -523,9 +528,9 @@ export class VideoRTC extends HTMLElement { if (ms.getVideoTracks().length > 0) rtcPriority += 0x220; if (ms.getAudioTracks().length > 0) rtcPriority += 0x102; - if (this.mseCodecs.indexOf("hvc1.") >= 0) msePriority += 0x230; - if (this.mseCodecs.indexOf("avc1.") >= 0) msePriority += 0x210; - if (this.mseCodecs.indexOf("mp4a.") >= 0) msePriority += 0x101; + if (this.mseCodecs.indexOf('hvc1.') >= 0) msePriority += 0x230; + if (this.mseCodecs.indexOf('avc1.') >= 0) msePriority += 0x210; + if (this.mseCodecs.indexOf('mp4a.') >= 0) msePriority += 0x101; if (rtcPriority >= msePriority) { this.video.srcObject = ms; @@ -549,36 +554,38 @@ export class VideoRTC extends HTMLElement { onmjpeg() { this.ondata = data => { this.video.controls = false; - this.video.poster = "data:image/jpeg;base64," + VideoRTC.btoa(data); + this.video.poster = 'data:image/jpeg;base64,' + VideoRTC.btoa(data); }; - this.send({type: "mjpeg"}); + this.send({type: 'mjpeg'}); } onhls() { - this.onmessage["hls"] = msg => { - const url = "http" + this.wsURL.substring(2, this.wsURL.indexOf("/ws")) + "/hls/"; - const playlist = msg.value.replace("hls/", url); - this.video.src = "data:application/vnd.apple.mpegurl;base64," + btoa(playlist); - this.play(); - } + this.onmessage['hls'] = msg => { + if (msg.type !== 'hls') return; - this.send({type: "hls", value: this.codecs("hls")}); + const url = 'http' + this.wsURL.substring(2, this.wsURL.indexOf('/ws')) + '/hls/'; + const playlist = msg.value.replace('hls/', url); + this.video.src = 'data:application/vnd.apple.mpegurl;base64,' + btoa(playlist); + this.play(); + }; + + this.send({type: 'hls', value: this.codecs('hls')}); } onmp4() { /** @type {HTMLCanvasElement} **/ - const canvas = document.createElement("canvas"); + const canvas = document.createElement('canvas'); /** @type {CanvasRenderingContext2D} */ let context; /** @type {HTMLVideoElement} */ - const video2 = document.createElement("video"); + const video2 = document.createElement('video'); video2.autoplay = true; video2.playsInline = true; video2.muted = true; - video2.addEventListener("loadeddata", ev => { + video2.addEventListener('loadeddata', () => { if (!context) { canvas.width = video2.videoWidth; canvas.height = video2.videoHeight; @@ -588,20 +595,20 @@ export class VideoRTC extends HTMLElement { context.drawImage(video2, 0, 0, canvas.width, canvas.height); this.video.controls = false; - this.video.poster = canvas.toDataURL("image/jpeg"); + this.video.poster = canvas.toDataURL('image/jpeg'); }); this.ondata = data => { - video2.src = "data:video/mp4;base64," + VideoRTC.btoa(data); + video2.src = 'data:video/mp4;base64,' + VideoRTC.btoa(data); }; - this.send({type: "mp4", value: this.codecs("mp4")}); + this.send({type: 'mp4', value: this.codecs('mp4')}); } static btoa(buffer) { const bytes = new Uint8Array(buffer); const len = bytes.byteLength; - let binary = ""; + let binary = ''; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } diff --git a/www/video-stream.js b/www/video-stream.js index 9347a6c2..6cafd3ce 100644 --- a/www/video-stream.js +++ b/www/video-stream.js @@ -1,23 +1,23 @@ -import {VideoRTC} from "./video-rtc.js"; +import {VideoRTC} from './video-rtc.js'; class VideoStream extends VideoRTC { set divMode(value) { - this.querySelector(".mode").innerText = value; - this.querySelector(".status").innerText = ""; + this.querySelector('.mode').innerText = value; + this.querySelector('.status').innerText = ''; } set divError(value) { - const state = this.querySelector(".mode").innerText; - if (state !== "loading") return; - this.querySelector(".mode").innerText = "error"; - this.querySelector(".status").innerText = value; + const state = this.querySelector('.mode').innerText; + if (state !== 'loading') return; + this.querySelector('.mode').innerText = 'error'; + this.querySelector('.status').innerText = value; } /** * Custom GUI */ oninit() { - console.debug("stream.oninit"); + console.debug('stream.oninit'); super.oninit(); this.innerHTML = ` @@ -43,57 +43,57 @@ class VideoStream extends VideoRTC { `; - const info = this.querySelector(".info") + const info = this.querySelector('.info'); this.insertBefore(this.video, info); } onconnect() { - console.debug("stream.onconnect"); + console.debug('stream.onconnect'); const result = super.onconnect(); - if (result) this.divMode = "loading"; + if (result) this.divMode = 'loading'; return result; } ondisconnect() { - console.debug("stream.ondisconnect"); + console.debug('stream.ondisconnect'); super.ondisconnect(); } onopen() { - console.debug("stream.onopen"); + console.debug('stream.onopen'); const result = super.onopen(); - this.onmessage["stream"] = msg => { - console.debug("stream.onmessge", msg); + this.onmessage['stream'] = msg => { + console.debug('stream.onmessge', msg); switch (msg.type) { - case "error": + case 'error': this.divError = msg.value; break; - case "mse": - case "hls": - case "mp4": - case "mjpeg": + case 'mse': + case 'hls': + case 'mp4': + case 'mjpeg': this.divMode = msg.type.toUpperCase(); break; } - } + }; return result; } onclose() { - console.debug("stream.onclose"); + console.debug('stream.onclose'); return super.onclose(); } onpcvideo(ev) { - console.debug("stream.onpcvideo"); + console.debug('stream.onpcvideo'); super.onpcvideo(ev); if (this.pcState !== WebSocket.CLOSED) { - this.divMode = "RTC"; + this.divMode = 'RTC'; } } } -customElements.define("video-stream", VideoStream); +customElements.define('video-stream', VideoStream); diff --git a/www/webrtc-sync.html b/www/webrtc-sync.html index 463a0f09..c7fe49bd 100644 --- a/www/webrtc-sync.html +++ b/www/webrtc-sync.html @@ -22,45 +22,45 @@ async function PeerConnection(media) { const pc = new RTCPeerConnection({ iceServers: [{urls: 'stun:stun.l.google.com:19302'}] - }) + }); document.getElementById('video').srcObject = new MediaStream([ pc.addTransceiver('audio', {direction: 'sendrecv'}).receiver.track, pc.addTransceiver('video', {direction: 'sendrecv'}).receiver.track, - ]) + ]); const tracks = await navigator.mediaDevices.getUserMedia({ video: media.indexOf('camera') >= 0, audio: media.indexOf('microphone') >= 0, - }) + }); tracks.getTracks().forEach(track => { - pc.addTrack(track) - }) + pc.addTrack(track); + }); - return pc + return pc; } function getCompleteOffer(pc, timeout) { return new Promise((resolve, reject) => { pc.addEventListener('icegatheringstatechange', () => { - if (pc.iceGatheringState === 'complete') resolve(pc.localDescription.sdp) - }) + if (pc.iceGatheringState === 'complete') resolve(pc.localDescription.sdp); + }); - pc.createOffer().then(offer => pc.setLocalDescription(offer)) + pc.createOffer().then(offer => pc.setLocalDescription(offer)); - setTimeout(() => resolve(pc.localDescription.sdp), timeout || 3000) - }) + setTimeout(() => resolve(pc.localDescription.sdp), timeout || 3000); + }); } async function connect() { - const media = new URLSearchParams(location.search).get('media') - const pc = await PeerConnection(media) - const url = new URL('api/webrtc' + location.search, location.href) - const r = await fetch(url, {method: 'POST', body: await getCompleteOffer(pc)}) - await pc.setRemoteDescription({type: 'answer', sdp: await r.text()}) + const media = new URLSearchParams(location.search).get('media'); + const pc = await PeerConnection(media); + const url = new URL('api/webrtc' + location.search, location.href); + const r = await fetch(url, {method: 'POST', body: await getCompleteOffer(pc)}); + await pc.setRemoteDescription({type: 'answer', sdp: await r.text()}); } - connect() + connect(); \ No newline at end of file diff --git a/www/webrtc.html b/www/webrtc.html index 2eda9541..e1b5313f 100644 --- a/www/webrtc.html +++ b/www/webrtc.html @@ -22,86 +22,86 @@ async function PeerConnection(media) { const pc = new RTCPeerConnection({ iceServers: [{urls: 'stun:stun.l.google.com:19302'}] - }) + }); - const localTracks = [] + const localTracks = []; if (/camera|microphone/.test(media)) { const tracks = await getMediaTracks('user', { video: media.indexOf('camera') >= 0, audio: media.indexOf('microphone') >= 0, - }) + }); tracks.forEach(track => { - pc.addTransceiver(track, {direction: 'sendonly'}) - if (track.kind === 'video') localTracks.push(track) - }) + pc.addTransceiver(track, {direction: 'sendonly'}); + if (track.kind === 'video') localTracks.push(track); + }); } if (media.indexOf('display') >= 0) { const tracks = await getMediaTracks('display', { video: true, audio: media.indexOf('speaker') >= 0, - }) + }); tracks.forEach(track => { - pc.addTransceiver(track, {direction: 'sendonly'}) - if (track.kind === 'video') localTracks.push(track) - }) + pc.addTransceiver(track, {direction: 'sendonly'}); + if (track.kind === 'video') localTracks.push(track); + }); } if (/video|audio/.test(media)) { const tracks = ['video', 'audio'] .filter(kind => media.indexOf(kind) >= 0) - .map(kind => pc.addTransceiver(kind, {direction: 'recvonly'}).receiver.track) - localTracks.push(...tracks) + .map(kind => pc.addTransceiver(kind, {direction: 'recvonly'}).receiver.track); + localTracks.push(...tracks); } - document.getElementById('video').srcObject = new MediaStream(localTracks) + document.getElementById('video').srcObject = new MediaStream(localTracks); - return pc + return pc; } async function getMediaTracks(media, constraints) { try { const stream = media === 'user' ? await navigator.mediaDevices.getUserMedia(constraints) - : await navigator.mediaDevices.getDisplayMedia(constraints) - return stream.getTracks() + : await navigator.mediaDevices.getDisplayMedia(constraints); + return stream.getTracks(); } catch (e) { - console.warn(e) - return [] + console.warn(e); + return []; } } async function connect(media) { - const pc = await PeerConnection(media) - const url = new URL('api/ws' + location.search, location.href) - const ws = new WebSocket('ws' + url.toString().substring(4)) + const pc = await PeerConnection(media); + const url = new URL('api/ws' + location.search, location.href); + const ws = new WebSocket('ws' + url.toString().substring(4)); ws.addEventListener('open', () => { pc.addEventListener('icecandidate', ev => { - if (!ev.candidate) return - const msg = {type: 'webrtc/candidate', value: ev.candidate.candidate} - ws.send(JSON.stringify(msg)) - }) + if (!ev.candidate) return; + const msg = {type: 'webrtc/candidate', value: ev.candidate.candidate}; + ws.send(JSON.stringify(msg)); + }); pc.createOffer().then(offer => pc.setLocalDescription(offer)).then(() => { - const msg = {type: 'webrtc/offer', value: pc.localDescription.sdp} - ws.send(JSON.stringify(msg)) - }) - }) + const msg = {type: 'webrtc/offer', value: pc.localDescription.sdp}; + ws.send(JSON.stringify(msg)); + }); + }); ws.addEventListener('message', ev => { - const msg = JSON.parse(ev.data) + const msg = JSON.parse(ev.data); if (msg.type === 'webrtc/candidate') { - pc.addIceCandidate({candidate: msg.value, sdpMid: '0'}) + pc.addIceCandidate({candidate: msg.value, sdpMid: '0'}); } else if (msg.type === 'webrtc/answer') { - pc.setRemoteDescription({type: 'answer', sdp: msg.value}) + pc.setRemoteDescription({type: 'answer', sdp: msg.value}); } - }) + }); } - const media = new URLSearchParams(location.search).get('media') - connect(media || 'video+audio') + const media = new URLSearchParams(location.search).get('media'); + connect(media || 'video+audio'); \ No newline at end of file