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