mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
ok/cancel translation fix + chunked changes; untested
This commit is contained in:
370
lib.js
370
lib.js
@@ -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 = [];
|
||||
|
||||
Reference in New Issue
Block a user