48fabec431
Added a `copyTextToClipboard` function to handle text copying across different browsers and fallback scenarios. This function utilizes the Clipboard API when available, providing an asynchronous method to copy text securely. For browsers where the Clipboard API is not available or the page is not served over a secure context, a fallback method using a temporary textarea element and `document.execCommand` is employed. Replaced direct use of `navigator.clipboard.writeText` with this function in the 'shareget' click event listener to enhance cross-browser support and error handling.
253 lines
10 KiB
HTML
253 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<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>
|
|
body {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
background-color: white;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
html, body {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
div {
|
|
padding: 10px;
|
|
}
|
|
|
|
div > li {
|
|
list-style-type: none;
|
|
padding-left: 10px;
|
|
position: relative;
|
|
}
|
|
|
|
div > li:before {
|
|
content: "-";
|
|
position: absolute;
|
|
left: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<script src="main.js"></script>
|
|
<div id="links"></div>
|
|
<script>
|
|
const src = new URLSearchParams(location.search).get('src').replace(/[<">]/g, ''); // sanitize
|
|
|
|
document.getElementById('links').innerHTML = `
|
|
<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="api/streams?src=${src}">info.json</a> page with active connections</li>
|
|
`;
|
|
|
|
const url = new URL('api', location.href);
|
|
fetch(url, {cache: 'no-cache'}).then(r => r.json()).then(data => {
|
|
let rtsp = location.host + ':8554';
|
|
try {
|
|
const host = data.host.match(/^[^:]+/)[0];
|
|
const port = data.rtsp.listen.match(/[0-9]+$/)[0];
|
|
rtsp = `${host}:${port}`;
|
|
} catch (e) {
|
|
}
|
|
|
|
document.getElementById('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}?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>
|
|
|
|
<pre>ffplay -fflags nobuffer -flags low_delay -rtsp_transport tcp "rtsp://${rtsp}/${src}"</pre>
|
|
|
|
<h2>H264/H265 source</h2>
|
|
<li><a href="stream.html?src=${src}&mode=webrtc">stream.html</a> WebRTC stream / browsers: all / codecs: H264, PCMU, PCMA, OPUS / +H265 in Safari</li>
|
|
<li><a href="stream.html?src=${src}&mode=mse">stream.html</a> MSE stream / browsers: Chrome, Firefox, Safari Mac/iPad / codecs: H264, H265*, AAC, PCMA*, PCMU*, PCM* / +OPUS in Chrome and Firefox</li>
|
|
<li><a href="api/stream.mp4?src=${src}">stream.mp4</a> legacy MP4 stream with AAC audio / browsers: Chrome, Firefox / codecs: H264, H265*, AAC</li>
|
|
<li><a href="api/stream.mp4?src=${src}&mp4=flac">stream.mp4</a> modern MP4 stream with common audio / browsers: Chrome, Firefox / codecs: H264, H265*, AAC, FLAC (PCMA, PCMU, PCM)</li>
|
|
<li><a href="api/stream.mp4?src=${src}&mp4=all">stream.mp4</a> MP4 stream with any audio / browsers: Chrome / codecs: H264, H265*, AAC, OPUS, MP3, FLAC (PCMA, PCMU, PCM)</li>
|
|
<li><a href="api/frame.mp4?src=${src}">frame.mp4</a> snapshot in MP4-format / browsers: all / codecs: H264, H265*</li>
|
|
<li><a href="api/stream.m3u8?src=${src}">stream.m3u8</a> legacy HLS/TS / browsers: Safari all, Chrome Android / codecs: H264</li>
|
|
<li><a href="api/stream.m3u8?src=${src}&mp4">stream.m3u8</a> legacy HLS/fMP4 / browsers: Safari all, Chrome Android / codecs: H264, H265*, AAC</li>
|
|
<li><a href="api/stream.m3u8?src=${src}&mp4=flac">stream.m3u8</a> modern HLS/fMP4 / browsers: Safari all, Chrome Android / codecs: H264, H265*, AAC, FLAC (PCMA, PCMU, PCM)</li>
|
|
|
|
<h2>MJPEG source</h2>
|
|
<li><a href="stream.html?src=${src}&mode=mjpeg">stream.html</a> with MJPEG mode / browsers: all / codecs: MJPEG, JPEG</li>
|
|
<li><a href="api/stream.mjpeg?src=${src}">stream.mjpeg</a> MJPEG stream / 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>
|
|
|
|
<div>
|
|
<h2>Play audio</h2>
|
|
<pre>example: ffmpeg:https://example.com/song.mp3#audio=pcma#input=file</pre>
|
|
<input id="play-url" type="text" placeholder="url">
|
|
<a id="play-send" href="#">send</a> / cameras with two way audio support
|
|
</div>
|
|
<script>
|
|
document.getElementById('play-send').addEventListener('click', ev => {
|
|
ev.preventDefault();
|
|
const url = new URL('api/streams', location.href);
|
|
url.searchParams.set('dst', src);
|
|
url.searchParams.set('src', document.getElementById('play-url').value);
|
|
fetch(url, {method: 'POST'});
|
|
});
|
|
</script>
|
|
|
|
<div>
|
|
<h2>Publish stream</h2>
|
|
<pre>YouTube: rtmps://xxx.rtmp.youtube.com/live2/xxxx-xxxx-xxxx-xxxx-xxxx
|
|
Telegram: rtmps://xxx-x.rtmp.t.me/s/xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx</pre>
|
|
<input id="pub-url" type="text" placeholder="url">
|
|
<a id="pub-send" href="#">send</a> / Telegram RTMPS server
|
|
</div>
|
|
<script>
|
|
document.getElementById('pub-send').addEventListener('click', ev => {
|
|
ev.preventDefault();
|
|
const url = new URL('api/streams', location.href);
|
|
url.searchParams.set('src', src);
|
|
url.searchParams.set('dst', document.getElementById('pub-url').value);
|
|
fetch(url, {method: 'POST'});
|
|
});
|
|
</script>
|
|
|
|
<div id="webrtc">
|
|
<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+microphone">video+audio+microphone = two way audio from camera</label><br>
|
|
<label><input type="radio" name="webrtc" value="camera+microphone">camera+microphone = stream from browser</label><br>
|
|
<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="shareadd" href="#">share link</a>
|
|
<a id="shareget" href="#">copy link</a>
|
|
<a id="sharedel" href="#">delete</a>
|
|
external WebRTC viewer
|
|
</li>
|
|
</div>
|
|
<script>
|
|
function webrtcLinksUpdate() {
|
|
const media = document.querySelector('input[name="webrtc"]:checked').value;
|
|
|
|
const direction = media.indexOf('video') >= 0 || media === 'audio' ? 'src' : 'dst';
|
|
document.getElementById('local').href = `webrtc.html?${direction}=${src}&media=${media}`;
|
|
|
|
const share = document.getElementById('shareget');
|
|
share.href = `https://alexxit.github.io/go2rtc/#${share.dataset.auth}&media=${media}`;
|
|
}
|
|
|
|
function share(method) {
|
|
const url = new URL('api/webtorrent', location.href);
|
|
url.searchParams.set('src', src);
|
|
return fetch(url, {method: method, cache: 'no-cache'});
|
|
}
|
|
|
|
function onshareadd(r) {
|
|
document.getElementById('shareget').dataset['auth'] = `share=${r.share}&pwd=${r.pwd}`;
|
|
|
|
document.getElementById('shareadd').style.display = 'none';
|
|
document.getElementById('shareget').style.display = '';
|
|
document.getElementById('sharedel').style.display = '';
|
|
|
|
webrtcLinksUpdate();
|
|
}
|
|
|
|
function onsharedel() {
|
|
document.getElementById('shareadd').style.display = '';
|
|
document.getElementById('shareget').style.display = 'none';
|
|
document.getElementById('sharedel').style.display = 'none';
|
|
}
|
|
|
|
function copyTextToClipboard(text) {
|
|
// Modern approach with the Clipboard API
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
// Navigator clipboard is available
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
console.log('Async: Copying to clipboard was successful!');
|
|
}, function(err) {
|
|
console.error('Async: Could not copy text: ', err);
|
|
});
|
|
} else {
|
|
// Fallback using document.execCommand()
|
|
try {
|
|
// Create a temporary element to hold the text to copy
|
|
var textarea = document.createElement("textarea");
|
|
// Make it almost invisible and uneditable
|
|
textarea.style.position = 'fixed';
|
|
textarea.style.top = 0;
|
|
textarea.style.left = 0;
|
|
textarea.style.width = '2em';
|
|
textarea.style.height = '2em';
|
|
textarea.style.padding = 0;
|
|
textarea.style.border = 'none';
|
|
textarea.style.outline = 'none';
|
|
textarea.style.boxShadow = 'none';
|
|
textarea.style.background = 'transparent';
|
|
|
|
// Set the text content and append the textarea to the body
|
|
textarea.value = text;
|
|
document.body.appendChild(textarea);
|
|
|
|
// Select all the text in the textarea
|
|
textarea.focus();
|
|
textarea.select();
|
|
|
|
// Attempt to copy the text selected in the textarea
|
|
var successful = document.execCommand('copy');
|
|
if (successful) {
|
|
console.log('Fallback: Copying text command was successful!');
|
|
} else {
|
|
console.error('Fallback: Could not copy text');
|
|
}
|
|
|
|
// Cleanup - remove the temporary textarea
|
|
document.body.removeChild(textarea);
|
|
} catch (err) {
|
|
console.error('Fallback: Exception while trying to copy', err);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
document.getElementById('shareadd').addEventListener('click', ev => {
|
|
ev.preventDefault();
|
|
share('POST').then(r => r.json()).then(r => onshareadd(r));
|
|
});
|
|
|
|
document.getElementById('shareget').addEventListener('click', ev => {
|
|
ev.preventDefault();
|
|
copyTextToClipboard(ev.target.href);
|
|
});
|
|
|
|
document.getElementById('sharedel').addEventListener('click', ev => {
|
|
ev.preventDefault();
|
|
share('DELETE').then(() => onsharedel());
|
|
});
|
|
|
|
document.getElementById('webrtc').addEventListener('click', ev => {
|
|
if (ev.target.tagName === 'INPUT') webrtcLinksUpdate();
|
|
});
|
|
|
|
share('GET').then(r => {
|
|
if (r.ok) r.json().then(r => onshareadd(r));
|
|
else onsharedel();
|
|
});
|
|
|
|
webrtcLinksUpdate();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|