Update WebUI design

This commit is contained in:
Alex X
2025-11-15 21:08:00 +03:00
parent e2c7d06730
commit 1fe602679e
7 changed files with 700 additions and 792 deletions
+162 -165
View File
@@ -1,41 +1,37 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>go2rtc - Add Stream</title> <title>go2rtc - Add Stream</title>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style> <style>
body { main > button {
margin: 0; background-color: #444;
padding: 0; color: white;
display: flex; cursor: pointer;
flex-direction: column; padding: 14px;
}
html, body {
width: 100%; width: 100%;
height: 100%; border: none;
text-align: left;
font-size: 16px;
font-weight: bold;
} }
.module { main > div {
display: none; display: none;
padding: 10px; gap: 10px;
} }
table tbody td {
font-size: 13px;
}
</style> </style>
</head> </head>
<body> <body>
<script src="main.js"></script> <script src="main.js"></script>
<script> <script>
function drawTable(table, data) { function drawTable(table, data) {
const cols = ['id', 'name', 'info', 'url', 'location']; const cols = ['id', 'name', 'info', 'url', 'location'];
const th = (row) => cols.reduce((html, k) => k in row ? `${html}<th>${k}</th>` : html, '<tr>') + '</tr>'; const th = (row) => cols.reduce((html, k) => k in row ? `${html}<th>${k}</th>` : html, '<tr>') + '</tr>';
const td = (row) => cols.reduce((html, k) => k in row ? `${html}<td style="word-break: break-word;white-space: normal;">${row[k]}</td>` : html, '<tr>') + '</tr>'; const td = (row) => cols.reduce((html, k) => k in row ? `${html}<td style="word-break: break-word; white-space: normal;">${row[k]}</td>` : html, '<tr>') + '</tr>';
const thead = th(data.sources[0]); const thead = th(data.sources[0]);
const tbody = data.sources.reduce((html, source) => `${html}${td(source)}`, ''); const tbody = data.sources.reduce((html, source) => `${html}${td(source)}`, '');
@@ -57,18 +53,18 @@
} }
</script> </script>
<main>
<button id="stream">Temporary stream</button> <button id="stream">Temporary stream</button>
<div class="module"> <div>
<form id="stream-form" style="padding: 10px"> <form id="stream-form">
<input type="text" name="name" placeholder="name"> <input type="text" name="name" placeholder="name">
<input type="text" name="src" placeholder="url"> <input type="text" name="src" placeholder="url" required size="30">
<input type="submit" value="add"> <button type="submit">add</button>
</form> </form>
</div> </div>
<script> <script>
document.getElementById('stream').addEventListener('click', async ev => { document.getElementById('stream').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
}); });
document.getElementById('stream-form').addEventListener('submit', async ev => { document.getElementById('stream-form').addEventListener('submit', async ev => {
@@ -81,36 +77,36 @@
const r = await fetch(url, {method: 'PUT'}); const r = await fetch(url, {method: 'PUT'});
alert(r.ok ? 'OK' : 'ERROR: ' + await r.text()); alert(r.ok ? 'OK' : 'ERROR: ' + await r.text());
}); });
</script> </script>
<button id="alsa">ALSA (Linux audio)</button> <button id="alsa">ALSA (Linux audio)</button>
<div class="module"> <div>
<table id="alsa-table"></table> <table id="alsa-table"></table>
</div> </div>
<script> <script>
document.getElementById('alsa').addEventListener('click', async ev => { document.getElementById('alsa').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('alsa-table', 'api/alsa'); await getSources('alsa-table', 'api/alsa');
}); });
</script> </script>
<button id="homekit">Apple HomeKit</button> <button id="homekit">Apple HomeKit</button>
<div class="module"> <div>
<form id="homekit-pair" style="margin-bottom: 10px"> <form id="homekit-pair">
<input type="text" name="id" placeholder="stream id" size="20"> <input type="text" name="id" placeholder="stream id" required>
<input type="text" name="src" placeholder="src" size="40"> <input type="text" name="src" placeholder="src" required size="30">
<input type="text" name="pin" placeholder="pin" size="10"> <input type="text" name="pin" placeholder="pin" required size="10">
<input type="submit" value="Pair"> <button type="submit">pair</button>
</form> </form>
<form id="homekit-unpair" style="margin-bottom: 10px"> <form id="homekit-unpair">
<input type="text" name="id" placeholder="stream id" size="20"> <input type="text" name="id" placeholder="stream id" required>
<input type="submit" value="Unpair"> <button type="submit">unpair</button>
</form> </form>
<table id="homekit-table"></table> <table id="homekit-table"></table>
</div> </div>
<script> <script>
async function reloadHomeKit() { async function reloadHomeKit() {
await getSources('homekit-table', 'api/discovery/homekit'); await getSources('homekit-table', 'api/discovery/homekit');
@@ -122,12 +118,12 @@
} else if (i > 0 && row.children[3].innerText) { } else if (i > 0 && row.children[3].innerText) {
commands += '<a href="#">unpair</a>'; commands += '<a href="#">unpair</a>';
} }
row.innerHTML += `<td>${commands}</td>`; row.innerHTML += i > 0 ? `<td>${commands}</td>` : '<th>commands</th>';
}); });
} }
document.getElementById('homekit').addEventListener('click', async ev => { document.getElementById('homekit').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await reloadHomeKit(); await reloadHomeKit();
}); });
@@ -163,59 +159,59 @@
await reloadHomeKit(); await reloadHomeKit();
}); });
</script> </script>
<button id="dvrip">DVRIP</button> <button id="dvrip">DVRIP</button>
<div class="module"> <div>
<table id="dvrip-table"></table> <table id="dvrip-table"></table>
</div> </div>
<script> <script>
document.getElementById('dvrip').addEventListener('click', async ev => { document.getElementById('dvrip').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('dvrip-table', 'api/dvrip'); await getSources('dvrip-table', 'api/dvrip');
}); });
</script> </script>
<button id="devices">FFmpeg Devices (USB)</button> <button id="devices">FFmpeg Devices (USB)</button>
<div class="module"> <div>
<table id="devices-table"></table> <table id="devices-table"></table>
</div> </div>
<script> <script>
document.getElementById('devices').addEventListener('click', async ev => { document.getElementById('devices').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('devices-table', 'api/ffmpeg/devices'); await getSources('devices-table', 'api/ffmpeg/devices');
}); });
</script> </script>
<button id="hardware">FFmpeg Hardware</button> <button id="hardware">FFmpeg Hardware</button>
<div class="module"> <div>
<table id="hardware-table"></table> <table id="hardware-table"></table>
</div> </div>
<script> <script>
document.getElementById('hardware').addEventListener('click', async ev => { document.getElementById('hardware').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('hardware-table', 'api/ffmpeg/hardware'); await getSources('hardware-table', 'api/ffmpeg/hardware');
}); });
</script> </script>
<button id="nest">Google Nest</button> <button id="nest">Google Nest</button>
<div class="module"> <div>
<form id="nest-form" style="margin-bottom: 10px"> <form id="nest-form">
<input type="text" name="client_id" placeholder="client_id"> <input type="text" name="client_id" placeholder="client_id" required>
<input type="text" name="client_secret" placeholder="client_secret"> <input type="text" name="client_secret" placeholder="client_secret" required>
<input type="text" name="refresh_token" placeholder="refresh_token"> <input type="text" name="refresh_token" placeholder="refresh_token" required>
<input type="text" name="project_id" placeholder="project_id"> <input type="text" name="project_id" placeholder="project_id" required>
<input type="submit" value="Login"> <button type="submit">login</button>
</form> </form>
<table id="nest-table"></table> <table id="nest-table"></table>
</div> </div>
<script> <script>
document.getElementById('nest').addEventListener('click', async ev => { document.getElementById('nest').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
}); });
document.getElementById('nest-form').addEventListener('submit', async ev => { document.getElementById('nest-form').addEventListener('submit', async ev => {
@@ -227,28 +223,77 @@
const r = await fetch(url, {cache: 'no-cache'}); const r = await fetch(url, {cache: 'no-cache'});
await getSources('nest-table', r); await getSources('nest-table', r);
}); });
</script> </script>
<button id="ring">Ring</button> <button id="gopro">GoPro</button>
<div class="module"> <div>
<form id="ring-credentials-form" style="margin-bottom: 10px"> <table id="gopro-table"></table>
<input type="email" name="email" placeholder="email"> </div>
<input type="password" name="password" placeholder="password"> <script>
document.getElementById('gopro').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'grid';
await getSources('gopro-table', 'api/gopro');
});
</script>
<button id="hass">Home Assistant</button>
<div>
<table id="hass-table"></table>
</div>
<script>
document.getElementById('hass').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'grid';
await getSources('hass-table', 'api/hass');
});
</script>
<button id="onvif">ONVIF</button>
<div>
<form id="onvif-form">
<input type="text" name="src" placeholder="onvif://user:pass@192.168.1.123:80" required size="30">
<button type="submit">test</button>
</form>
<table id="onvif-table"></table>
</div>
<script>
document.getElementById('onvif').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'grid';
await getSources('onvif-table', 'api/onvif');
});
document.getElementById('onvif-form').addEventListener('submit', async ev => {
ev.preventDefault();
const url = new URL('api/onvif', location.href);
url.searchParams.set('src', ev.target.elements['src'].value);
await getSources('onvif-table', url.toString());
});
</script>
<button id="ring">Ring</button>
<div>
<form id="ring-credentials-form">
<input type="email" name="email" placeholder="email" required>
<input type="password" name="password" placeholder="password" required>
<div id="tfa-field" style="display: none"> <div id="tfa-field" style="display: none">
<input type="text" name="code" placeholder="2FA code"> <input type="text" name="code" placeholder="2FA code">
<div id="tfa-prompt"></div> <div id="tfa-prompt"></div>
</div> </div>
<input type="submit" value="Login"> <button type="submit">login</button>
</form> </form>
<form id="ring-token-form" style="margin-bottom: 10px"> <form id="ring-token-form">
<input type="text" name="refresh_token" placeholder="refresh_token"> <input type="text" name="refresh_token" placeholder="refresh_token" required>
<input type="submit" value="Login"> <button type="submit">login</button>
</form> </form>
<table id="ring-table"></table> <table id="ring-table"></table>
</div> </div>
<script> <script>
document.getElementById('ring').addEventListener('click', async ev => { document.getElementById('ring').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
}); });
async function handleRingAuth(ev) { async function handleRingAuth(ev) {
@@ -282,70 +327,22 @@
document.getElementById('ring-credentials-form').addEventListener('submit', handleRingAuth); document.getElementById('ring-credentials-form').addEventListener('submit', handleRingAuth);
document.getElementById('ring-token-form').addEventListener('submit', handleRingAuth); document.getElementById('ring-token-form').addEventListener('submit', handleRingAuth);
</script> </script>
<button id="gopro">GoPro</button>
<div class="module">
<table id="gopro-table"></table>
</div>
<script>
document.getElementById('gopro').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block';
await getSources('gopro-table', 'api/gopro');
});
</script>
<button id="hass">Home Assistant</button> <button id="roborock">Roborock</button>
<div class="module"> <div>
<table id="hass-table"></table> <form id="roborock-form">
</div> <input type="text" name="username" placeholder="username" required>
<script> <input type="password" name="password" placeholder="password" required>
document.getElementById('hass').addEventListener('click', async ev => { <button type="submit">login</button>
ev.target.nextElementSibling.style.display = 'block';
await getSources('hass-table', 'api/hass');
});
</script>
<button id="onvif">ONVIF</button>
<div class="module">
<form id="onvif-form" style="padding: 10px">
<input type="text" name="src" placeholder="onvif://user:pass@192.168.1.123:80" size="50">
<input type="submit" value="test">
</form>
<table id="onvif-table"></table>
</div>
<script>
document.getElementById('onvif').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block';
await getSources('onvif-table', 'api/onvif');
});
document.getElementById('onvif-form').addEventListener('submit', async ev => {
ev.preventDefault();
const url = new URL('api/onvif', location.href);
url.searchParams.set('src', ev.target.elements['src'].value);
await getSources('onvif-table', url.toString());
});
</script>
<button id="roborock">Roborock</button>
<div class="module">
<form id="roborock-form" style="margin-bottom: 10px">
<input type="text" name="username" placeholder="username">
<input type="password" name="password" placeholder="password">
<input type="submit" value="Login">
</form> </form>
<table id="roborock-table"> <table id="roborock-table">
</table> </table>
</div> </div>
<script> <script>
document.getElementById('roborock').addEventListener('click', async ev => { document.getElementById('roborock').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('roborock-table', 'api/roborock'); await getSources('roborock-table', 'api/roborock');
}); });
@@ -354,32 +351,32 @@
const r = await fetch('api/roborock', {method: 'POST', body: new FormData(ev.target)}); const r = await fetch('api/roborock', {method: 'POST', body: new FormData(ev.target)});
await getSources('roborock-table', r); await getSources('roborock-table', r);
}); });
</script> </script>
<button id="v4l2">V4L2 (Linux video)</button> <button id="v4l2">V4L2 (Linux video)</button>
<div class="module"> <div>
<table id="v4l2-table"></table> <table id="v4l2-table"></table>
</div> </div>
<script> <script>
document.getElementById('v4l2').addEventListener('click', async ev => { document.getElementById('v4l2').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('v4l2-table', 'api/v4l2'); await getSources('v4l2-table', 'api/v4l2');
}); });
</script> </script>
<button id="webtorrent">WebTorrent Shares</button> <button id="webtorrent">WebTorrent Shares</button>
<div class="module"> <div>
<table id="webtorrent-table"></table> <table id="webtorrent-table"></table>
</div> </div>
<script> <script>
document.getElementById('webtorrent').addEventListener('click', async ev => { document.getElementById('webtorrent').addEventListener('click', async ev => {
ev.target.nextElementSibling.style.display = 'block'; ev.target.nextElementSibling.style.display = 'grid';
await getSources('webtorrent-table', 'api/webtorrent'); await getSources('webtorrent-table', 'api/webtorrent');
}); });
</script> </script>
</main>
</body> </body>
</html> </html>
+17 -21
View File
@@ -1,41 +1,36 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>go2rtc - File Editor</title> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>go2rtc - Config</title>
<script src="https://unpkg.com/ace-builds@1.33.1/src-min/ace.js"></script> <script src="https://unpkg.com/ace-builds@1.33.1/src-min/ace.js"></script>
<style> <style>
body {
font-family: Arial, Helvetica, sans-serif;
background-color: white;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
html, body, #config { html, body, #config {
width: 100%;
height: 100%; height: 100%;
} }
</style> </style>
</head> </head>
<body> <body>
<script src="main.js"></script>
<div>
<button id="save">Save & Restart</button>
</div>
<br>
<div id="config"></div>
<script>
let dump;
<script src="main.js"></script>
<main>
<div>
<button id="save">Save & Restart</button>
</div>
</main>
<div id="config"></div>
<script>
/* global ace */
ace.config.set('basePath', 'https://unpkg.com/ace-builds@1.33.1/src-min/'); ace.config.set('basePath', 'https://unpkg.com/ace-builds@1.33.1/src-min/');
const editor = ace.edit('config', { const editor = ace.edit('config', {
mode: 'ace/mode/yaml', mode: 'ace/mode/yaml',
}); });
let dump;
document.getElementById('save').addEventListener('click', async () => { document.getElementById('save').addEventListener('click', async () => {
let r = await fetch('api/config', {cache: 'no-cache'}); let r = await fetch('api/config', {cache: 'no-cache'});
if (r.ok && dump !== await r.text()) { if (r.ok && dump !== await r.text()) {
@@ -67,5 +62,6 @@
} }
}); });
</script> </script>
</body> </body>
</html> </html>
+23 -34
View File
@@ -1,61 +1,49 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="apple-touch-icon" href="https://alexxit.github.io/go2rtc/icons/apple-touch-icon-180x180.png" sizes="180x180">
<link rel="icon" href="https://alexxit.github.io/go2rtc/icons/favicon.ico">
<link rel="manifest" href="https://alexxit.github.io/go2rtc/manifest.json">
<title>go2rtc</title> <title>go2rtc</title>
<style> <style>
body { .controls {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
table tbody td {
font-size: 13px;
}
label {
display: flex; display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center; align-items: center;
} }
.controls { .info {
display: flex; color: #888;
padding: 5px;
}
.controls > label {
margin-left: 10px;
} }
</style> </style>
</head> </head>
<body> <body>
<script src="main.js"></script> <script src="main.js"></script>
<div class="info"></div>
<div class="controls"> <main>
<div class="controls">
<button>stream</button> <button>stream</button>
modes
<label><input type="checkbox" name="webrtc" checked>webrtc</label> <label><input type="checkbox" name="webrtc" checked>webrtc</label>
<label><input type="checkbox" name="mse" checked>mse</label> <label><input type="checkbox" name="mse" checked>mse</label>
<label><input type="checkbox" name="hls" checked>hls</label> <label><input type="checkbox" name="hls" checked>hls</label>
<label><input type="checkbox" name="mjpeg" checked>mjpeg</label> <label><input type="checkbox" name="mjpeg" checked>mjpeg</label>
</div> </div>
<table> <table>
<thead> <thead>
<tr> <tr>
<th><label><input id="selectall" type="checkbox">Name</label></th> <th><label><input id="selectall" type="checkbox">name</label></th>
<th>Online</th> <th>online</th>
<th>Commands</th> <th>commands</th>
</tr> </tr>
</thead> </thead>
<tbody id="streams"> <tbody id="streams">
</tbody> </tbody>
</table> </table>
<div class="info"></div>
</main>
<script> <script>
const templates = [ const templates = [
'<a href="stream.html?src={name}">stream</a>', '<a href="stream.html?src={name}">stream</a>',
@@ -159,10 +147,11 @@
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 => { 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}`; info.innerText = `version: ${data.version} / config: ${data.config_path}`;
}); });
reload(); reload();
</script> </script>
</body> </body>
</html> </html>
+50 -45
View File
@@ -1,27 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>go2rtc - links</title> <title>go2rtc - links</title>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style> <style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
html, body {
width: 100%;
height: 100%;
}
div {
padding: 10px;
}
div > li { div > li {
list-style-type: none; list-style-type: none;
padding-left: 10px; padding-left: 10px;
@@ -36,12 +19,17 @@
</style> </style>
</head> </head>
<body> <body>
<script src="main.js"></script> <script src="main.js"></script>
<div id="links"></div>
<script> <main>
<div id="links"></div>
<script>
const src = new URLSearchParams(location.search).get('src').replace(/[<">]/g, ''); // sanitize const src = new URLSearchParams(location.search).get('src').replace(/[<">]/g, ''); // sanitize
document.getElementById('links').innerHTML = ` const links = document.getElementById('links');
links.innerHTML = `
<h2>Any codec in source</h2> <h2>Any codec in source</h2>
<li><a href="stream.html?src=${src}">stream.html</a> with auto-select mode / browsers: all / codecs: H264, H265*, MJPEG, JPEG, AAC, PCMU, PCMA, OPUS</li> <li><a href="stream.html?src=${src}">stream.html</a> with auto-select mode / browsers: all / codecs: H264, H265*, MJPEG, JPEG, AAC, PCMU, PCMA, OPUS</li>
<li><a href="api/streams?src=${src}">info.json</a> page with active connections</li> <li><a href="api/streams?src=${src}">info.json</a> page with active connections</li>
@@ -57,7 +45,7 @@
} catch (e) { } catch (e) {
} }
document.getElementById('links').innerHTML += ` links.innerHTML += `
<li><a href="rtsp://${rtsp}/${src}">rtsp</a> with only one video and one audio / codecs: any</li> <li><a href="rtsp://${rtsp}/${src}">rtsp</a> with only one video and one audio / codecs: any</li>
<li><a href="rtsp://${rtsp}/${src}?mp4">rtsp</a> for MP4 recording (Hass or Frigate) / codecs: H264, H265, AAC</li> <li><a href="rtsp://${rtsp}/${src}?mp4">rtsp</a> for MP4 recording (Hass or Frigate) / codecs: H264, H265, AAC</li>
<li><a href="rtsp://${rtsp}/${src}?video=all&audio=all">rtsp</a> with all tracks / codecs: any</li> <li><a href="rtsp://${rtsp}/${src}?video=all&audio=all">rtsp</a> with all tracks / codecs: any</li>
@@ -81,18 +69,25 @@
<li><a href="api/frame.jpeg?src=${src}">frame.jpeg</a> snapshot in JPEG-format / browsers: all / codecs: MJPEG, JPEG</li> <li><a href="api/frame.jpeg?src=${src}">frame.jpeg</a> snapshot in JPEG-format / browsers: all / codecs: MJPEG, JPEG</li>
`; `;
}); });
</script> </script>
<div> <div>
<h2>Play audio</h2> <h2>Play audio</h2>
<label><input type="radio" name="play" value="file" checked>file - play remote (https://example.com/song.mp3) or local (/media/song.mp3) file</label><br> <label><input type="radio" name="play" value="file" checked>
<label><input type="radio" name="play" value="live">live - play remote live stream (radio, etc.)</label><br> file - play remote (https://example.com/song.mp3) or local (/media/song.mp3) file
<label><input type="radio" name="play" value="text">text - play Text To Speech (if your FFmpeg support this)</label><br> </label>
<label><input type="radio" name="play" value="live">
live - play remote live stream (radio, etc.)
</label>
<label><input type="radio" name="play" value="text">
text - play Text To Speech (if your FFmpeg support this)
</label>
<br> <br>
<input id="play-url" type="text" placeholder="path / url / text"> <input id="play-url" type="text" placeholder="path / url / text">
<a id="play-send" href="#">send</a> / cameras with two way audio support <button id="play-send">send</button>
</div> / cameras with two way audio support
<script> </div>
<script>
document.getElementById('play-send').addEventListener('click', ev => { document.getElementById('play-send').addEventListener('click', ev => {
ev.preventDefault(); ev.preventDefault();
// action - file / live / text // action - file / live / text
@@ -102,16 +97,17 @@
url.searchParams.set(action, document.getElementById('play-url').value); url.searchParams.set(action, document.getElementById('play-url').value);
fetch(url, {method: 'POST'}); fetch(url, {method: 'POST'});
}); });
</script> </script>
<div> <div>
<h2>Publish stream</h2> <h2>Publish stream</h2>
<pre>YouTube: rtmps://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx <pre>YouTube: rtmps://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx
Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre> Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre>
<input id="pub-url" type="text" placeholder="url"> <input id="pub-url" type="text" placeholder="url">
<a id="pub-send" href="#">send</a> / Telegram RTMPS server <button id="pub-send">send</button>
</div> / Telegram RTMPS server
<script> </div>
<script>
document.getElementById('pub-send').addEventListener('click', ev => { document.getElementById('pub-send').addEventListener('click', ev => {
ev.preventDefault(); ev.preventDefault();
const url = new URL('api/streams', location.href); const url = new URL('api/streams', location.href);
@@ -119,14 +115,22 @@ Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre>
url.searchParams.set('dst', document.getElementById('pub-url').value); url.searchParams.set('dst', document.getElementById('pub-url').value);
fetch(url, {method: 'POST'}); fetch(url, {method: 'POST'});
}); });
</script> </script>
<div id="webrtc"> <div id="webrtc">
<h2>WebRTC Magic</h2> <h2>WebRTC Magic</h2>
<label><input type="radio" name="webrtc" value="video+audio" checked>video+audio = simple viewer</label><br> <label><input type="radio" name="webrtc" value="video+audio" checked>
<label><input type="radio" name="webrtc" value="video+audio+microphone">video+audio+microphone = two way audio from camera</label><br> video+audio = simple viewer
<label><input type="radio" name="webrtc" value="camera+microphone">camera+microphone = stream from browser</label><br> </label>
<label><input type="radio" name="webrtc" value="display+speaker">display+speaker = broadcast software</label><br> <label><input type="radio" name="webrtc" value="video+audio+microphone">
video+audio+microphone = two way audio from camera
</label>
<label><input type="radio" name="webrtc" value="camera+microphone">
camera+microphone = stream from browser
</label>
<label><input type="radio" name="webrtc" value="display+speaker">
display+speaker = broadcast software
</label>
<br> <br>
<li><a id="local" href="webrtc.html?src=">webrtc.html</a> local WebRTC viewer</li> <li><a id="local" href="webrtc.html?src=">webrtc.html</a> local WebRTC viewer</li>
@@ -137,8 +141,8 @@ Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre>
<a id="sharedel" href="#">delete</a> <a id="sharedel" href="#">delete</a>
external WebRTC viewer external WebRTC viewer
</li> </li>
</div> </div>
<script> <script>
function webrtcLinksUpdate() { function webrtcLinksUpdate() {
const media = document.querySelector('input[name="webrtc"]:checked').value; const media = document.querySelector('input[name="webrtc"]:checked').value;
@@ -221,7 +225,8 @@ Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre>
}); });
webrtcLinksUpdate(); webrtcLinksUpdate();
</script> </script>
</main>
</body> </body>
</html> </html>
+30 -34
View File
@@ -1,59 +1,52 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>go2rtc - Logs</title> <title>go2rtc - Logs</title>
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<style> <style>
body { main > div {
font-family: Arial, Helvetica, sans-serif;
background-color: white;
margin: 0;
padding: 0;
display: flex; display: flex;
flex-direction: column; flex-wrap: wrap;
gap: 10px;
} }
html, body { table tbody {
width: 100%;
height: 100%;
}
table tbody td {
font-size: 13px; font-size: 13px;
vertical-align: top;
}
.info {
color: #0174DF;
}
.debug {
color: #808080;
}
.error {
color: #DF0101;
} }
.trace { .trace {
color: #585858; color: #585858 !important;
}
.debug {
color: #808080 !important;
}
.info {
color: #0174DF !important;
} }
.warn { .warn {
color: #FF9966; color: #FF9966 !important;
}
.error {
color: #DF0101 !important;
} }
</style> </style>
</head> </head>
<body> <body>
<script src="main.js"></script> <script src="main.js"></script>
<div>
<main>
<div>
<button id="clean">Clean</button> <button id="clean">Clean</button>
<button id="update">Auto Update: ON</button> <button id="update">Auto Update: ON</button>
<button id="reverse">Reverse Log Order: OFF</button> <button id="reverse">Reverse Log Order: OFF</button>
</div> </div>
<br> <table>
<table>
<thead> <thead>
<tr> <tr>
<th style="width: 100px">Time</th> <th style="width: 100px">Time</th>
@@ -63,7 +56,9 @@
</thead> </thead>
<tbody id="log"> <tbody id="log">
</tbody> </tbody>
</table> </table>
</main>
<script> <script>
document.getElementById('clean').addEventListener('click', async () => { document.getElementById('clean').addEventListener('click', async () => {
const r = await fetch('api/log', {method: 'DELETE'}); const r = await fetch('api/log', {method: 'DELETE'});
@@ -145,5 +140,6 @@
if (autoUpdateEnabled) reload(); if (autoUpdateEnabled) reload();
}, 5000); }, 5000);
</script> </script>
</body> </body>
</html> </html>
+127 -193
View File
@@ -1,200 +1,134 @@
// main menu document.head.innerHTML += `
document.body.innerHTML = `
<style> <style>
ul { body {
list-style: none; display: flex;
margin: 0 auto; flex-direction: column;
} font-family: Arial, sans-serif;
margin: 0;
}
a { /* navigation block */
text-decoration: none; nav {
font-family: 'Lora', serif;
transition: .5s linear;
}
i {
margin-right: 10px;
}
nav {
display: block;
margin: 0 auto 10px;
}
nav ul {
padding: 1em 0;
background: #ECDAD6;
}
nav a {
padding: 1em;
background: rgba(177, 152, 145, .3);
border-right: 1px solid #b19891;
color: #695753;
}
nav a:hover {
background: #b19891;
}
nav li {
display: inline;
}
body {
font-family: Arial, Helvetica, sans-serif;
background-color: white;
}
table {
background-color: white;
text-align: left;
border-collapse: collapse;
}
table thead {
background: #CFCFCF;
background: linear-gradient(to bottom, #dbdbdb 0%, #d3d3d3 66%, #CFCFCF 100%);
border-bottom: 3px solid black;
}
table thead th {
font-size: 15px;
font-weight: bold;
color: black;
text-align: center;
}
table td, table th {
border: 1px solid black;
padding: 5px 5px;
}
/* Dark mode styles */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
}
body.dark-mode nav ul {
background: #333;
}
body.dark-mode a {
background: rgba(45, 45, 45, .8);
border-right: 1px solid #2c2c2c;
color: #c7c7c7;
}
body.dark-mode a:hover {
background: #555;
}
body.dark-mode a:visited {
color: #999;
}
body.dark-mode table {
background-color: #222;
color: #ddd;
}
body.dark-mode table thead {
background: linear-gradient(to bottom, #444 0%, #3d3d3d 66%, #333 100%);
border-bottom: 3px solid #888;
}
body.dark-mode table thead th {
font-size: 15px;
font-weight: bold;
color: #ddd;
text-align: center;
}
body.dark-mode table td, body.dark-mode table th {
border: 1px solid #444;
}
body.dark-mode button {
background: rgba(255, 255, 255, .1);
border: 1px solid #444;
color: #ccc;
}
body.dark-mode input,
body.dark-mode select,
body.dark-mode textarea {
background-color: #333; background-color: #333;
color: #e0e0e0; overflow: hidden;
border: 1px solid #444; }
}
body.dark-mode input::placeholder, nav a {
body.dark-mode textarea::placeholder { float: left;
color: #bbb; display: block;
} color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
body.dark-mode hr { nav a:hover {
border-top: 1px solid #444; background-color: #ddd;
} color: black;
}
/* main block */
main {
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
/* checkbox */
label {
display: flex;
gap: 5px;
align-items: center;
cursor: pointer;
}
input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
/* form */
form {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
input[type="text"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
button {
padding: 10px 20px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
/* table */
table {
width: 100%;
background-color: white;
border-collapse: collapse;
margin: 0 auto;
overflow: hidden;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
th {
background-color: #444;
color: white;
}
tr:nth-child(even) {
background-color: #fafafa;
}
tr:hover {
background-color: #edf7ff;
transition: background-color 0.3s ease;
}
/* table on mobile */
@media (max-width: 480px) {
table, thead, tbody, th, td, tr {
display: block;
}
th, td {
box-sizing: border-box;
width: 100% !important;
border: none;
}
tr {
margin-bottom: 10px;
border-radius: 4px;
}
}
</style> </style>
<nav> `;
<ul>
<li><a href="index.html">Streams</a></li> document.body.innerHTML = `
<li><a href="add.html">Add</a></li> <header>
<li><a href="editor.html">Config</a></li> <nav>
<li><a href="log.html">Log</a></li> <a href="index.html"><b>go2rtc</b></a>
<li><a href="network.html">Net</a></li> <a href="add.html">add</a>
<li><a href="#" id="darkModeToggle"> <a href="config.html">config</a>
&#127769; <a href="log.html">log</a>
</a> <a href="net.html">net</a>
</li> </nav>
</ul> </header>
</nav>
` + document.body.innerHTML; ` + document.body.innerHTML;
const sunIcon = '&#9728;&#65039;';
const moonIcon = '&#127765;';
document.addEventListener('DOMContentLoaded', () => {
const darkModeToggle = document.getElementById('darkModeToggle');
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkModeEnabled = () => document.body.classList.contains('dark-mode');
// Update the toggle button based on the dark mode state
const updateToggleButton = () => {
if (isDarkModeEnabled()) {
darkModeToggle.innerHTML = sunIcon;
darkModeToggle.setAttribute('aria-label', 'Enable light mode');
} else {
darkModeToggle.innerHTML = moonIcon;
darkModeToggle.setAttribute('aria-label', 'Enable dark mode');
}
};
const updateDarkMode = () => {
if (localStorage.getItem('darkMode') === 'enabled' || prefersDarkScheme.matches && localStorage.getItem('darkMode') !== 'disabled') {
document.body.classList.add('dark-mode');
} else {
document.body.classList.remove('dark-mode');
}
updateEditorTheme();
updateToggleButton();
};
// Update the editor theme based on the dark mode state
const updateEditorTheme = () => {
if (typeof editor !== 'undefined') {
editor.setTheme(isDarkModeEnabled() ? 'ace/theme/tomorrow_night_eighties' : 'ace/theme/github');
}
};
// Initial update for dark mode and toggle button
updateDarkMode();
// Listen for changes in the system's color scheme preference
prefersDarkScheme.addEventListener('change', updateDarkMode); // Modern approach
// Toggle dark mode and update local storage on button click
darkModeToggle.addEventListener('click', () => {
const enabled = document.body.classList.toggle('dark-mode');
localStorage.setItem('darkMode', enabled ? 'enabled' : 'disabled');
updateToggleButton(); // Update the button after toggling
updateEditorTheme();
});
});
+6 -15
View File
@@ -2,31 +2,21 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>go2rtc - Network</title> <title>go2rtc - Network</title>
<script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script> <script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script>
<style> <style>
body {
font-family: Arial, Helvetica, sans-serif;
background-color: white;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
}
html, body, #network { html, body, #network {
height: 100%; height: 100%;
width: 100%;
}
#network {
flex-grow: 1;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="network"></div>
<script src="main.js"></script> <script src="main.js"></script>
<div id="network"></div>
<script> <script>
/* global vis */ /* global vis */
window.addEventListener('load', () => { window.addEventListener('load', () => {
@@ -79,5 +69,6 @@
update(); update();
}); });
</script> </script>
</body> </body>
</html> </html>