diff --git a/fileshare.html b/fileshare.html
new file mode 100644
index 0000000..d8fdeaf
--- /dev/null
+++ b/fileshare.html
@@ -0,0 +1,167 @@
+
+
+
+ WebRTC File Sharing
+
+
+
+ WebRTC File Sharing
+
+
+
+
+
+
+
+
+
+
Connecting...
+
0 connections
+
+
+
+
+
\ No newline at end of file
diff --git a/lib.js b/lib.js
index 186ba3d..524d630 100644
--- a/lib.js
+++ b/lib.js
@@ -346,7 +346,6 @@ function applyNewParams(changeParams){
function submitDebugLog(msg=false){
try {
- appendDebugLog({"connection_type": session.stats.network_type});
if (navigator.userAgent){
var _, userAgent = navigator.userAgent;
appendDebugLog({"userAgent": userAgent});
@@ -1284,6 +1283,59 @@ function combinedLayout(layout){
}
return combined;
}
+
+function combinedLayoutSimple(layout){
+ var combined = {};
+ Object.keys(layout).forEach(i=>{
+ if (!layout[i]){return;}
+
+ if (i===""){
+ layout[i].forEach(j=>{
+ if (!j){return;}
+ var streamID = null;
+ if ("slot" in j){
+ try {
+ streamID = session.currentSlots[parseInt( j.slot)+1]; // slot 1 is index of 0, but slot 0 is considered NULL; I need to stream line this a bit
+ } catch(e){
+ errorlog(e);
+ streamID = null;
+ }
+ }
+ if (!streamID){
+ if (combined[""]){
+ combined[""].push(j);
+ } else {
+ combined[""] = j;
+ }
+ } else {
+ combined[streamID] = j;
+ }
+ });
+
+ } else {
+
+ var streamID = null;
+ if ("slot" in layout[i]){
+ try {
+ streamID = session.currentSlots[parseInt(layout[i].slot)+1]; // slot 1 is index of 0, but slot 0 is considered NULL; I need to stream line this a bit
+ } catch(e){
+ errorlog(e);
+ streamID = null;
+ }
+ }
+ if (!streamID){
+ if (combined[""]){
+ combined[""].push(layout[i]);
+ } else {
+ combined[""] = [layout[i]];
+ }
+ } else {
+ combined[streamID] = layout[i];
+ }
+ }
+ });
+ return combined;
+}
session.obsSceneSync = function(){
if (session.layouts && session.obsSceneTriggers && session.obsState && session.obsState.details && session.obsState.details.currentScene.name && session.obsSceneTriggers.includes(session.obsState.details.currentScene.name)){
@@ -1297,7 +1349,9 @@ session.obsSceneSync = function(){
}
}
}
+ return true
}
+ return false;
}
@@ -3652,11 +3706,11 @@ function updateMixer(e=false){
controlBar.style.transform = `translate(${controlBar.xOffset}px, ${controlBar.yOffset}px)`;
}
-
if (session.manual === true){return;}
else if (!session.switchMode && session.director){return;}
else if (session.windowed){return;}
+
clearInterval(updateMixerTimer);
if (updateMixerActive){
if (session.mobile){
@@ -6835,7 +6889,12 @@ function setupCanvas() {
session.canvas = document.createElement("canvas");
session.canvas.width = 512;
session.canvas.height = 288;
- session.canvasCtx = session.canvas.getContext('2d', {alpha: session.alpha, desynchronized: true});
+ try {
+ session.canvasCtx = session.canvas.getContext('2d', {alpha: true, willReadFrequently: true});
+ } catch(e){
+ errorlog(e);
+ session.canvasCtx = session.canvas.getContext('2d');
+ }
session.canvasCtx.fillStyle = "blue";
session.canvasCtx.fillRect(0, 0, 512, 288);
@@ -7140,7 +7199,7 @@ async function makeImages(startup=false){
height = width*session.forceAspectRatio;
}
- session.webPcanvasCtx = session.webPcanvas.getContext('2d', {alpha: false, desynchronized: true});
+ session.webPcanvasCtx = session.webPcanvas.getContext('2d', {alpha: false});
session.webPcanvasCtx.fillStyle = "black";
session.webPcanvasCtx.fillRect(0, 0, width, height);
} else {
@@ -7507,11 +7566,13 @@ function TFLiteWorker(){
const segmentationMaskCanvas = document.createElement('canvas');
segmentationMaskCanvas.width = segmentationWidth;
segmentationMaskCanvas.height = segmentationHeight;
- const segmentationMaskCtx = segmentationMaskCanvas.getContext('2d');
+ const segmentationMaskCtx = segmentationMaskCanvas.getContext('2d', {alpha:true, willReadFrequently: true});
session.tfliteModule.nowTime = new Date().getTime();
session.tfliteModule.offsetTime = 0;
+
function process(){
+
clearTimeout(session.tfliteModule.timeout);
if (!(session.effect=="3" || session.effect=="4" || session.effect=="5")){
@@ -7534,7 +7595,8 @@ function TFLiteWorker(){
}
}
- try{
+ try {
+ segmentationMaskCtx.filter = 'none';
segmentationMaskCtx.drawImage(
session.canvasSource,
0,
@@ -7546,54 +7608,124 @@ function TFLiteWorker(){
segmentationWidth,
segmentationHeight
)
-
+
const imageData = segmentationMaskCtx.getImageData(
0,
0,
segmentationWidth,
segmentationHeight
);
-
+
for (let i = 0; i < segmentationPixelCount; i++) {
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3] = imageData.data[i * 4] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 1] = imageData.data[i * 4 + 1] / 255;
session.tfliteModule.HEAPF32[inputMemoryOffset + i * 3 + 2] = imageData.data[i * 4 + 2] / 255;
}
-
+
session.tfliteModule._runInference();
- for (let i = 0; i < segmentationPixelCount; i++) {
- const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
- const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
- const shift = Math.max(background, person);
- const backgroundExp = Math.exp(background - shift);
- const personExp = Math.exp(person - shift);
- segmentationMask.data[i * 4 + 3] = (255 * personExp) / (backgroundExp + personExp); // softmax
- }
- segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
-
- session.canvasCtx.globalCompositeOperation = 'copy';
-
- if (session.mobile && (session.roomid !==false)){
+ if (!session.experimental){ // standard mode
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
+ const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
+ const shift = Math.max(background, person);
+ const backgroundExp = Math.exp(background - shift);
+ const personExp = Math.exp(person - shift);
+ segmentationMask.data[i * 4 + 3] = Math.min(Math.pow((255 * personExp) / (backgroundExp + personExp),1.5) - 10,255); // softmax
+ }
+
+ segmentationMaskCtx.putImageData(segmentationMask, 0, 0);
+
+ session.canvasCtx.globalCompositeOperation = 'copy';
+ session.canvasCtx.filter = 'blur(8px)';
+ session.canvasCtx.drawImage(
+ segmentationMaskCanvas,
+ 0,
+ 0,
+ segmentationWidth,
+ segmentationHeight,
+ 0,
+ 0,
+ session.canvasSource.width,
+ session.canvasSource.height
+ )
+
+ session.canvasCtx.globalCompositeOperation = 'source-in';
session.canvasCtx.filter = 'none';
- } else {
- session.canvasCtx.filter = 'blur(4px)';
+ session.canvasCtx.drawImage(session.canvasSource, 0, 0);
+
+ } else { // experimental mode, so contouring
+
+ /* session.canvasCtx.clearRect(0, 0, session.canvasSource.width, session.canvasSource.height);
+
+ for (let i = 0; i < segmentationPixelCount; i++) {
+ const background = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2];
+ const person = session.tfliteModule.HEAPF32[outputMemoryOffset + i * 2 + 1];
+ const shift = Math.max(background, person);
+ const backgroundExp = Math.exp(background - shift);
+ const personExp = Math.exp(person - shift);
+ const value = (255 * personExp) / (backgroundExp + personExp); // softmax
+
+ if (value>70){
+ maskData[i] = 1;
+ } else if (value>20 && maskData[i]===1){ // was positive, lets keep it positive
+ maskData[i] = 1;
+ } else {
+ maskData[i] = 0;
+ }
+
+ }
+
+ try {
+ contours = FindContours(maskData, segmentationWidth, segmentationHeight);
+ } catch(e){
+ contours = [];
+ }
+
+ var awidth = session.canvasSource.width/segmentationWidth;
+ var aheight = session.canvasSource.height/segmentationHeight;
+
+
+
+ var biggestContour = [];
+ for (let contour of contours){
+ if (!contour.isHole && (contour.points.length > biggestContour.length)){
+ biggestContour = contour.points;
+ }
+ }
+
+ if (biggestContour.length) { // don't show small things
+ session.canvasCtx.filter = "blur(4px)";
+ session.canvasCtx.beginPath();
+ biggestContour = approxPolyDP(biggestContour);
+ let pt = biggestContour.pop();
+
+ let lastx= Math.round(pt[0]*awidth);
+ let lasty= Math.round(pt[1]*aheight);
+ session.canvasCtx.moveTo(lastx, lasty);
+ while (biggestContour.length){
+ pt = biggestContour.pop();
+ if (pt[0]<=1){pt[0]=-100;}
+ else if (pt[0]>=segmentationWidth-2){pt[0]=segmentationWidth+100;}
+ if (pt[1]<=1){pt[1]=-100;}
+ else if (pt[1]>=segmentationHeight-2){pt[1]=segmentationHeight+100;}
+
+ let nox= Math.round(pt[0]*awidth);
+ let noy= Math.round(pt[1]*aheight);
+
+ session.canvasCtx.quadraticCurveTo(lastx, lasty,nox,noy);
+ lastx = nox;
+ lasty = noy;
+ }
+ session.canvasCtx.closePath();
+ session.canvasCtx.fill();
+ }
+
+ session.canvasCtx.globalCompositeOperation = 'source-in';
+ session.canvasCtx.filter = 'none';
+ session.canvasCtx.drawImage(session.canvasSource, 0, 0); */
}
- session.canvasCtx.drawImage(
- segmentationMaskCanvas,
- 0,
- 0,
- segmentationWidth,
- segmentationHeight,
- 0,
- 0,
- session.canvasSource.width,
- session.canvasSource.height
- )
-
- session.canvasCtx.globalCompositeOperation = 'source-in';
- session.canvasCtx.filter = 'none';
- session.canvasCtx.drawImage(session.canvasSource, 0, 0);
session.canvasCtx.globalCompositeOperation = 'destination-over';
if (session.effect=="4"){ // greenscreen
@@ -7614,20 +7746,25 @@ function TFLiteWorker(){
session.canvasCtx.filter = 'blur(4px)'; // Does not work on Safari
}
session.canvasCtx.drawImage(session.canvasSource, 0, 0);
+ session.canvasCtx.filter = 'none';
} else {
session.tfliteModule.activelyProcessing=false;
session.tfliteModule.looping=false;
return;
}
+
+ //////
+
} catch (e){
- errorlog(e);
- session.tfliteModule.activelyProcessing=false;
- session.tfliteModule.looping=false;
- return;
+ errorlog(e);
+ session.tfliteModule.activelyProcessing=false;
+ session.tfliteModule.looping=false;
+ return;
}
session.tfliteModule.lastTime = session.tfliteModule.nowTime;
session.tfliteModule.nowTime = new Date().getTime();
+
var time = 33 - (session.tfliteModule.nowTime - session.tfliteModule.lastTime);
time = time + session.tfliteModule.offsetTime;
session.tfliteModule.activelyProcessing=false;
@@ -8727,8 +8864,20 @@ function processStats(UUID){
}
try {
- if (session.rpcs[UUID].getStats){
- session.rpcs[UUID].getStats().then(function(stats){
+
+ if (session.rpcs[UUID].realUUID && session.rpcs[session.rpcs[UUID].realUUID]){
+ var node = session.rpcs[session.rpcs[UUID].realUUID];
+ } else {
+ var node = session.rpcs[UUID];
+ }
+
+ var validTrackIds = [];
+ session.rpcs[UUID].videoElement.srcObject.getTracks().forEach(trk=>{
+ validTrackIds.push(trk.id);
+ });
+
+ if (node.getStats){
+ node.getStats().then(function(stats){
if (!(UUID in session.rpcs)){return;}
setTimeout(processStats, session.statsInterval, UUID);
@@ -8747,6 +8896,10 @@ function processStats(UUID){
var trackID = stat.trackIdentifier || stat.id || false;
if ((stat.type=="track") && stat.remoteSource){
+
+ if (stat.trackIdentifier && !validTrackIds.includes(stat.trackIdentifier)){
+ return;
+ }
if (stat.id in session.rpcs[UUID].stats){
session.rpcs[UUID].stats[stat.id]._trackID = stat.trackIdentifier;
@@ -8811,6 +8964,10 @@ function processStats(UUID){
}
} else if ((stat.type=="inbound-rtp") && trackID){
+
+ if (stat.trackIdentifier && !validTrackIds.includes(stat.trackIdentifier)){
+ return;
+ }
session.rpcs[UUID].stats[trackID] = session.rpcs[UUID].stats[trackID] || {};
@@ -8836,7 +8993,6 @@ function processStats(UUID){
session.rpcs[UUID].stats[trackID]._last_bytes = stat.bytesReceived || session.rpcs[UUID].stats[trackID]._last_bytes;
session.rpcs[UUID].stats[trackID]._last_time = stat.timestamp || session.rpcs[UUID].stats[trackID]._last_time;
-
if (stat.mediaType=="video"){
session.rpcs[UUID].stats._codecId = stat.codecId;
@@ -9290,6 +9446,7 @@ function playoutdelay(UUID){ // applies a delay to all videos
}
}
} else if (session.rpcs[UUID].stats[tid]._type=="video"){
+
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset;
receiver.playoutDelayHint = parseFloat(sync_offset/1000); // Chrome seems to somewhat sync audio and video when using the delay
// receiver.jitterBufferDelayhint = parseFloat(sync_offset/1000); // This is deprecated I believe
@@ -9878,7 +10035,7 @@ function processMeshcastStats(UUID){
}
-function printMyStats(menu) { // see: setupStatsMenu
+function printMyStats(menu, screenshare=false) { // see: setupStatsMenu
if (!session){return;}
@@ -9928,7 +10085,7 @@ function printMyStats(menu) { // see: setupStatsMenu
}
} catch(e){errorlog(e);}
- function printViewValues(obj, UUID=false) {
+ function printViewValues(obj, UUID=false) {
if (!(document.getElementById("menuStatsBox"))){
return;
@@ -10013,7 +10170,7 @@ function printMyStats(menu) { // see: setupStatsMenu
if (session.pcs[UUID].savedBitrate){
menu.innerHTML += "current bitrate target" + session.pcs[UUID].savedBitrate + "";
}
- if (session.room!==false){
+ if (session.room!==false && !session.pcs[UUID].meshcast && (session.meshcast!=="audio")){
menu.innerHTML += "adjust video bitrate";
}
}
@@ -10022,17 +10179,25 @@ function printMyStats(menu) { // see: setupStatsMenu
printViewValues(session.stats);
menu.innerHTML += "";
- if (session.mc && session.mc.stats){
+ if (!screenshare && session.mc && session.mc.stats){
printViewValues({"Meshcast_connection":session.mc.stats});
menu.innerHTML += "
";
}
- if (session.whepIn && session.whepIn.stats){
+ if (!screenshare && session.whepIn && session.whepIn.stats){
printViewValues({"Whip_Out_connection":session.whepIn.stats});
menu.innerHTML += "
";
}
for (var uuid in session.pcs) {
- printViewValues(session.pcs[uuid].stats, uuid);
- menu.innerHTML += "
";
+ if (screenshare){
+ if (session.pcs[uuid].realUUID){
+ printViewValues(session.pcs[uuid].stats, uuid);
+ menu.innerHTML += "
";
+ }
+ } else if (!session.pcs[uuid].realUUID){
+ printViewValues(session.pcs[uuid].stats, uuid);
+ menu.innerHTML += "
";
+ }
+
}
if ((iOS) || (iPad)){
menu.innerHTML += "
";
@@ -10400,9 +10565,26 @@ function updateLocalStats(){
}
}
+ var screenTracksIds = [];
+ if (session.screenStream !== false){ // null if already used. false if never used.
+ session.screenStream.getTracks().forEach(trk=>{
+ screenTracksIds.push(trk.id);
+ });
+ }
+
setTimeout(function(UUID) {
if (!( session.pcs[UUID])){return;}
- session.pcs[UUID].getStats().then(function(stats) {
+
+
+ if (session.pcs[UUID].realUUID && session.pcs[session.pcs[UUID].realUUID]){
+ var thisIsAlt = true;;
+ var node = session.pcs[session.pcs[UUID].realUUID];
+ } else {
+ var thisIsAlt = false;
+ var node = session.pcs[UUID];
+ }
+
+ node.getStats().then(function(stats) {
if (!(UUID in session.pcs)){return;}
@@ -10413,8 +10595,16 @@ function updateLocalStats(){
var nominatedCandidate = false;
var candidates = {};
+ var statObject = [];
+ var altStreamList = {};
stats.forEach(stat => {
-
+ statObject.push(stat);
+ if (screenTracksIds.includes(stat.trackIdentifier)){
+ altStreamList[stat.id] = stat.trackIdentifier;
+ }
+ })
+
+ statObject.forEach(stat => {
if (stat.id && stat.id.startsWith("DEPRECATED_")){return;}
if (stat.type == "transport"){
@@ -10432,6 +10622,15 @@ function updateLocalStats(){
session.pcs[UUID].stats._timestamp3 = stat.timestamp;
}
} else if (stat.type == "outbound-rtp") {
+
+ if (thisIsAlt && stat.mediaSourceId && !altStreamList[stat.mediaSourceId]){
+ // this isn't an alt stream, but we are in alt mode
+ return;
+ } else if (!thisIsAlt && stat.mediaSourceId && altStreamList[stat.mediaSourceId]){
+ // this is an alt stream, but we are not in alt mode
+ return;
+ }
+
if (stat.kind == "video") {
if ("framesPerSecond" in stat) {
@@ -11287,6 +11486,8 @@ function toggleVideoMute(apply = false) { // TODO: I need to have this be MUTE,
var toggleSettingsState = false;
async function toggleSettings(forceShow = false) { // TODO: I need to have this be MUTE, toggle, with volume not touched.
+ if (session.nosettings){return;}
+
getById("multiselect-trigger3").dataset.state = "0";
getById("multiselect-trigger3").classList.add('closed');
getById("multiselect-trigger3").classList.remove('open');
@@ -11577,6 +11778,8 @@ async function directMigrate(ele, event, room=false) { // everyone in the room w
broadcastMode = transferSettings.broadcast;
} else if (session.rpcs[ele.dataset.UUID] && session.rpcs[ele.dataset.UUID].stats.info && ("broadcast_mode" in session.rpcs[ele.dataset.UUID].stats.info)){
broadcastMode = session.rpcs[ele.dataset.UUID].stats.info.broadcast_mode;
+ } else if (session.broadcastTransfer){
+ broadcastMode = session.broadcastTransfer;
}
var updateurl = null;
@@ -11771,20 +11974,53 @@ function syncDirectorState(ele){
}
function getDetailedState(sid=false){
- var streamList = {};
+ var streamList = {};
var guestFeeds = document.getElementById("guestFeeds");
for (var UUID in session.rpcs){
if (session.rpcs[UUID].streamID){
if (sid && (sid!==session.rpcs[UUID].streamID)){continue;}
- var item = {};
+ let item = {};
item.streamID = session.rpcs[UUID].streamID;
item.label = session.rpcs[UUID].label;
item.group = session.rpcs[UUID].group;
+
+ try {
+ item.layout = session.rpcs[UUID].layout;
+ if (session.director && session.slotmode){
+ item.slot = query("[data--u-u-i-d='"+UUID+"'][data-slot]").dataset.slot || false;
+ } else if (session.currentSlots){
+ item.slot = Object.keys(session.currentSlots).find(key => session.currentSlots[key] === session.rpcs[UUID].streamID) || false;
+ }
+ if (item.slot){
+ item.slot = parseInt(item.slot);
+ }
+ if (session.director){
+ let featured = query("[data--u-u-i-d='"+UUID+"'][data-action-type='solo-video']");
+ if (featured && parseInt(featured.value)){
+ item.featured = true;
+ } else {
+ item.featured = false;
+ }
+ } else if (session.infocus && session.infocus===session.rpcs[UUID].streamID){
+ item.featured = true;
+ } else {
+ item.featured = false;
+ }
+
+ } catch(e){errorlog(e);}
+
item.iframeSrc = session.rpcs[UUID].iframeSrc;
item.localStream = false;
item.muted = session.rpcs[UUID].remoteMuteState;
item.videoMuted = session.rpcs[UUID].videoMuted;
+ try {
+ item.activeSpeaker = session.rpcs[UUID].activelySpeaking;
+ item.defaultSpeaker = session.rpcs[UUID].defaultSpeaker;
+ } catch(e){
+ errorlog(e);
+ }
+
item.videoVisible = session.rpcs[UUID].videoElement && session.rpcs[UUID].videoElement.checkVisibility();
if (session.rpcs[UUID].videoElement){
item.videoVolume = session.rpcs[UUID].videoElement.volume;
@@ -11860,6 +12096,21 @@ function getDetailedState(sid=false){
streamList[session.streamID].scenes = sceneState;
}
} catch(e){}
+
+
+ if (session.director){
+ let featured = query("#highlightDirector[data-action-type='solo-video'], #container_director [data-action-type='solo-video']");
+ if (featured && parseInt(featured.value)){
+ streamList[session.streamID].featured = true;
+ } else {
+ streamList[session.streamID].featured = false;
+ }
+ } else if (session.infocus && session.infocus===session.streamID){
+ streamList[session.streamID].featured = true;
+ } else {
+ streamList[session.streamID].featured = false;
+ }
+
streamList[session.streamID].label = session.label;
streamList[session.streamID].group = session.group;
streamList[session.streamID].groupView = session.groupView;
@@ -11876,6 +12127,19 @@ function getDetailedState(sid=false){
streamList[session.streamID].speakerMuted = session.speakerMuted;
streamList[session.streamID].position = null;
streamList[session.streamID].meshcast = session.meshcast;
+ streamList[session.streamID].layout = session.layout;
+
+ try {
+ if (session.director && session.slotmode){
+ streamList[session.streamID].slot = query("#container_director [data-slot]").dataset.slot || false;
+ if (streamList[session.streamID].slot){
+ streamList[session.streamID].slot = parseInt(streamList[session.streamID].slot);
+ }
+ } else if (session.currentSlots && session.streamID){
+ streamList[session.streamID].slot = Object.keys(session.currentSlots).find(key => session.currentSlots[key] === session.streamID) || false;
+ }
+ } catch(e){errorlog(e);}
+
if (session.info && session.info.out){
streamList[session.streamID].outbound = session.info.out;
}
@@ -13083,7 +13347,11 @@ async function publishScreen() {
formSubmitting = false;
- var quality = parseInt(getById("webcamquality2").elements.namedItem("resolution2").value) || 0;
+ var quality = 0;
+
+ if (document.getElementById("webcamquality2")){
+ quality = parseInt(document.getElementById("webcamquality2").elements.namedItem("resolution2").value) || 0;
+ }
session.quality_ss = quality;
@@ -13226,9 +13494,9 @@ async function publishScreen() {
session.sink = outputSelect.options[outputSelect.selectedIndex].value; // will probably fail on Safari.
log("Session Sink: " + session.sink);
saveSettings();
- } catch (e){errorlog(e);}
+ } catch (e){warnlog(e);}
- publishScreen2(constraints, audioSelect, true, overrideFramerate).then((res) => {
+ return await publishScreen2(constraints, audioSelect, true, overrideFramerate).then((res) => {
if (res == false) {
return;
} // no screen selected
@@ -13304,6 +13572,8 @@ async function publishScreen() {
getById("head1").className = 'hidden';
getById("head2").className = 'hidden';
+
+ return res;
}).catch(() => {});
}
@@ -13627,16 +13897,23 @@ function publishWebcam(btn = false, miconly=false) {
}
if (!session.avatar && session.mobile && session.streamSrc && !session.streamSrc.getVideoTracks().length){ // this just keeps the phone active.
- let fakeElement = document.createElement("video");
- fakeElement.autoplay = true;
- fakeElement.loop = true;
- fakeElement.muted = true;
- fakeElement.src = "./media/micro.mp4";
- fakeElement.style.width = "1px";
- fakeElement.style.height ="1px";
- fakeElement.controls = false;
- fakeElement.id = "keepAlivePlayer";
- getById("main").appendChild(fakeElement);
+
+ setInterval(function(){
+ if (document.getElementById("keepAlivePlayer") && session.streamSrc.getVideoTracks().length){
+ getById("keepAlivePlayer").remove();
+ } else if (!document.getElementById("keepAlivePlayer")){
+ let fakeElement = document.createElement("video");
+ fakeElement.autoplay = true;
+ fakeElement.loop = true;
+ fakeElement.muted = true;
+ fakeElement.src = "./media/micro.mp4";
+ fakeElement.style.width = "1px";
+ fakeElement.style.height ="1px";
+ fakeElement.controls = false;
+ fakeElement.id = "keepAlivePlayer";
+ getById("main").appendChild(fakeElement);
+ }
+ }, 4000);
}
@@ -15420,6 +15697,13 @@ async function createRoom(roomname = false) {
await createRoomCallback(passAdd, passAdd2);
}
+
+ if (session.meshcast){
+ if (!session.cleanOutput && !session.cleanDirector){
+ document.getElementById("meshcastMenu").classList.remove("hidden");
+ }
+ }
+
pokeIframeAPI("create-room", roomname);
}
@@ -15999,10 +16283,10 @@ async function createRoomCallback(passAdd, passAdd2) {
var tabindex = 26;
if (session.rooms && session.rooms.length > 0){
var container = getById("rooms");
- container.innerHTML += 'Arm Transfer: ';
+ container.innerHTML += 'Arm Transfer: ';
session.rooms.forEach(function (r) {
if(session.roomid == r) return; //don't include self
- container.innerHTML += '';
+ container.innerHTML += '';
tabindex++;
});
}
@@ -16561,9 +16845,14 @@ async function createDirectorScreenshareOnlyBox() { // sstype=3
\
";
if (session.directorUUID){
- controls.innerHTML += "This is you, a co-director.
You are also a performer.
";
+ controls.innerHTML += "This is you, a co-director.
You are also a performer.
";
+ } else if (session.showDirector===false){
+ try {
+ controls.querySelectorAll('[data-action-type="addToScene"]').forEach((ele) => { ele.classList.add("hidden"); });
+ } catch(e){errorlog(e);}
+ controls.innerHTML += "This is your screen share
It's *not* a performer.
";
} else {
- controls.innerHTML += "This is you, the director.
You are also a performer.
";
+ controls.innerHTML += "This your screen share.
It's also a performer.
";
}
}
@@ -16575,6 +16864,8 @@ async function createDirectorScreenshareOnlyBox() { // sstype=3
getById("guestFeeds").appendChild(container);
+
+
Object.keys(session.sceneList).forEach((scene, index) => {
if (document.getElementById("container_screen_director")){
if (!(getById("container_screen_director").querySelectorAll('[data-scene="'+scene+'"]').length)){
@@ -20983,16 +21274,24 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
if (session.avatar && session.avatar.ready){
updateRenderOutpipe();
} else if (session.mobile && !document.getElementById("keepAlivePlayer")){ // keep alive player doens't exist
- let fakeElement = document.createElement("video");
- fakeElement.autoplay = true;
- fakeElement.loop = true;
- fakeElement.muted = true;
- fakeElement.src = "./media/micro.mp4";
- fakeElement.style.width = "1px";
- fakeElement.style.height ="1px";
- fakeElement.controls = false;
- fakeElement.id = "keepAlivePlayer";
- getById("main").appendChild(fakeElement);
+
+ setInterval(function(){
+ if (document.getElementById("keepAlivePlayer") && session.streamSrc.getVideoTracks().length){
+ getById("keepAlivePlayer").remove();
+ } else if (!document.getElementById("keepAlivePlayer")){
+ let fakeElement = document.createElement("video");
+ fakeElement.autoplay = true;
+ fakeElement.loop = true;
+ fakeElement.muted = true;
+ fakeElement.src = "./media/micro.mp4";
+ fakeElement.style.width = "1px";
+ fakeElement.style.height ="1px";
+ fakeElement.controls = false;
+ fakeElement.id = "keepAlivePlayer";
+ getById("main").appendChild(fakeElement);
+ }
+ }, 4000);
+
}
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
@@ -21270,6 +21569,8 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
if (constraints.video && (constraints.video!==true) && (Object.keys(constraints.video).length==0)){
constraints.video = true;
+ } else if (constraints.video && (constraints.video!==true) && (Object.keys(constraints.video).length==1) && ("deviceId" in constraints.video) && ("exact" in constraints.video.deviceId) && (constraints.video.deviceId.exact==="default")){
+ constraints.video = true; // solves issues with IOS, where no permission yet given - can't request device ID it seems until permissions is given.
}
log(constraints);
@@ -22137,7 +22438,7 @@ function setEncodings(sender, settings=null, callback=null, cbarg=null){
// if old Firefox, see if I can do something other than Active?
- if (!changed){
+ if (!changed && !Firefox && !SafariVersion){
log("SET ENCODINGS MATCH INPUT; skipping");
if (callback){
if (cbarg){
@@ -22150,8 +22451,8 @@ function setEncodings(sender, settings=null, callback=null, cbarg=null){
setEncodings(sender);
return;
}
-
- if (Firefox && !(Firefox >=110)){ // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpEncodingParameters now supported in v110, but old versions will need this function still
+
+ if (Firefox && !(Firefox >=110)){ // https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpEncodingParameters now supported in v110, but old versions will need this function still
if ("active" in settings){
warnlog("Firefox does not support track active state. We will use enable/disable for that instead.");
if (FirefoxSenders.sender){
@@ -22182,6 +22483,40 @@ function setEncodings(sender, settings=null, callback=null, cbarg=null){
return;
}
}
+ } else if (Firefox){ // Firefox , all versions, don't support active state with audio yet.?? GAhhhhhhhh!
+ if (("track" in sender) && ("kind" in sender.track) && (sender.track.kind == "audio")){
+ if ("active" in settings){
+ warnlog("Firefox does not support track active state with AUDIO yet... We will use enable/disable for that instead.");
+ if (FirefoxSenders.sender){
+ if (FirefoxSenders.sender.lastState === false){
+ FirefoxSenders.sender.activeState = settings.active;
+ // already set to false, so should stay disabled
+ } else {
+ FirefoxSenders.sender.activeState = settings.active;
+ sender.track.enabled = settings.active; // either true or false
+ }
+ } else {
+ FirefoxSenders.sender = {lastState: sender.track.enabled, activeState: settings.active};
+ sender.track.enabled = settings.active;
+ }
+
+ delete settings.active;
+ if (!Object.keys(settings).length){
+ if (callback){
+ if (cbarg){
+ setTimeout(function(){callback(cbarg);},0);
+ } else {
+ setTimeout(function(){callback();},0);
+ }
+ }
+ log("COMPELTED FIREFOX SET ENCODINGS");
+ sender.encodingsQueueActive = false;
+ setEncodings(sender);
+ return;
+ }
+ }
+
+ }
}
sender.setParameters(params).then(() => {
@@ -23427,7 +23762,7 @@ async function publishScreen2(constraints, audioList=[], audio=true, overrideFra
clearInterval(session.updateLocalStatsInterval);
session.updateLocalStatsInterval = setInterval(function(){updateLocalStats();},session.statsInterval);
-
+
session.seeding=true;
session.seedStream();
@@ -23617,11 +23952,13 @@ function fileShareMessage(fileinfo, idx){
} else if (fileinfo.status === 2){
data.msg = " has a shared a file with you:
"+fileinfo.name+"
";
} else if (fileinfo.status === 3){
- data.msg = " has a shared a file with you:
"+fileinfo.name+"
";
+ data.msg = " has a shared a file with you:
"+fileinfo.name+"
";
+ transferList[idx].status = 6;
} else if (fileinfo.status === 4){
data.msg = " has a shared a file with you:
"+fileinfo.name+"
";
} else if (fileinfo.status === 5){
data.msg = " has a shared a file with you:
"+fileinfo.name+"
";
+ transferList[idx].status = 6;
} else if (fileinfo.status === 6){
return;
}
@@ -23643,6 +23980,7 @@ function fileShareMessage(fileinfo, idx){
data.label = "Someone";
}
data.type = "action";
+
msgTransferList.push(data);
}
@@ -29576,6 +29914,13 @@ function pauseVideo(videoEle, update=true){
window.open(URL, '_blank').focus();
} else if (link.getAttribute("data-action") === "pip-clock") {
popOutClock(taskItemInContext.children[0]);
+ } else if (link.getAttribute("data-action") === "Publish") {
+
+ var URL = taskItemInContext.href;
+ URL+="&clean&chroma=000&ssar=landscape&nosettings&prefercurrenttab&selfbrowsersurface=include&displaysurface=browser&np&nopush&publish&whippush&whippushtoken";
+ var win = window.open( URL ,'targetWindow', 'toolbar=no,location=no,status=no,scaling=no,menubar=no,scrollbars=no,resizable=no,width=1280,height=720');
+ win.focus();
+ win.resizeTo(1280,720);
}
if (inputElement===false){
@@ -29697,6 +30042,12 @@ function pauseVideo(videoEle, update=true){
} else {
items[i].parentNode.classList.remove("hidden");
}
+ } else if (items[i].getAttribute("data-action") === "Publish") {
+ if (taskItemInContext.classList.contains("publish")){
+ items[i].parentNode.classList.remove("hidden");
+ } else {
+ items[i].parentNode.classList.add("hidden");
+ }
}
}
}
@@ -30485,6 +30836,7 @@ function updateMessages(){
var msg = document.createElement("div");
if ("idx" in msgTransferList[i]){
msg.id = "transfer_"+msgTransferList[i].idx;
+ msg.classList.add("transfer");
}
if (msgTransferList[i].type == "sent") {
msg.innerHTML = msgTransferList[i].msg + "" + time + "";
@@ -30503,7 +30855,12 @@ function updateMessages(){
msg.innerHTML = msgTransferList[i].msg;
msg.classList.add("outMessage");
}
- document.getElementById("chatBody").appendChild(msg);
+
+ if (msg.id && document.getElementById(msg.id)){
+ document.getElementById(msg.id).innerHTML = msg.innerHTML;
+ } else {
+ getById("chatBody").appendChild(msg);
+ }
}
if (chatUpdateTimeout) {
clearInterval(chatUpdateTimeout);
@@ -30692,6 +31049,69 @@ function setHotKey(keyinput=true){
return false;
}
+
+async function streamVideoToDropbox(filename) {
+
+ if (!session.dbx){return;}
+
+ return await session.dbx.filesUploadSessionStart({ close: false }).then(function (response) {
+ var sessionId = response.result.session_id;
+ var offset = 0;
+ var queue = [];
+
+ console.log(response);
+
+ function uploadChunk(chunk, oldCursor = false) {
+
+ if (queue.length){ // still uploading
+ queue.push(chunk);
+ return;
+ }
+
+ queue.push(chunk);
+
+ if (chunk===false){
+
+ console.log("DONE UPLOADING.. closing dropbox file: "+offset);
+ console.log(oldCursor);
+
+ session.dbx.filesUploadSessionFinish({ cursor: { session_id: sessionId, offset: offset }, commit: { path: '/' + filename } }).then(function (response) {
+ console.log(response);
+ console.log('File uploaded to Dropbox:', response.path_display);
+ })
+ .catch(function (error) {
+ console.error('Error uploading file:', error);
+ });
+
+ } else {
+
+ var currentOffset = offset;
+ offset += chunk.size;
+
+ var cursor = { session_id: sessionId, offset: currentOffset };
+
+ console.log(cursor);
+
+ session.dbx.filesUploadSessionAppendV2({ cursor: cursor, close: false, contents: chunk }).then(function () {
+ console.log("uploaded");
+ console.log(queue);
+ var x = queue.shift();
+ if (queue.length){
+ uploadChunk(queue.shift(), cursor)
+ }
+ }).catch(function (error) {
+ console.error('Error appending chunk:', error);
+ });
+ }
+ }
+
+ return uploadChunk;
+
+ }).catch(function (error) {
+ console.error('Error starting upload session:', error);
+ });
+}
+
var recordingBitratePromise = false;
var defaultRecordingBitrate = false;
async function recordVideo(target, event = null, videoKbps = false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
@@ -30849,6 +31269,11 @@ async function recordVideo(target, event = null, videoKbps = false) { // event.c
}
video.recorder.stop = function(restart = false, notify = false) {
+
+ if (session.dbx && video.recorder && video.recorder.dropbox){
+ video.recorder.dropbox(false);
+ }
+
if (!video.recording) {
errorlog("ALREADY STOPPED");
updateLocalRecordButton(UUID, -1);
@@ -30951,6 +31376,11 @@ async function recordVideo(target, event = null, videoKbps = false) { // event.c
options.bitsPerSecond = parseInt(videoKbps * 1024); // 100 to 132 kbps audio
}
video.recorder.mediaRecorder = new MediaRecorder(video.srcObject, options);
+
+ //if (session.dbx){
+ // video.recorder.dropbox = await streamVideoToDropbox(); // i don't want to upload to dropbox remote streams; just local
+ //}
+
} else {
options.mimeType = 'audio/webm';
if (audioKbps == 0) {
@@ -30965,6 +31395,10 @@ async function recordVideo(target, event = null, videoKbps = false) { // event.c
stream.addTrack(track, video.srcObject);
});
video.recorder.mediaRecorder = new MediaRecorder(stream, options);
+
+ //if (session.dbx){
+ // video.recorder.dropbox = await streamVideoToDropbox();
+ //}
}
log(options);
@@ -30994,6 +31428,15 @@ async function recordVideo(target, event = null, videoKbps = false) { // event.c
updateLocalRecordButton(UUID, (parseInt((Date.now() - timestamp) / 1000) || 0));
}
} catch(e){warnlog("Stream recording error or ended");}
+
+ try {
+ if (session.dbx && video.recorder && video.recorder.dropbox){
+ video.recorder.dropbox(event.data);
+ }
+
+ } catch(e){
+ errorlog(e);
+ }
}
}
@@ -31240,7 +31683,7 @@ function setupSensorData(pollrate = 30) {
}
-function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
+async function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // event.currentTarget,this.parentNode.parentNode.dataset.UUID
if (session.record === false){warnlog("recordings are disabled by decree of thy host magistrate");}
@@ -31351,6 +31794,11 @@ function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // ev
}
video.recorder.stop = function(restart = false, notify=false) {
+
+ if (session.dbx && video.recorder && video.recorder.dropbox){
+ video.recorder.dropbox(false);
+ }
+
if (!remote){
if (restart){
if (getById("recordLocalbutton").dataset.state == 2) {
@@ -31505,6 +31953,11 @@ function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // ev
return;
}
}
+
+ if (session.dbx){
+ video.recorder.dropbox = await streamVideoToDropbox(filename.toString() + '.webm');
+ }
+
log(video.recorder.mediaRecorder);
} else {
@@ -31520,7 +31973,12 @@ function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // ev
video.srcObject.getAudioTracks().forEach((track) => {
stream.addTrack(track, video.srcObject);
});
- video.recorder.mediaRecorder = new MediaRecorder(stream, options);
+ video.recorder.mediaRecorder = new MediaRecorder(stream, options);
+
+ if (session.dbx){
+ video.recorder.dropbox = await streamVideoToDropbox(filename.toString() + '.webm');
+ }
+
}
log(options);
@@ -31537,6 +31995,10 @@ function recordLocalVideo(action = null, videoKbps = 6000, remote=false) { // ev
}
}
}
+
+ if (session.dbx && video.recorder && video.recorder.dropbox){
+ video.recorder.dropbox(event.data);
+ }
}
}
@@ -32032,7 +32494,11 @@ function addAudioPipeline(UUID, track){ // INBOUND AUDIO EFFECTS ; audio tracks
} catch(e){ }
}
- if (session.rpcs[UUID].channelOffset !== false){
+ if (session.playChannel){
+ session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
+ source = selectChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.playChannel);
+ screwedUp = true;
+ } else if (session.rpcs[UUID].channelOffset !== false){
log("custom offset set");
session.rpcs[UUID].inboundAudioPipeline[trackid].destination = session.audioCtx.createMediaStreamDestination();
source = offsetChannel( session.rpcs[UUID].inboundAudioPipeline[trackid].destination, source, session.rpcs[UUID].channelOffset, session.rpcs[UUID].channelWidth);
@@ -32522,6 +32988,27 @@ function toggleMonoStereoMic(ele){
}
}
+function selectChannel(destination, source, channel){
+ session.audioCtx.destination.channelCountMode = 'explicit';
+ session.audioCtx.destination.channelInterpretation = 'discrete';
+ destination.channelCountMode = 'explicit';
+ destination.channelInterpretation = 'discrete';
+
+ try {
+ destination.channelCount = 1;
+ } catch (e){errorlog("Max channels: "+destination.channelCount);}
+
+
+ var splitter = session.audioCtx.createChannelSplitter(6);
+ var merger = session.audioCtx.createChannelMerger(1); // mono
+
+ source.connect(splitter);
+ splitter.connect(merger, channel-1, 0);
+
+ return merger;
+}
+
+
function offsetChannel(destination, source, offset, width=false){
session.audioCtx.destination.channelCountMode = 'explicit';
session.audioCtx.destination.channelInterpretation = 'discrete';
@@ -32845,8 +33332,8 @@ function audioMeterGuest(mediaStreamSource, UUID, trackid){
for (var i = 0; i < dataArray.length; i++){
total += dataArray[i];
}
- total = total/100;
- session.rpcs[UUID].stats.Audio_Loudness = parseInt(total);
+ total = parseInt(total/150);
+ session.rpcs[UUID].stats.Audio_Loudness = total;
if (session.pushLoudness==true){
var loudnessObj = {};
@@ -32880,15 +33367,17 @@ function audioMeterGuest(mediaStreamSource, UUID, trackid){
if (session.rpcs[UUID].voiceMeter){
session.rpcs[UUID].voiceMeter.dataset.level = total;
if (session.meterStyle==1){
- session.rpcs[UUID].voiceMeter.style.height = Math.min(total,100) + "%";
- if (total>75){
- total = Math.min(100,(total - 75)*4);
- var R = parseInt((255 * total) / 100).toString(16).padStart(2, '0');
- var G = parseInt(255 * (100 - total) / 100).toString(16).padStart(2, '0');
+ var perct = Math.min(total,100);
+
+ session.rpcs[UUID].voiceMeter.style.height = perct + "%";
+ if (total>80){
+ var R = parseInt(255 * perct/100).toString(16).padStart(2, '0');
+ var G = parseInt(255 - 255 * perct /100).toString(16).padStart(2, '0');
session.rpcs[UUID].voiceMeter.style.backgroundColor = "#" + R + G + "00";
} else {
session.rpcs[UUID].voiceMeter.style.backgroundColor = "#00FF00";
}
+
} else {
if (total>15){
session.rpcs[UUID].voiceMeter.style.opacity = 100; // temporary
@@ -33632,7 +34121,87 @@ function targetGuest(target, action, value=null){
return false;
}
+async function startPublishing(){
+
+ if (query("#publishOutURL input[type='text']").dataset.twitch=="true"){
+ session.whipOutput = "https://g.webrtc.live-video.net:4443/v2/offer";
+ } else {
+ session.whipOutput = query("#publishOutURL input[type='text']").value || session.whipOutput || null;
+ }
+
+ if (!session.whipOutput){
+ warnUser("Please first provided an output destination",2500);
+ return;
+ }
+
+ if (!session.whipOutputToken){
+ session.whipOutputToken = query("#publishOutToken input[type='password']").value || false;
+ }
+
+ if (!session.whipOutputToken && query("#publishOutURL input[type='text']").dataset.twitch=="true"){
+ warnUser("Please enter a Twitch stream token first",2000);
+ return;
+ }
+ getById("publishSettings").classList.add("hidden");
+ var ret = await publishScreen();
+ if (ret){
+ getById("publishSettings").classList.add("hidden");
+ resizeWindow(1280,720);
+ document.title="PUBLISHING🔴"+document.title;
+ } else {
+ getById("publishSettings").classList.remove("hidden");
+ }
+}
+function twitchSelect(ele){
+ if (ele.checked){
+ //query("#publishOutURL input[type='text']").value =
+ query("#publishOutURL input[type='text']").disabled = true;
+ query("#publishOutURL input[type='text']").classList.add("disable");
+ query("#publishOutURL input[type='text']").dataset.twitch = "true";
+
+ query("#publishOutToken input[type='password']").placeholder = "Twitch stream token here";
+ } else {
+ query("#publishOutURL input[type='text']").disabled = null;
+ query("#publishOutURL input[type='text']").classList.remove("disable");
+ delete getById("publishOutURL").disabled;
+ query("#publishOutURL input[type='text']").dataset.twitch = "false";
+ query("#publishOutToken input[type='password']").placeholder = "WHIP auth token here";
+ }
+}
+
+function resizeWindow(width, height){
+ if (window.outerWidth) {
+ window.resizeTo(
+ width + (window.outerWidth - window.innerWidth),
+ height + (window.outerHeight - window.innerHeight)
+ );
+ } else {
+ window.resizeTo(500, 500);
+ window.resizeTo(
+ width + (500 - document.body.offsetWidth),
+ height + (500 - document.body.offsetHeight)
+ );
+ }
+
+ setInterval(function(){
+ if ((window.innerWidth/window.innerHeight > 17/9) && (window.innerWidth/window.innerHeight < 15/9)){
+ return;
+ }
+ if (window.outerWidth) {
+ window.resizeTo(
+ width + (window.outerWidth - window.innerWidth),
+ height + (window.outerHeight - window.innerHeight)
+ );
+ } else {
+ window.resizeTo(500, 500);
+ window.resizeTo(
+ width + (500 - document.body.offsetWidth),
+ height + (500 - document.body.offsetHeight)
+ );
+ }
+ },5000);
+}
function whipOut(){
log("whipOut");
@@ -33801,59 +34370,15 @@ function whipOut(){
var contentType = this.getResponseHeader('content-type');
- if (contentType.startsWith("application/sdp")){
+ if (contentType.startsWith("text/plain")){
+ console.warn("The WHIP output destination responded with an incorrect content type; will attempt to continue still.");
+ }
+
+ if (contentType.startsWith("application/sdp") || contentType.startsWith("text/plain")){
var jsep = {};
jsep.sdp = this.responseText;
jsep.type = "answer";
- /* var audio = {};
- if (session.stereo && (session.stereo!==2)){
- audio.stereo = 1;
- } else {
- audio.stereo = 0;
- }
-
- var audioBitrate = 64;
- */
- /* if (session.meshcastAudioBitrate){
- if (session.meshcastAudioBitrate>510){
- session.meshcastAudioBitrate = 510;
- }
- audio.maxaveragebitrate = session.meshcastAudioBitrate * 1024;
- audio.useinbandfec = session.noFEC ? 0 : 1;
-
- audio.dtx = session.dtx;
- audio.cbr = session.cbr; // this is a bit silly, as its meant to be a viewer flag.
-
- audioBitrate = session.meshcastAudioBitrate;
- }
- jsep.sdp = CodecsHandler.setOpusAttributes(jsep.sdp, audio);
- */
-
-
- /* if (!codec){
- jsep.sdp = jsep.sdp.replace(/42001f/gi,"42e01f");
- jsep.sdp = jsep.sdp.replace(/420029/gi,"42e01f");
- } else if (codec.length==6){
- jsep.sdp = jsep.sdp.replace(/42e01f/gi,codec); // openH264
- jsep.sdp = jsep.sdp.replace(/42001f/gi,codec); // external encoder
- jsep.sdp = jsep.sdp.replace(/420029/gi,codec); // external encoder
- jsep.sdp = jsep.sdp.replace(/42a01e/gi,codec); // external encoder
- jsep.sdp = jsep.sdp.replace(/42a014/gi,codec); // external encoder
- jsep.sdp = jsep.sdp.replace(/42a00b/gi,codec); // external encoder
- jsep.sdp = jsep.sdp.replace(/640c1f/gi,codec); // will not work
- } */
-
-
- /* if (session.meshcastBitrate){
- try {
- var kbps = audioBitrate + session.meshcastBitrate;
- jsep.sdp = CodecsHandler.setVideoBitrates(jsep.sdp, {
- min: parseInt(kbps/10) || 1,
- max: kbps || 1
- }, codec);
- }catch(e){}
- } */
warnlog("Processing answer:");
warnlog(jsep);
@@ -33893,9 +34418,9 @@ function whipOut(){
xhttp.open("POST", session.whipOutput, true);
}
- if (session.whipOutputToken){
+ if (session.whipOutputToken){
xhttp.setRequestHeader('Authorization', 'Bearer ' + session.whipOutputToken);
- }
+ }
xhttp.setRequestHeader('Content-Type', 'application/'+type);
@@ -36148,9 +36673,9 @@ async function createSecondStream() { //////////////////////////// &sstype=3 ?
var [menu, innerMenu] = statsMenuCreator();
- menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu, true);
- printMyStats(innerMenu);
+ printMyStats(innerMenu, true);
e.stopPropagation();
return false;
}
@@ -36187,9 +36712,9 @@ async function createSecondStream() { //////////////////////////// &sstype=3 ?
var [menu, innerMenu] = statsMenuCreator();
- menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu);
+ menu.interval = setInterval(printMyStats,session.statsInterval, innerMenu, true);
- printMyStats(innerMenu);
+ printMyStats(innerMenu, true);
event.stopPropagation();
return false;
//////
diff --git a/main.css b/main.css
index 346dc61..440693a 100644
--- a/main.css
+++ b/main.css
@@ -1165,9 +1165,7 @@ hr {
margin: 0;
}
.queueNotification {
- position: relative;
- top: -40px;
- right: -33px;
+
padding: 2px 0;
border-radius: 50%;
background: #335c3a;
@@ -1284,6 +1282,22 @@ button#press2talk{
position: relative;
}
+button.btnArmTransferRoom{
+ width:auto;
+ margin-left: 2px;
+ height:38px;
+ border-radius: 15px;
+}
+button.btnArmTransferRoom i{
+ margin-right:3px;
+}
+button.btnArmTransferRoom:hover{
+ background-color: var(--green-accent)!important;
+}
+
+button.btnArmTransferRoom.selected{
+ background-color: #840000!important;
+}
#container.vidcon {
height:100%;
@@ -1354,22 +1368,6 @@ button#press2talk{
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
-button.btnArmTransferRoom{
- width:auto;
- margin-left: 2px;
- height:38px;
- border-radius: 15px;
-}
-button.btnArmTransferRoom i{
- margin-right:3px;
-}
-button.btnArmTransferRoom:hover{
- background-color: var(--green-accent);
-}
-
-button.btnArmTransferRoom.selected{
- background-color: #840000;
-}
@media only screen and (max-height: 540px){
#gridlayout>#container.vidcon {
@@ -1602,7 +1600,7 @@ body.darktheme{
.gowebcam:enabled {
background-color: #26e726 !important;
background: radial-gradient(#26e726, #2EeF2E);
- color: black;
+ color: black!important;
font-weight: bold !important;
box-shadow: 0 0 31px 0px #244e1c44;
animation: pulsate 2s ease-out infinite;
@@ -1725,7 +1723,7 @@ input[type=range]::-webkit-slider-thumb {
box-shadow: 1px 1px 1px #000,0 0 1px #0d0d0d;
border: 1px solid #000;
height: 24px;
- width: 14px;
+ width: 24px;
border-radius: 3px;
cursor: pointer;
-webkit-appearance: none;
@@ -1737,7 +1735,7 @@ input[type=range]::-moz-range-thumb {
box-shadow: 1px 1px 1px #000,0 0 1px #0d0d0d;
border: 1px solid #000;
height: 24px;
- width: 14px;
+ width: 24px;
border-radius: 3px;
cursor: pointer;
background-color: var(--lighttheme-2);
@@ -1747,7 +1745,7 @@ input[type=range]::-ms-thumb {
box-shadow: 1px 1px 1px #000,0 0 1px #0d0d0d;
border: 1px solid #000;
height: 24px;
- width: 14px;
+ width: 24px;
border-radius: 3px;
cursor: pointer;
background-color: var(--lighttheme-2);
@@ -1848,7 +1846,7 @@ div#chatBody a {
display: flex;
flex-wrap: wrap;
width: 100%;
- max-width: min(500px, 100vh);
+ max-width: min(500px, 100vw);
z-index: 3;
margin-bottom: 65px;
gap: 0px 5px;
@@ -1857,11 +1855,11 @@ div#chatBody a {
#chatInput {
color: #000;
background-color: #FFFE;
- width: 70%;
+ width: calc(100% - 160px);
padding: 3px;
}
.chatBarInputButton {
- width: calc(30% - 5px);
+ width: 50px;
margin: unset;
}
@@ -3426,10 +3424,12 @@ div#roomnotes2 {
}
.directorBlue{
background-color: #5c7785 !important;
+ display: var(--show-codirectors) !important;
}
.directorBox{
background-color: #606383 !important;
+ display: var(--show-codirectors) !important;
}
/* ---- DIRECTORS PAGE - Guest Controls Box ---- */
.controlsGrid {
@@ -3946,6 +3946,7 @@ a#reshare {
border-radius: 2vh;
pointer-events:none;
border: 1px black solid;
+ z-Index: 2;
}
.video-meter-2 {
@@ -3962,6 +3963,7 @@ a#reshare {
border-radius: 5px;
pointer-events:none;
border: 5px green solid;
+ z-Index: 2;
}
.video-meter-director {
@@ -3984,6 +3986,7 @@ a#reshare {
pointer-events:none;
border: 1px black solid;
transition: height 0.1s ease, background-color 0.1s ease;
+ z-Index: 2;
}
@@ -4028,6 +4031,7 @@ a#reshare {
border-radius: 2vh;
background-color:#b11313;
padding: 2px 2px 2px 1px;
+ z-index: 2;
}
.video-mute-state-userlist {
@@ -4300,6 +4304,23 @@ input:checked + .slider:before {
overflow: hidden;
overflow-wrap: break-word;
}
+#publishSettings{
+ position: absolute;
+ background-color: #ddddddee;
+ box-shadow: 0 0 30px 10px #0000005c;
+ color: black;
+ font-size: 1.0em;
+ bottom: calc(50% - 130px);
+ left: 50%;
+ transform: translate(-50%, 0%);
+ border-radius: 10px;
+ font-weight: bold;
+ z-index:31;
+ width:550px;
+ max-width:100%;
+ overflow: hidden;
+ overflow-wrap: break-word;
+}
.largeTextEntry {
width: 90%;
margin: 10px 5%;
@@ -4953,12 +4974,12 @@ button:hover {
button i {
font-size: 130%;
}
-.darktheme button {
+.darktheme :not(.promptModalInner) > button {
background-color: var(--discord-grey-5);
border: 1px solid var(--discord-grey-8);
color: var(--discord-text);
}
-.darktheme button:hover {
+.darktheme :not(.promptModalInner) > button:hover {
filter: brightness(1.05);
}
@@ -5080,13 +5101,13 @@ input[type='checkbox'], input[type='radio'] {
}
-.darktheme input {
+.darktheme :not(.promptModalInner) > input {
border: 1px solid var(--discord-grey-8);
color: var(--near-black);
border-radius: 4px;
}
-.darktheme input::placeholder {
+.darktheme :not(.promptModalInner) > input::placeholder {
color: var(--discord-grey-8) !important;
}
@@ -5105,7 +5126,6 @@ input[type='checkbox'], input[type='radio'] {
.container-inner {
display: none;
background-color: var(--lighttheme-3);
- max-height: 100%;
min-height: calc(100% - 100px);
margin-bottom:30px;
}
@@ -5224,41 +5244,6 @@ input[type='checkbox'], input[type='radio'] {
cursor: not-allowed;
}
-/* Buttons to disable due to them being a director */
-.directorBoxColor button[data-action-type="hangup"],
-.directorBoxColor button[data-action-type="toggle-remote-speaker"],
-.directorBoxColor button[data-action-type="toggle-remote-display"],
-.directorBoxColor button[data-action-type="mute-video-guest"],
-.directorBoxColor button[data-action-type="advanced-camera-settings"],
-.directorBoxColor button[data-action-type="advanced-audio-settings"],
-.directorBoxColor button[data-action-type="order-down"],
-.directorBoxColor button[data-action-type="order-up"],
-.directorBoxColor button[data-action-type="toggle-group"],
-.directorBoxColor button[data-action-type="mute-guest"],
-.directorBoxColor .tooltip {
- opacity: 0.5;
- cursor: not-allowed;
- pointer-events: none;
-}
-
-.directorBoxColor {
- border: 2px solid var(--director-box);
- box-shadow: 0px 0px 15px var(--director-box);
- display: var(--show-codirectors) !important;
-}
-
-.darktheme .codirectorBoxColor {
- border: 2px solid var(--codirector-dark-box);
- box-shadow: 0px 0px 15px var(--codirector-dark-box);
- display: var(--show-codirectors) !important;
-}
-
-.codirectorBoxColor {
- border: 2px solid var(--codirector-box);
- box-shadow: 0px 0px 15px var(--codirector-box);
- display: var(--show-codirectors) !important;
-}
-
.darktheme .invite_setting_group {
color: var(--discord-text);
}
diff --git a/main.js b/main.js
index 36c2499..a98da19 100644
--- a/main.js
+++ b/main.js
@@ -293,17 +293,28 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (urlParams.has('whippush') || urlParams.has('whipout') || urlParams.has('pushwhip')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
- let whippush = urlParams.get('whippush') || urlParams.get('whipout') || urlParams.get('pushwhip');
- if (whippush){
+ session.whipOutput = urlParams.get('whippush') || urlParams.get('whipout') || urlParams.get('pushwhip') || null;
+ if (session.whipOutput){
try {
- session.whipOutput = decodeURIComponent(whippush);
+ if (session.whipOutput == 'twitch'){
+ session.whipOutput = "https://g.webrtc.live-video.net:4443/v2/offer";
+ query("#publishOutToken input[type='password']").placeholder = "Twitch stream token here";
+ } else {
+ session.whipOutput = decodeURIComponent(session.whipOutput);
+ }
} catch(e){
errorlog(e);
}
+ } else {
+ getById("publishOutURL").classList.remove("hidden");
}
+
}
if (urlParams.has('whippushtoken') || urlParams.has('whipouttoken') || urlParams.has('pushwhiptoken')) {// URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
session.whipOutputToken = urlParams.get('whippushtoken') || urlParams.get('whipouttoken') || urlParams.get('pushwhiptoken') || false;
+ if (!session.whipOutputToken){
+ getById("publishOutToken").classList.remove("hidden");
+ }
}
if (urlParams.has('whepplay')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
@@ -328,6 +339,17 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
+
+ if (urlParams.has('whepplaytoken')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
+ if (urlParams.get('whepplaytoken')){
+ try {
+ session.whepInputToken = urlParams.get('whepplaytoken')
+ } catch(e){
+ errorlog(e);
+ }
+ }
+ }
+
if (urlParams.has('nomouseevents') || urlParams.has('nme')) {
session.disableMouseEvents = true;
}
@@ -415,6 +437,25 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
+ if (urlParams.has('broadcasttransfer') || urlParams.has('bct')) {
+ log("Broadcast transfer flag set");
+ session.broadcastTransfer = urlParams.get('broadcasttransfer') || urlParams.get('bct') || null;
+ if (session.broadcastTransfer === "false") {
+ session.broadcastTransfer = false;
+ } else if (session.broadcastTransfer=== "0") {
+ session.broadcastTransfer = false;
+ } else if (session.broadcastTransfer === "no") {
+ session.broadcastTransfer = false;
+ } else if (session.broadcastTransfer === "off") {
+ session.broadcastTransfer = false;
+ } else {
+ session.broadcastTransfer = true;
+ }
+ if (transferSettings){
+ transferSettings.broadcast = session.broadcastTransfer;
+ }
+ }
+
if (urlParams.has('broadcast') || urlParams.has('bc')) {
log("Broadcast flag set");
session.broadcast = urlParams.get('broadcast') || urlParams.get('bc') || null;
@@ -567,6 +608,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
if (urlParams.has('showdirector') || urlParams.has('sd')) {
session.showDirector = parseInt(urlParams.get('showdirector')) || parseInt(urlParams.get('sd')) || true; // if 2, video only allowed. True or 1 will be video + audio allowed.
+ // fyi, true is the same as 1 when == is used, so assert(1==true) is true.
}
if (urlParams.has('bitratecutoff') || urlParams.has('bitcut')) {
@@ -623,6 +665,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
document.addEventListener('fullscreenchange', event => {
log("full screen change event");
log(event);
+
+ if (document.getElementById("previewWebcam")){
+ return;
+ }
+
if (session.orientation && session.mobile){
if (document.fullscreenElement) {
document.exitFullscreen();
@@ -751,6 +798,8 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("container-5").classList.add("skip-animation");
getById("container-5").classList.remove('pointer');
+ getById("sharefilebutton").style.display = "flex";
+
if (SafariVersion){
getById("safari_warning_fileshare").classList.remove('hidden');
} else if (!Firefox){
@@ -812,6 +861,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.displaySurface = urlParams.get('displaysurface') || "monitor";
}
+ if (urlParams.has('locksize')){ // browser, window, or monitor (which is default selected)
+ session.lockWindowSize = urlParams.get('locksize') || true;
+ }
+
if (urlParams.has('intro') || urlParams.has('ib')) {
session.introButton = true;
}
@@ -1118,12 +1171,24 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("mutetoggle").style.top = "unset";
}
+
+ if (urlParams.has('nosettings')){
+ session.nosettings = true;
+ getById("settingsbutton").classList.add("hidden");
+ }
+
+ if (urlParams.has('publish')){
+ session.publish = true;
+ getById("publishSettings").style.display = "block";
+ }
if (urlParams.has('nopush') || urlParams.has('noseed') || urlParams.has('viewonly') || urlParams.has('viewmode')) { // this is like a scene; Seeding is disabled. Can be used with &showall to show all videos on load
session.doNotSeed=true;
- session.scene = null; // not a scene, but sorta. false vs null makes a difference here.
- session.videoDevice = 0;
- session.audioDevice = 0;
+
+ if (session.scene===false){
+ session.scene = null; // not a scene, but sorta. false vs null makes a difference here.
+ }
+
session.dataMode = true; // thios will let us connect
// session.showall = true; // this can be used to SHOW the videos. (&showall)
}
@@ -1167,8 +1232,12 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
if (session.dataMode){
- session.videoDevice = 0;
- session.audioDevice = 0;
+
+ if (!(session.meshcast || (session.whipOutput!==false) || session.screenshare)){
+ session.videoDevice = 0;
+ session.audioDevice = 0;
+ }
+
getById("mainmenu").classList.add("hidden");
//session.autohide = true;
//session.autostart = true;
@@ -2255,6 +2324,15 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}, session.forceRetry*1000);
}
+ session.dbx = false;
+ if (urlParams.get('dropbox')){
+ loadScript("https://cdnjs.cloudflare.com/ajax/libs/dropbox.js/10.34.0/Dropbox-sdk.min.js", ()=>{
+ log("Loaded dropbox SDK");
+ var accessToken = urlParams.get('dropbox');
+ session.dbx = new Dropbox.Dropbox({ accessToken: accessToken });
+ });
+ }
+
try {
if (urlParams.has("darkmode") || urlParams.has("nightmode")){
session.darkmode = urlParams.get("darkmode") || urlParams.get("nightmode") || null;
@@ -2542,6 +2620,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.slots = parseInt(urlParams.get('slots')) || 4;
}
+ if (urlParams.has('alpha')) {
+ session.alpha = true;
+ }
+
if (urlParams.has('chunked')) {
session.chunked = parseInt(urlParams.get('chunked')) || 2500; // sender side; enables to allows.
// session.alpha = true;
@@ -3107,7 +3189,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
log("max channels is 32; channels offset");
session.audioEffects = true;
}
-
+ if (urlParams.get('playchannel')) { // must be loaded before channelOffset
+ session.playChannel = parseInt(urlParams.get('playchannel')); // for audio output ; not input. see: &channelcount instead.
+ session.audioEffects = true;
+ }
if (urlParams.has('enhance')) {
//if (parseInt(urlParams.get('enhance')>0){
session.enhance = true; //parseInt(urlParams.get('enhance'));
@@ -4070,6 +4155,28 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.screenShareLabel = session.screenShareLabel.replace(/_/g, " ")
}
+ if (urlParams.has('whepshare') || urlParams.has('whepsrc')) { // URL or data:base64 image. Becomes local to this viewer only. This is like &avatar, but slightly different. Just CSS in this case
+ try {
+
+ session.whepSrc = urlParams.get('whepshare') || urlParams.get('whepsrc') || false;
+ console.log(session.whepSrc);
+ if (!session.whepSrc){
+ session.whepSrc = await promptAlt("Enter the WHEP source as a URL");
+ } else {
+ session.whepSrc = decodeURIComponent(session.whepSrc, true);
+ }
+ getById("container-6").classList.remove('hidden');
+ getById("container-6").classList.add("skip-animation");
+ getById("container-6").classList.remove('pointer');
+
+ if (session.whepSrc){
+ delayedStartupFuncs.push([shareWebsite, session.whepSrc]);
+ }
+ } catch(e){
+ errorlog(e);
+ }
+ }
+
if (session.roomid!==false){
if (!(session.cleanOutput)) {
if (session.roomid === "test") {
@@ -4476,6 +4583,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
getById("gridlayout").classList.add("nocontrolbar");
}
+ if (urlParams.has('experimental')) {
+ session.experimental = true;
+ }
+
if (urlParams.has('flagship')) {
session.flagship = true;
}
@@ -5540,38 +5651,45 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
});
window.addEventListener("online", function (e) {
+
log("Back ONLINE");
closeModal();
+ if (!session.onceConnected){ // never connected to websockets before. Let's not trigger retryWatchInterval if we don't have to.
+ return;
+ }
+
if (!session.retryWatchInterval()){ // ask for the streams again to watch
session.ping(); // if no streams requested, let's ping instead.
}
});
- function updateConnectionStatus() {
+ /* function updateConnectionStatus() { // no longer works in chrome.
try{
if (!session.stats){
return;
}
-
- log("Connection type changed from " + session.stats.network_type + " to " + Connection.type);
- if (session.stats.network_type && (session.stats.network_type !== Connection.type)){
- var miniInfo = {};
- miniInfo.con = Connection.type;
- session.sendMessage({"miniInfo":miniInfo});
+ if (Connection.type){
+ log("Connection type changed from " + session.stats.network_type + " to " + Connection.type);
- if (!session.retryWatchInterval()){ // ask for the streams again to watch
+ if (session.stats.network_type && (session.stats.network_type !== Connection.type)){
+ var miniInfo = {};
+ miniInfo.con = Connection.type;
+ session.sendMessage({"miniInfo":miniInfo});
+
+ if (!session.retryWatchInterval()){ // ask for the streams again to watch
+ session.ping(); // if no streams requested, let's ping instead.
+ }
+
+ } else { // connection state changed, but doesn't seem like it actually changed...
session.ping(); // if no streams requested, let's ping instead.
}
- } else { // connection state changed, but doesn't seem like it actually changed...
- session.ping(); // if no streams requested, let's ping instead.
+ session.stats.network_type = Connection.type;
}
- session.stats.network_type = Connection.type;
-
} catch(e){warnlog(e);}
}
@@ -5579,10 +5697,12 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
try {
var Connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
if (Connection){
- session.stats.network_type = Connection.type
+ if (Connection.type){
+ session.stats.network_type = Connection.type
+ }
Connection.addEventListener('change', updateConnectionStatus);
}
- } catch (e) {log(e);} // effectiveType is not yet supported by Firefox or Safari; 2021
+ } catch (e) {log(e);} // effectiveType is not yet supported by Firefox or Safari; 2021 */
setInterval(function() {
diff --git a/mixer.html b/mixer.html
index c5173fb..6b94a23 100644
--- a/mixer.html
+++ b/mixer.html
@@ -2399,6 +2399,19 @@
document.getElementById("sources").appendChild(a);
+ var button = document.createElement("button");
+ button.innerHTML = "Publish to Twitch";
+ button.id = "publishTwitch";
+ button.onclick = function(){
+ var URL = window.location.href.replace("/mixer","");
+ URL+="/?scene=0&layout&remote&room="+roomname+additional;
+ URL+="&clean&chroma=000&ssar=landscape&nosettings&prefercurrenttab&selfbrowsersurface=include&displaysurface=browser&np&nopush&publish&whippush=twitch&whippushtoken";
+ var win = window.open( URL ,'targetWindow', 'toolbar=no,location=no,status=no,scaling=no,menubar=no,scrollbars=no,resizable=no,width=1280,height=720');
+ win.focus();
+ win.resizeTo(1280,720);
+ };
+ document.getElementById("sources").appendChild(button);
+
var slots = document.getElementById("col1").children;
for (var i=0;i
+
+
+
+
+
+
+
+
+
+
+
+
+