ok/cancel translation fix + chunked changes; untested

This commit is contained in:
steveseguin
2024-01-29 03:41:05 -05:00
parent 4845391035
commit 7072d32f58
7 changed files with 513 additions and 77 deletions

370
lib.js
View File

@@ -165,7 +165,8 @@ var miscTranslations = { // i can replace this list from time to time from the g
"this-is-you": "This is you, a co-director.<br />You are also a performer.",
"preview-meshcast-disabled": "You can't adjust the preview bitrate for Meshcast or WHIP-based streams",
"no-network": "Network connection lost 🤷‍♀️❌📶",
"no-network-details": "Network connection lost. 🤷‍♀️❌📶\n\nHave you lost your Internet connection?"
"no-network-details": "Network connection lost. 🤷‍♀️❌📶\n\nHave you lost your Internet connection?",
"enter-password-if-desired": "Enter a password if provided, otherwise just click Cancel"
}
function getTranslation(key){ // when using this, instead of miniTranslate, if the user changes the language, it might not update. Used mainly when you don't want any HTML (<span data-translate>) being including in the translation
@@ -788,9 +789,11 @@ function promptUser(eleId, UUID=null){
document.body.insertAdjacentHTML("beforeend", modalTemplate); // Insert modal at body end
getById("promptModalMessage").innerHTML = getById(eleId).innerHTML;
miniTranslate(getById("promptModal"));
if (UUID){
getById("promptModalMessage").dataset.UUID = UUID;
}
}
document.getElementById("modalBackdrop").addEventListener("click", closeModal);
@@ -987,11 +990,12 @@ async function promptAlt(inputText, block=false, asterix=false, value=false, tim
Prompts[pid].resolve();
});
} catch(e){}
getById("modal_"+promptID).addEventListener("click", function(e) {
e.stopPropagation();
return false;
});
miniTranslate(getById("modal_"+promptID));
return;
});
return result;
@@ -1102,6 +1106,7 @@ async function promptRecord(){
e.stopPropagation();
return false;
});
miniTranslate(getById("modal_"+promptID));
return;
});
return result;
@@ -1203,6 +1208,7 @@ async function promptTransfer(value=null, bcmode = null, updateurl = null, queue
e.stopPropagation();
return false;
});
miniTranslate(getById("modal_"+promptID));
return;
});
return result;
@@ -1311,6 +1317,7 @@ async function confirmAlt(inputText, block=false){
e.stopPropagation();
return false;
});
miniTranslate(getById("modal_"+promptID));
return;
});
return result;
@@ -2386,6 +2393,8 @@ function applySceneState(){ // guest side; tally light, etc.
}
}
//miniTranslate(getById("obsState"));
if (visibility){ // BASIC TALLY LIGHT (on deck disabled)
getById("obsState").classList.add("onair"); // LIVE
if (session.tallyStyle){
@@ -6794,7 +6803,7 @@ async function jumptoroom(event = null) {
var passStr = "";
window.focus();
var pass = await promptAlt("Enter a password if provided, otherwise just click Cancel", false, true); //sanitizePassword(session.password);
var pass = await promptAlt(getTranslation("enter-password-if-desired"), false, true); //sanitizePassword(session.password);
if (pass && pass.length) {
session.password = sanitizePassword(pass);
passStr = "&password=" + session.password;
@@ -11911,6 +11920,7 @@ function updateLocalStats(){
}
} catch(e){}
document.getElementById("head5").innerHTML = headerStats;
//miniTranslate(document.getElementById("head5"));
document.getElementById("head5").onclick = function(){
var [menu, innerMenu] = statsMenuCreator();
menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
@@ -15052,6 +15062,21 @@ function publishWebcam(btn = false, miconly=false) {
window.scrollTo(0, 0); // iOS has a nasty habit of overriding the CSS when changing camaera selections, so this addresses that.
getById("head2").className = 'hidden';
if (session.mobile && !session.roomid && (session.permaid===false)){
if (!getById("rememberStreamID").classList.contains("hidden")){
if (getById("rememberStreamIDcheck").checked){
session.streamID = getStorage("permaid") || session.streamID;
setStorage("permaid", session.streamID, 99999); // ~ 13 months?
setStorage("rememberStreamIDmobile", "true", 99999);
} else {
removeStorage("permaid");
setStorage("rememberStreamIDmobile", "false", 99999);
}
}
}
if (session.roomid !== false) { // they are in a room or a faux room
@@ -15309,7 +15334,7 @@ function parseURL4Iframe(iframeURL){
}
}
} else if ((domain=="twitch.tv") || (domain=="www.twitch.tv")){
if (iframeURL.includes("twitch.tv/popout/")){
// this is a twitch live chat window
@@ -15332,7 +15357,7 @@ function parseURL4Iframe(iframeURL){
} else if ((domain=="www.vimeo.com") || (domain=="vimeo.com")){
iframeURL = iframeURL.replace("//vimeo.com/","//player.vimeo.com/video/");
iframeURL = iframeURL.replace("//www.vimeo.com/","//player.vimeo.com/video/");
} else if (domain.includes("tiktok.com")){
} else if (domain.endsWith(".tiktok.com") || (domain == "tiktok.com")){
var split = iframeURL.split("/video/");
if (split.length>1){
split = split[1].split("/")[0].split("?")[0].split("#")[0];
@@ -17887,7 +17912,7 @@ async function createRoomCallback(passAdd, passAdd2) {
if (!session.showDirector) { // if null or false, we want to show the solo link, since the director won't have their control box. The director will be visible in their solo link
getById("miniPerformer").innerHTML = '<button id="press2talk" onmousedown="event.preventDefault(); event.stopPropagation();" class="float" onclick="press2talk(true);" title="You can also enable the director`s Video Output afterwards by clicking the Setting`s button"><i class="las la-headset"></i><span data-translate="push-to-talk-enable"> enable director`s microphone or video<br />(only guests can see this feed)</span></button>';
miniTranslate(getById("miniPerformer"));
//miniTranslate(getById("miniPerformer"));
getById("grabDirectorSoloLink").dataset.raw = "https://" + location.host + location.pathname + "?solo&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
getById("grabDirectorSoloLink").href = "https://" + location.host + location.pathname + "?solo&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
getById("grabDirectorSoloLink").innerText = "https://" + location.host + location.pathname + "?solo&r=" + session.roomid + "&v="+session.streamID + passAdd2 + wss + token;
@@ -17895,6 +17920,8 @@ async function createRoomCallback(passAdd, passAdd2) {
} else {
getById("miniPerformer").innerHTML = '<button id="press2talk" onmousedown="event.preventDefault(); event.stopPropagation();" class="float" onclick="press2talk(true);" title="You can also enable the director`s Video Output afterwards by clicking the Setting`s button"><i class="las la-headset"></i><span data-translate="push-to-talk-enable-2"> enable director`s microphone or video</span></button>';
}
miniTranslate(getById("miniPerformer"));
getById("miniPerformer").className = "";
var tabindex = 26;
@@ -21463,6 +21490,7 @@ function gotDevicesRemote(deviceInfos, UUID) {
}
var matched = false;
var audiomatched = false;
for (let i = 0; i !== deviceInfos.length; ++i) {
const deviceInfo = deviceInfos[i];
if (deviceInfo == null) {
@@ -21484,13 +21512,14 @@ function gotDevicesRemote(deviceInfos, UUID) {
option.text = deviceInfo.label || `microphone ${audioSelect.length + 1}`;
if (getById("remoteAudioLabel_"+UUID).innerText == option.text){
option.selected = "true";
audiomatched = true;
}
audioSelect.appendChild(option);
} else if (deviceInfo.kind === 'audiooutput'){
const option = document.createElement('option');
option.value = deviceInfo.deviceId || "default";
option.text = deviceInfo.label || `microphone ${audioOutputSelect.length + 1}`;
option.text = deviceInfo.label || `speaker ${audioOutputSelect.length + 1}`;
if (getById("remoteAudioOutputSelect_"+UUID).innerText == option.text){
option.selected = "true";
}
@@ -21502,12 +21531,12 @@ function gotDevicesRemote(deviceInfos, UUID) {
if (!matched){
getById("requestVideoDevice_"+UUID).innerHTML = '<i class="las la-video"></i> request';
getById("requestVideoDevice_"+UUID).title = "This will ask the remote guest for permission to change";
} else {
getById("requestVideoDevice_"+UUID).innerHTML = '<i class="las la-video"></i> refresh';
getById("requestVideoDevice_"+UUID).title = "This will reconnect the guest's active video source.";
}
if (!audiomatched){
getById("requestAudioDevice_"+UUID).innerHTML = '<i class="las la-microphone-alt"></i> request';
getById("requestAudioDevice_"+UUID).title = "This will ask the remote guest for permission to change";
}
} catch(e){errorlog(e);}
pokeIframeAPI("remote-devices-info", deviceInfos, UUID);
@@ -21879,6 +21908,9 @@ function cleanupMediaTracks() {
} catch (e) {
errorlog(e);
}
getById("gowebcam").dataset.ready = "false";
getById("gowebcam").dataset.audioready = "false"
getById("gowebcam").disabled = true;
}
/// Detect system changes; handle change or use for debugging
@@ -27171,6 +27203,8 @@ function loadIframe(iframesrc, UUID) { // this is pretty important if you want
}
// trusted domains
var ipsafe = false;
if (iframesrc.startsWith("https://vdo.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://obs.ninja/")){
@@ -27181,19 +27215,32 @@ function loadIframe(iframesrc, UUID) { // this is pretty important if you want
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://backup.obs.ninja/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://www.youtube.com/")){
} else if (iframesrc.startsWith("https://www.youtube.com/") || iframesrc.startsWith("https://youtube.com/")){
iframe.style.border = "0";
setTimeout(function(iframe_id){YoutubeListen(iframe_id);}, 1000, iframeID); // create stats feedback for the director; syncing.
if (session.noaudio){
if (iframesrc.includes("?")){
iframesrc += "&mute=1";
} else {
iframesrc += "?mute=1";
}
}
ipsafe = true;
} else if (iframesrc.startsWith("https://player.twitch.tv/")){
iframe.style.border = "0";
ipsafe = true;
} else if (iframesrc.startsWith("https://twitch.tv/")){
iframe.style.border = "0";
ipsafe = true;
} else if (iframesrc.startsWith("https://www.twitch.tv/")){
iframe.style.border = "0";
ipsafe = true;
} else if (iframesrc.startsWith("https://vimeo.com/")){
iframe.style.border = "0";
ipsafe = true;
} else if (iframesrc.startsWith("https://player.vimeo.com/")){
iframe.style.border = "0";
ipsafe = true;
} else if (iframesrc.startsWith("https://meshcast.io/")){
//iframesrc = iframesrc.replace("//meshcast.io/", "//meshcast.vdo.ninja/");
iframe.style.border = "0";
@@ -27205,10 +27252,18 @@ function loadIframe(iframesrc, UUID) { // this is pretty important if you want
} catch(e){
errorlog(e);
}
} else if (iframesrc.startsWith("https://s10.fun/")){
iframe.style.border = "0";
} else if (iframesrc.startsWith("https://play.rozy.tv/")){
ipsafe = true;
} else if (iframesrc.startsWith("https://app.stageten.tv/")){
iframe.style.border = "0";
ipsafe = true;
}
if (!ipsafe && (urlParams.has('privacy') || urlParams.has('private'))){
if (session.cleanOutput || window.obsstudio){
iframesrc = "./confirm.html?clean&url="+ encodeURI(iframesrc);
} else {
iframesrc = "./confirm.html?url="+ encodeURI(iframesrc);
}
}
iframe.src = iframesrc;
@@ -30964,9 +31019,15 @@ async function updateCameraConstraints(constraint, value = null, ctrl=false, UUI
} else if (constraints.width){
constraints.height = constraints.width;
delete constraints.width;
if (!constraints.aspectRatio && session.currentCameraConstraints && session.currentCameraConstraints.height){
constraints.width = session.currentCameraConstraints.height;
}
} else if (constraints.height){
constraints.width = constraints.height;
delete constraints.height;
if (!constraints.aspectRatio && session.currentCameraConstraints && session.currentCameraConstraints.width){
constraints.height = session.currentCameraConstraints.width;
}
}
}
} else if (window.matchMedia("(orientation: portrait)").matches){ // legacy
@@ -30977,9 +31038,15 @@ async function updateCameraConstraints(constraint, value = null, ctrl=false, UUI
} else if (constraints.width){
constraints.height = constraints.width;
delete constraints.width;
if (!constraints.aspectRatio && session.currentCameraConstraints && session.currentCameraConstraints.height){
constraints.width = session.currentCameraConstraints.height;
}
} else if (constraints.height){
constraints.width = constraints.height;
delete constraints.height;
if (!constraints.aspectRatio && session.currentCameraConstraints && session.currentCameraConstraints.width){
constraints.height = session.currentCameraConstraints.width;
}
}
}
}
@@ -31280,6 +31347,9 @@ async function requestBasicPermissions(constraint = {video: true, audio: true},
//constraints can not be satisfied by avb. devices
} else if (err.name == "NotAllowedError" || err.name == "PermissionDeniedError") {
//permission denied in browser
if (isIFrame){
console.error('Make sure that this IFRAME has the correct permissions allowed, ie:\n\niframe.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;midi;geolocation;";');
}
if (!(session.cleanOutput)) {
setTimeout(function() {
if (window.obsstudio){
@@ -35342,16 +35412,20 @@ function setupSensorData(pollrate = 30) {
const startSensor = (SensorType, sensorKey) => {
if (window[SensorType] && session.sensorDataFilter.includes(sensorKey)) {
session.sensors.data[sensorKey] = {};
let sensor = new window[SensorType]({ frequency: pollrate });
sensor.addEventListener('reading', () => {
session.sensors.data[sensorKey].x = sensor.x !== null ? parseFloat(sensor.x.toFixed(5)) : null;
session.sensors.data[sensorKey].y = sensor.y !== null ? parseFloat(sensor.y.toFixed(5)) : null;
session.sensors.data[sensorKey].z = sensor.z !== null ? parseFloat(sensor.z.toFixed(5)) : null;
session.sensors.data[sensorKey].t = parseInt(Math.round(sensor.timeStamp || 0)) || Date.now()
});
sensor.start();
session.sensors[sensorKey] = sensor;
try {
session.sensors.data[sensorKey] = {};
let sensor = new window[SensorType]({ frequency: pollrate });
sensor.addEventListener('reading', () => {
session.sensors.data[sensorKey].x = sensor.x !== null ? parseFloat(sensor.x.toFixed(5)) : null;
session.sensors.data[sensorKey].y = sensor.y !== null ? parseFloat(sensor.y.toFixed(5)) : null;
session.sensors.data[sensorKey].z = sensor.z !== null ? parseFloat(sensor.z.toFixed(5)) : null;
session.sensors.data[sensorKey].t = parseInt(Math.round(sensor.timeStamp || 0)) || Date.now()
});
sensor.start();
session.sensors[sensorKey] = sensor;
} catch(e){
errorlog(e);
}
}
};
@@ -35380,33 +35454,37 @@ function setupSensorData(pollrate = 30) {
let isFirstUpdate = true;
if (navigator.geolocation && session.sensorDataFilter.includes("pos")) {
navigator.geolocation.watchPosition((pos) => {
session.sensors.data.pos = {
speed: pos.coords.speed!== null ? parseFloat(pos.coords.speed.toFixed(3)) : null,
alt: pos.coords.altitude!== null ? parseFloat(pos.coords.altitude.toFixed(3)) : null,
acc: pos.coords.accuracy!== null ? parseFloat(pos.coords.accuracy.toFixed(3)) : null,
lat: pos.coords.latitude!== null ? parseFloat(pos.coords.latitude.toFixed(3)) : null,
lon: pos.coords.longitude!== null ? parseFloat(pos.coords.longitude.toFixed(3)) : null,
t: parseInt(Math.round(pos.timeStamp || 0)) || Date.now()
};
if (isFirstUpdate && (pos.coords.latitude || pos.coords.longitude)){
isFirstUpdate = false;
console.log(session.sensors.data);
warnUser("🌎🌍🌏 Geo-location sharing is enabled.\n\nIf being tracked is unwanted, please disable the 'Location' permissions in your browser's site settings.",10000);
}
}, (error) => {
errorlog("Geolocation Error:", error);
if (error.code === error.PERMISSION_DENIED) {
warnUser("Geolocation permission was denied.");
}
}, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
});
try {
navigator.geolocation.watchPosition((pos) => {
session.sensors.data.pos = {
speed: pos.coords.speed!== null ? parseFloat(pos.coords.speed.toFixed(3)) : null,
alt: pos.coords.altitude!== null ? parseFloat(pos.coords.altitude.toFixed(3)) : null,
acc: pos.coords.accuracy!== null ? parseFloat(pos.coords.accuracy.toFixed(3)) : null,
lat: pos.coords.latitude!== null ? parseFloat(pos.coords.latitude.toFixed(3)) : null,
lon: pos.coords.longitude!== null ? parseFloat(pos.coords.longitude.toFixed(3)) : null,
t: parseInt(Math.round(pos.timeStamp || 0)) || Date.now()
};
if (isFirstUpdate && (pos.coords.latitude || pos.coords.longitude)){
isFirstUpdate = false;
console.log(session.sensors.data);
warnUser("🌎🌍🌏 Geo-location sharing is enabled.\n\nIf being tracked is unwanted, please disable the 'Location' permissions in your browser's site settings.",10000);
}
}, (error) => {
errorlog("Geolocation Error:", error);
if (error.code === error.PERMISSION_DENIED) {
warnUser("Geolocation permission was denied.");
}
}, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
});
} catch (e) {
errorlog("Device Orientation Error:", e);
}
}
setInterval(function(){
@@ -36230,7 +36308,7 @@ session.onTrack = function(event, UUID){
}
var screenshare = false;
if (session.rpcs[UUID].screenIndexes && session.rpcs[UUID].screenIndexes.length){
if (session.rpcs[UUID].screenIndexes && session.rpcs[UUID].getReceivers && session.rpcs[UUID].screenIndexes.length){
log("session.rpcs[UUID].screenIndexes: " + session.rpcs[UUID].screenIndexes);
var receievers = session.rpcs[UUID].getReceivers(); // excluded
for (var i=0;i<receievers.length;i++){
@@ -36285,7 +36363,7 @@ session.onTrack = function(event, UUID){
return;
}
if (session.encodedInsertableStreams){
if (session.encodedInsertableStreams && session.rpcs[UUID] && session.rpcs[UUID].getReceivers){
var receievers = session.rpcs[UUID].getReceivers(); // excluded
for (var i=0;i<receievers.length;i++){
for (var j=0;j<newTracks.length;j++){
@@ -36411,7 +36489,7 @@ session.onTrack = function(event, UUID){
} else if (videoAdded){
updateIncomingVideoElement(UUID, true, false);
} else if (audioAdded){
updateIncomingVideoElement(UUID, false, true);
updateIncomingVideoElement(UUID, false, true);
if (!session.roomid && session.view && !session.permaid){
setTimeout(function(){updateMixer();},10); // video already has an auto-start, with aspect ratio size change. audio doesn't.
}
@@ -36483,6 +36561,13 @@ function updateIncomingVideoElement(UUID, video=true, audio=true){
}
if (audio){
updateIncomingAudioElement(UUID) // do the same for audio now.
if (!video){
if (session.rpcs[UUID] && session.rpcs[UUID].videoElement){ // this bit of code fixes an issue where the volume button doesn't show, after adding an audio track for the first time
var pastMuteState = session.rpcs[UUID].videoElement.muted;
session.rpcs[UUID].videoElement.muted = !pastMuteState;
session.rpcs[UUID].videoElement.muted = pastMuteState;
}
}
}
}
@@ -38935,6 +39020,14 @@ function whipOut(){
return;
}
//log(event.candidate);
try {
if (session.localNetworkOnly){
if (!filterIceLAN(event.candidate)){
return;
}
}
} catch(e) {errorlog(e);}
candidates.push(event.candidate);
};
@@ -38975,6 +39068,32 @@ function whipOut(){
} catch(e){errorlog(e);}
}
function isSDP(str) {
if (typeof str !== 'string' || str.trim() === '') {
return false;
}
if (!str.startsWith('v=')) {
return false;
}
const lines = str.split('\n');
const sdpKeywords = ['v=', 'o=', 's=', 't=', 'm='];
for (const line of lines) {
const trimmedLine = line.trim();
if (sdpKeywords.some(keyword => trimmedLine.startsWith(keyword))) {
continue;
} else {
return false;
}
}
return true;
}
function ajax(data, type, callback=false){
log("AJAX: "+type);
//log(data);
@@ -38985,7 +39104,7 @@ function whipOut(){
var contentType = this.getResponseHeader('content-type');
if (contentType.startsWith("text/plain")){
if (!contentType.startsWith("application/sdp")){
warnlog("The WHIP output destination responded with an incorrect content type; will attempt to continue still.");
}
@@ -39000,10 +39119,12 @@ function whipOut(){
log(this.getAllResponseHeaders());
if (this.getAllResponseHeaders().indexOf("whep") >= 0) {
WHELPlaybackURL = this.getResponseHeader('whep') || false;
} else {
console.log("Note: No WHEP key/value was found in the WHIP header response or it was not exposed.\n\nProviding the WHEP URL for this WHIP output via the WHEP header key will allow p2p access to the WHEP stream for others conneted to this peer.");
}
if (!WHELPlaybackURL && session.whipOutput){
var targetDomain = session.whipOutput.split("/");
if (targetDomain[2].endsWith(".cloudflarestream.com") && (targetDomain[3].length == 65)){
if ((targetDomain.length>2) && targetDomain[2].endsWith(".cloudflarestream.com") && (targetDomain[3].length == 65)){
WHELPlaybackURL = "https://"+targetDomain[2]+"/"+targetDomain[3].slice(33,65)+"/webRTC/play";
session.whipOut.stats.whipHost = "Cloudflare";
}
@@ -39017,7 +39138,7 @@ function whipOut(){
session.whipoutSettings = {type:"whep", "url": WHELPlaybackURL};
}
if (contentType.startsWith("application/sdp") || contentType.startsWith("text/plain")){
if (contentType.startsWith("application/sdp") || isSDP(this.responseText)){
var jsep = {};
jsep.sdp = this.responseText;
jsep.type = "answer";
@@ -39031,6 +39152,10 @@ function whipOut(){
warnlog("Processing answer:");
warnlog(jsep);
if (session.localNetworkOnly){
jsep.sdp = filterSDPLAN(jsep.sdp)
}
session.whipOut.setRemoteDescription(jsep).then(async function(){
warnlog("SHOULD BE CONNECTED?");
var content = "";
@@ -39061,16 +39186,57 @@ function whipOut(){
}
//}
}).catch(function(e){log(e);});
}).catch(async function(e){
errorlog(e);
errorlog("Recieved an invalid SDP answer response from the WHIP endpoint. While things may still work, it won't work as intended.");
if (WHELPlaybackURL){
log("Since we have the WHEP URL, we will assume its desired to forward it on, even if it may not work.");
try {
if (!keyframe){
keyframe = setInterval(function(){GOP();},6000); // ensure GOP no longer than 6s
}
session.whipOutSetScale();
} catch(e){
errorlog(e);
}
await sleep(1000); // give whip server a moment to setup I guess.
if (session.whipoutSettings){
for (var UUID in session.pcs){
if (session.pcs[UUID].whipout===null){
var data = {}
data.whepSettings = session.whipoutSettings;
if (session.sendMessage(data, UUID)){
session.pcs[UUID].whipout = true;
}
}
}
}
}
});
} else if (contentType == "application/error"){
if (this.responseText==432){
warnUser("Whip out error: 432");
} else {
warnUser("Unknown Whipe Out error");
warnUser("Unknown Whip Out error");
}
} else if (callback){
callback();
} else if (WHELPlaybackURL){
errorlog("WHEP URL provided in header response, but no SDP answer provided. Will still use the WHEP URL, and hope for the best..");
if (session.whipoutSettings){
for (var UUID in session.pcs){
if (session.pcs[UUID].whipout===null){
var data = {}
data.whepSettings = session.whipoutSettings;
if (session.sendMessage(data, UUID)){
session.pcs[UUID].whipout = true;
}
}
}
}
}
}
};
@@ -39335,6 +39501,41 @@ function processSDPFromServer(sdp){ // not the description package; just the sdp
return sdp;
}
function filterIceLAN(candidate){
try {
let candidateString = candidate.candidate;
let privateIPPattern = /(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1]))[0-9\.]*/;
let isMDNSHostname = candidateString.includes('.local');
if (!(candidateString.includes('typ host') && (privateIPPattern.test(candidateString) || isMDNSHostname))) {
log("🟧 dropped candidate due to not being a LAN candidate");
return false;
}
log("🟢 candidate allowed since host-type and has a LAN-IP or is a .local hostname");
} catch(e){
errorlog(e);
}
return true;
}
function filterSDPLAN(sdp){
try {
return sdp.split('\n').filter(line => {
if (line.startsWith('a=candidate:')) {
let parts = line.split(' ');
let type = parts[7]; // The 'typ' field in the candidate string
let address = parts[4];
let privateIPPattern = /(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1]))[0-9\.]*/;
let isMDNSHostname = address.endsWith('.local');
log("🟥 dropped candidate from SDP due to not being a LAN candidate");
return type === 'host' && (privateIPPattern.test(address) || isMDNSHostname);
}
return true;
}).join('\n');
} catch(e){
errorlog(e);
return sdp
}
}
async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLAY WHEP
var candidates = [];
var responseLocation = false;
@@ -39456,6 +39657,15 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
return;
} else if (eTag && icePwd && iceUfrag && acceptPatch && (acceptPatch == "application/trickle-ice-sdpfrag") && event.candidate && responseLocation && !session.rpcs[UUID].whep.iceCompletedCallback){ // "left over" candidates not sent with the SDP offer
log("Send patch request with ice candidate");
try {
if (session.localNetworkOnly){
if (!filterIceLAN(event.candidate)){
return;
}
}
} catch(e) {errorlog(e);}
if (event.candidate.candidate){
let patchCandidate = 'a=ice-ufrag:' + iceUfrag + '\r\n' + // <== what a mess.. https://datatracker.ietf.org/doc/html/draft-murillo-whep
'a=ice-pwd:' + icePwd + '\r\n' +
@@ -39478,6 +39688,15 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
ajax(patchCandidate, "trickle-ice-sdpfrag", false, {"if-match":eTag});
}
} else {
try {
if (session.localNetworkOnly){
if (!filterIceLAN(event.candidate)){
return;
}
}
} catch(e) {errorlog(e);}
candidates.push(event.candidate); // send later if I can?
}
//log(event.candidate);
@@ -39500,6 +39719,11 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
try {
session.rpcs[UUID].whep.createOffer().then(async function(offer){
if (session.localNetworkOnly){
offer.sdp = filterSDPLAN(offer.sdp)
}
offer.sdp = processSDPFromServer(offer.sdp);
//offer.sdp = CodecsHandler.setOpusAttributes(offer.sdp, {'stereo': 1});
return session.rpcs[UUID].whep.setLocalDescription(offer);
@@ -39582,6 +39806,10 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
icePwd = icePwd[1];
}
if (session.localNetworkOnly){
description.sdp = filterSDPLAN(description.sdp)
}
description.sdp = processSDPFromServer(description.sdp); // setup stereo/mono
session.rpcs[UUID].whep.setRemoteDescription(description).then(function(){
@@ -39798,8 +40026,21 @@ async function processWHEPout(data){ // LISTEN FOR REMOTE WHIP
if (event.candidate) {
// Handle ICE candidate.. or not.
}
// try {
// if (session.localNetworkOnly) {
//if (session.localNetworkOnly){
// if (!filterIceLAN(event.candidate)){
// return;
// }
// }
// }
// } catch(e) {errorlog(e);}
};
if (session.localNetworkOnly){
description.sdp = filterSDPLAN(description.sdp)
}
try {
await session.pcs[UUID].setRemoteDescription(description);
}catch(e){
@@ -39853,6 +40094,11 @@ async function processWHEPout(data){ // LISTEN FOR REMOTE WHIP
//await sleep(300); // give it time to collect ice candidates. too lazy to do a promise callback right now
const localDescription = await session.pcs[UUID].createAnswer();
if (session.localNetworkOnly){
localDescription.sdp = filterSDPLAN(localDescription.sdp)
}
await session.pcs[UUID].setLocalDescription(localDescription);
await sleep(session.whepWait);
@@ -41070,7 +41316,7 @@ function getReceivers2(UUID){
ssTracks = session.rpcs[UUID].screenIndexes;
}
if (session.rpcs[UUID].getReceivers){
if (session.rpcs[UUID] && session.rpcs[UUID].getReceivers){
var receivers = session.rpcs[UUID].getReceivers();
} else {
var receivers = [];