install go2rtc on bob

This commit is contained in:
2026-04-04 19:36:14 +02:00
parent f0b56e63d1
commit ccf88187b8
537 changed files with 69213 additions and 0 deletions
@@ -0,0 +1,188 @@
import {defineConfig} from 'vitepress';
function replace_link(md) {
md.core.ruler.after('inline', 'replace-link', function (state) {
for (const block of state.tokens) {
if (block.type === 'inline' && block.children) {
for (const token of block.children) {
const href = token.attrGet('href');
if (href && href.indexOf('README.md') >= 0) {
// token.attrJoin('style', 'color:red;');
token.attrSet('href', href.replace('README.md', 'index.md'));
}
}
}
}
return true;
});
}
export default defineConfig({
title: 'go2rtc',
description: 'Ultimate camera streaming application',
head: [
// first line (green bold) of Telegram card, autodetect from hostname
['meta', { property: 'og:site_name', content: 'go2rtc.org' }],
// second line of Telegram card (black bold), autodetect from site description
['meta', { property: 'og:title', content: 'go2rtc - Ultimate camera streaming application' }],
// third line of Telegram card, autodetect from site description
['meta', { property: 'og:description', content: 'Support alsa, doorbird, dvrip, eseecloud, ffmpeg, gopro, hass, hls, homekit, mjpeg, mp4, mpegts, nest, onvif, ring, roborock, rtmp, rtsp, tapo, vigi, tuya, v4l2, webrtc, wyze, xiaomi.' }],
['meta', { property: 'og:url', content: 'https://go2rtc.org/' }],
['meta', { property: 'og:image', content: 'https://go2rtc.org/images/logo.png' }],
// important for Telegram - the image will be at the bottom and large
['meta', { property: 'twitter:card', content: 'summary_large_image' }],
],
sitemap: {hostname: 'https://go2rtc.org'},
themeConfig: {
nav: [
{text: 'Home', link: '/'},
],
sidebar: [
{
items: [
{text: 'Installation', link: '/#installation'},
{text: 'Configuration', link: '/#configuration'},
{text: 'Security', link: '/#security'},
],
},
{
text: 'Features',
items: [
{text: 'Streaming input', link: '/#streaming-input'},
{text: 'Streaming output', link: '/#streaming-output'},
{text: 'Streaming ingest', link: '/#streaming-ingest'},
{text: 'Two-way audio', link: '/#two-way-audio'},
{text: 'Stream to camera', link: '/#stream-to-camera'},
{text: 'Publish stream', link: '/#publish-stream'},
{text: 'Preload stream', link: '/#preload-stream'},
{text: 'Streaming stats', link: '/#streaming-stats'},
],
collapsed: false,
},
{
text: 'Codecs',
items: [
{text: 'Codecs filters', link: '/#codecs-filters'},
{text: 'Codecs madness', link: '/#codecs-madness'},
{text: 'Built-in transcoding', link: '/#built-in-transcoding'},
{text: 'Codecs negotiation', link: '/#codecs-negotiation'},
],
collapsed: true,
},
{
text: 'Other',
items: [
{text: 'Projects using go2rtc', link: '/#projects-using-go2rtc'},
{text: 'Camera experience', link: '/#camera-experience'},
{text: 'Tips', link: '/#tips'},
],
collapsed: true,
},
{
text: 'Core modules',
items: [
{text: 'app', link: '/internal/app/'},
{text: 'api', link: '/internal/api/'},
{text: 'streams', link: '/internal/streams/'},
],
collapsed: false,
},
{
text: 'Main modules',
items: [
{text: 'http', link: '/internal/http/'},
{text: 'mjpeg', link: '/internal/mjpeg/'},
{text: 'mp4', link: '/internal/mp4/'},
{text: 'rtsp', link: '/internal/rtsp/'},
{text: 'webrtc', link: '/internal/webrtc/'},
],
collapsed: false,
},
{
text: 'Other modules',
items: [
{text: 'hls', link: '/internal/hls/'},
{text: 'homekit', link: '/internal/homekit/'},
{text: 'onvif', link: '/internal/onvif/'},
{text: 'rtmp', link: '/internal/rtmp/'},
{text: 'webtorrent', link: '/internal/webtorrent/'},
{text: 'wyoming', link: '/internal/wyoming/'},
],
collapsed: false,
},
{
text: 'Script sources',
items: [
{text: 'echo', link: '/internal/echo/'},
{text: 'exec', link: '/internal/exec/'},
{text: 'expr', link: '/internal/expr/'},
{text: 'ffmpeg', link: '/internal/ffmpeg/'},
],
collapsed: false,
},
{
text: 'Other sources',
items: [
{text: 'alsa', link: '/internal/alsa/'},
{text: 'bubble', link: '/internal/bubble/'},
{text: 'doorbird', link: '/internal/doorbird/'},
{text: 'dvrip', link: '/internal/dvrip/'},
{text: 'eseecloud', link: '/internal/eseecloud/'},
{text: 'flussonic', link: '/internal/flussonic/'},
{text: 'gopro', link: '/internal/gopro/'},
{text: 'hass', link: '/internal/hass/'},
{text: 'isapi', link: '/internal/isapi/'},
{text: 'ivideon', link: '/internal/ivideon/'},
{text: 'kasa', link: '/internal/kasa/'},
{text: 'mpeg', link: '/internal/mpeg/'},
{text: 'multitrans', link: '/internal/multitrans/'},
{text: 'nest', link: '/internal/nest/'},
{text: 'ring', link: '/internal/ring/'},
{text: 'roborock', link: '/internal/roborock/'},
{text: 'tapo', link: '/internal/tapo/'},
{text: 'tuya', link: '/internal/tuya/'},
{text: 'v4l2', link: '/internal/v4l2/'},
{text: 'wyze', link: '/internal/wyze/'},
{text: 'xiaomi', link: '/internal/xiaomi/'},
{text: 'yandex', link: '/internal/yandex/'},
],
collapsed: false,
},
{
text: 'Helper modules',
items: [
{text: 'debug', link: '/internal/debug/'},
{text: 'ngrok', link: '/internal/ngrok/'},
{text: 'pinggy', link: '/internal/pinggy/'},
{text: 'srtp', link: '/internal/srtp/'},
],
collapsed: false,
},
],
socialLinks: [
{icon: 'github', link: 'https://github.com/AlexxIT/go2rtc'}
],
outline: [2, 3],
search: {provider: 'local'},
},
rewrites(id) {
// change file names
return id.replace('README.md', 'index.md');
},
markdown: {
config: (md) => {
// change markdown links
md.use(replace_link);
}
},
srcDir: '..',
srcExclude: ['examples/', 'pkg/'],
// cleanUrls: true,
ignoreDeadLinks: true,
});
@@ -0,0 +1,9 @@
# WebSite
These are the sources of the [go2rtc.org](https://go2rtc.org/) website. It's content published on GitHub Pages and is a mirror of [alexxit.github.io/go2rtc/](http://alexxit.github.io/go2rtc/).
The site contains:
- Project's documentation, which is compiled via [vitepress](https://github.com/vuejs/vitepress) from `README.md` files located in the root of the repository, as well as in the `internal` folder.
- Project's API in OpenAPI format, and the [Redoc](https://github.com/Redocly/redoc) viewer
- Project's assets (logo).
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>API | go2rtc</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url="openapi.yaml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

@@ -0,0 +1,18 @@
{
"name": "go2rtc",
"icons": [
{
"src": "https://go2rtc.org/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://go2rtc.org/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000"
}
@@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>webtorrent - go2rtc</title>
<style>
body {
background-color: black;
margin: 0;
padding: 0;
}
html, body, video {
height: 100%;
width: 100%;
}
div {
position: absolute;
top: 50%;
left: 50%;
display: flex;
flex-direction: column;
transform: translateX(-50%) translateY(-50%);
}
</style>
</head>
<body>
<video id="video" autoplay controls playsinline muted></video>
<div id="login">
<input id="share" type="text" placeholder="share">
<input id="pwd" type="text" placeholder="password">
<button id="connect">connect</button>
</div>
<script>
async function PeerConnection(media) {
const pc = new RTCPeerConnection({
iceServers: [{urls: 'stun:stun.l.google.com:19302'}]
});
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);
});
}
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);
});
}
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);
}
document.getElementById('video').srcObject = new MediaStream(localTracks);
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();
} catch (e) {
console.warn(e);
return [];
}
}
function getOffer(pc, timeout) {
return new Promise((resolve, reject) => {
pc.addEventListener('icegatheringstatechange', () => {
if (pc.iceGatheringState === 'complete') resolve(pc.localDescription.sdp);
});
pc.createOffer().then(offer => pc.setLocalDescription(offer));
setTimeout(() => resolve(pc.localDescription.sdp), timeout || 5000);
});
}
</script>
<script>
function decode(buffer) {
return String.fromCharCode(...new Uint8Array(buffer));
}
function encode(string) {
return Uint8Array.from(string, c => c.charCodeAt(0));
}
async function cipher(share, pwd) {
const hash = await crypto.subtle.digest('SHA-256', encode(share));
const nonce = (Date.now() * 1000000).toString(36);
const ivData = await crypto.subtle.digest('SHA-256', encode(share + ':' + nonce));
const keyData = await crypto.subtle.digest('SHA-256', encode(nonce + ':' + pwd));
const key = await crypto.subtle.importKey(
'raw', keyData, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt'],
);
return {
hash: btoa(decode(hash)),
nonce: nonce,
encrypt: async function (plaintext) {
const cryptotext = await crypto.subtle.encrypt(
{name: 'AES-GCM', iv: ivData.slice(0, 12), additionalData: encode(nonce)},
key, encode(plaintext),
);
return btoa(decode(cryptotext));
},
decrypt: async function (cryptotext) {
const plaintext = await crypto.subtle.decrypt(
{name: 'AES-GCM', iv: ivData.slice(0, 12), additionalData: encode(nonce)},
key, encode(atob(cryptotext)),
);
return decode(plaintext);
}
};
}
</script>
<script>
async function connect(share, pwd, media, tracker) {
const crypto = await cipher(share, pwd);
const pc = await PeerConnection(media || 'video+audio');
const offer = await crypto.encrypt(await getOffer(pc));
const ws = new WebSocket(tracker || 'wss://tracker.openwebtorrent.com/');
ws.addEventListener('open', () => {
ws.send(JSON.stringify({
action: 'announce',
info_hash: crypto.hash,
peer_id: Math.random().toString(36).substring(2),
offers: [{
offer_id: crypto.nonce,
offer: {type: 'offer', sdp: offer},
}],
numwant: 1,
}));
});
ws.addEventListener('message', async (ev) => {
const msg = JSON.parse(ev.data);
if (!msg.answer) return;
const answer = await crypto.decrypt(msg.answer.sdp);
await pc.setRemoteDescription({type: 'answer', sdp: answer});
ws.close();
});
}
document.getElementById('connect').addEventListener('click', () => {
const share = document.getElementById('share').value;
const pwd = document.getElementById('pwd').value;
connect(share, pwd);
document.getElementById('login').style.display = 'none';
});
if (location.hash) {
const params = new URLSearchParams(location.hash.substring(1));
const share = params.get('share');
const pwd = params.get('pwd');
const media = params.get('media');
const tracker = params.get('tr');
connect(share, pwd, media, tracker);
document.getElementById('login').style.display = 'none';
}
</script>
</body>
</html>