mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
svc support in whip.html; pixel preview freezing fix; whep improvements; motion recorder
This commit is contained in:
@@ -90,9 +90,9 @@
|
||||
<link itemprop="url" href="./media/vdoNinja_logo_full.png" />
|
||||
</span>
|
||||
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=49"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=6"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/aes.js"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=703"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=706"></script>
|
||||
<input id="zoomSlider" type="range" style="display: none;" />
|
||||
<span id="electronDragZone" style="pointer-events: none; z-index:-10; position:absolute;top:0;left:0;width:100%;height:2%;-webkit-app-region: drag;min-height:20px;"></span>
|
||||
<div id="header">
|
||||
@@ -2638,11 +2638,11 @@
|
||||
// session.record = false; // uncomment to block users from being able to record via vdo.ninja's built in recording function
|
||||
// session.whipServerURL = "wss://whip.vdo.ninja"; // If you deploy your own whip websocket service
|
||||
</script>
|
||||
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=929"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=939"></script>
|
||||
<!--
|
||||
// If you wish to change branding, blank offers a good clean start.
|
||||
<script type="text/javascript" id="main-js" src="./main.js" data-translation="blank"></script>
|
||||
-->
|
||||
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=747"></script>
|
||||
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=751"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
498
lib.js
498
lib.js
@@ -539,6 +539,8 @@ try{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch(e){errorlog(e);}
|
||||
|
||||
if (session.audioCtx && session.audioCtx.sampleRate && (session.audioCtx.sampleRate > 192000)){
|
||||
@@ -6632,6 +6634,14 @@ function sleep(ms = 0) {
|
||||
return new Promise(r => setTimeout(r, ms)); // LOLz!
|
||||
}
|
||||
|
||||
function sleepCancellable(ms = 0) {
|
||||
let resolve;
|
||||
const promise = new Promise(r => {
|
||||
resolve = r;
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
async function changeAvatarImage(ev, ele, set=false){
|
||||
log("changeAvatarImage() triggered");
|
||||
@@ -7363,7 +7373,7 @@ function motionDetection(video, threshold = 15, sensitivity=75){
|
||||
}
|
||||
if (matches >= threshold){
|
||||
log("MOTION DETECTED: "+matches);
|
||||
if (window.obsstudio && window.obsstudio["setCurrentScene"]){
|
||||
if (session.motionSwitch && window.obsstudio && window.obsstudio["setCurrentScene"]){
|
||||
if (!changeSceneEnabled){ // the bit cut scene change is already active.
|
||||
if (session.obsState && session.obsState.details && session.obsState.details.thisScene && session.obsState.details.currentScene){
|
||||
if (session.obsState.details.thisScene !== session.obsState.details.currentScene.name){ // don't trigger it multiple times; makes it hard to prep next scene
|
||||
@@ -7380,6 +7390,16 @@ function motionDetection(video, threshold = 15, sensitivity=75){
|
||||
updateMixer();
|
||||
}
|
||||
}
|
||||
|
||||
if (session.motionRecord){
|
||||
if (!session.motionRecordTimeout){
|
||||
session.motionRecordTimeout = setTimeout(function(){
|
||||
session.motionRecordTimeout = null;
|
||||
},1000);
|
||||
saveVideoFrameToClipboard(video);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9306,11 +9326,9 @@ function processStats(UUID){
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e){};
|
||||
} catch (e){};
|
||||
|
||||
|
||||
if (session.rpcs[UUID].whep){
|
||||
processMeshcastStats(UUID);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -9320,6 +9338,7 @@ function processStats(UUID){
|
||||
var node = session.rpcs[UUID];
|
||||
}
|
||||
|
||||
|
||||
var validTrackIds = [];
|
||||
if (session.rpcs[UUID].streamSrc){
|
||||
session.rpcs[UUID].streamSrc.getTracks().forEach(trk=>{
|
||||
@@ -9327,6 +9346,13 @@ function processStats(UUID){
|
||||
});
|
||||
}
|
||||
|
||||
if (session.rpcs[UUID].whep){
|
||||
processMeshcastStats(UUID);
|
||||
if (!node.getStats){
|
||||
setTimeout(processStats, session.statsInterval, UUID); // no p2p, so lets do WHEP again manually.
|
||||
}
|
||||
}
|
||||
|
||||
if (node.getStats){
|
||||
node.getStats().then(function(stats){
|
||||
if (!(UUID in session.rpcs)){return;}
|
||||
@@ -9853,38 +9879,41 @@ function playoutdelay(UUID){ // applies a delay to all videos
|
||||
var sync_offset = 0.0;
|
||||
|
||||
if (session.rpcs[UUID].stats[tid]._sync_offset){
|
||||
sync_offset = session.rpcs[UUID].stats[tid]._sync_offset;
|
||||
sync_offset = session.rpcs[UUID].stats[tid]._sync_offset || 0;
|
||||
} else {
|
||||
session.rpcs[UUID].stats[tid]._sync_offset = 0;
|
||||
}
|
||||
|
||||
sync_offset += target_buffer;
|
||||
sync_offset -= session.rpcs[UUID].stats[tid].Jitter_Buffer_ms;
|
||||
sync_offset -= session.rpcs[UUID].stats[tid].Jitter_Buffer_ms || 0;
|
||||
|
||||
if (session.includeRTT){
|
||||
sync_offset -= parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2); // I can't be sure what the actual one-way delay is
|
||||
if (session.includeRTT && session.rpcs[UUID].stats['Peer-to-Peer_Connection']){
|
||||
sync_offset -= parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2) || 0; // I can't be sure what the actual one-way delay is
|
||||
}
|
||||
|
||||
if (sync_offset>target_buffer){
|
||||
sync_offset=target_buffer;
|
||||
if (sync_offset > target_buffer){
|
||||
sync_offset = target_buffer || 0;
|
||||
}
|
||||
|
||||
if (sync_offset<0){sync_offset=0;}
|
||||
|
||||
session.rpcs[UUID].stats[tid].Added_Buffer_Delay_ms = sync_offset;
|
||||
session.rpcs[UUID].stats[tid].Added_Buffer_Delay_ms = sync_offset || 0;
|
||||
|
||||
session.rpcs[UUID].stats[tid].Total_Playout_Delay_ms = sync_offset + parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2) + session.rpcs[UUID].stats[tid].Jitter_Buffer_ms;
|
||||
if (session.rpcs[UUID].stats['Peer-to-Peer_Connection']){
|
||||
session.rpcs[UUID].stats[tid].Total_Playout_Delay_ms = (sync_offset + parseInt(session.rpcs[UUID].stats['Peer-to-Peer_Connection'].Round_Trip_Time_ms/2) + session.rpcs[UUID].stats[tid].Jitter_Buffer_ms) || 0;
|
||||
}
|
||||
|
||||
if (session.rpcs[UUID].stats[tid]._type=="audio"){
|
||||
|
||||
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset;
|
||||
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset || 0;
|
||||
|
||||
receiver.playoutDelayHint = parseFloat(sync_offset/1000);
|
||||
|
||||
receiver.playoutDelayHint = parseFloat(sync_offset/1000) || 0;
|
||||
// receiver.jitterBufferDelayhint = parseFloat(sync_offset/1000); // This is deprecated I believe
|
||||
|
||||
if (session.sync!==false){
|
||||
var audio_delay = session.sync || 0; // video is typically showing greater delay than audio.
|
||||
audio_delay += target_buffer - session.rpcs[UUID].stats[tid].Jitter_Buffer_ms;
|
||||
audio_delay += (target_buffer - session.rpcs[UUID].stats[tid].Jitter_Buffer_ms) || 0;
|
||||
|
||||
if ((receiver.track.kind=="audio") && (receiver.track.id in session.rpcs[UUID].inboundAudioPipeline)){
|
||||
if (session.rpcs[UUID].inboundAudioPipeline[receiver.track.id] && session.rpcs[UUID].inboundAudioPipeline[receiver.track.id].delayNode){
|
||||
@@ -9894,14 +9923,14 @@ function playoutdelay(UUID){ // applies a delay to all videos
|
||||
} catch(e){
|
||||
session.rpcs[UUID].inboundAudioPipeline[receiver.track.id].delayNode.delayTime.setValueAtTime(parseFloat(audio_delay/1000.0), session.audioCtx.currentTime+1);
|
||||
}
|
||||
session.rpcs[UUID].stats[tid].Audio_Sync_Delay_ms = audio_delay;
|
||||
session.rpcs[UUID].stats[tid].Audio_Sync_Delay_ms = audio_delay || 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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
|
||||
session.rpcs[UUID].stats[tid]._sync_offset = sync_offset || 0;
|
||||
receiver.playoutDelayHint = parseFloat(sync_offset/1000) || 0; // Chrome seems to somewhat sync audio and video when using the delay
|
||||
// receiver.jitterBufferDelayhint = parseFloat(sync_offset/1000); // This is deprecated I believe
|
||||
}
|
||||
|
||||
@@ -10163,8 +10192,8 @@ function processMeshcastStats(UUID){
|
||||
session.rpcs[UUID].whep.getStats().then(function(stats){
|
||||
if (!(UUID in session.rpcs)){return;}
|
||||
|
||||
if (!session.rpcs[UUID].stats['Meshcast_Connection']){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'] = {};
|
||||
if (!session.rpcs[UUID].stats['WHEP_Connection']){ // meshcast
|
||||
session.rpcs[UUID].stats['WHEP_Connection'] = {};
|
||||
}
|
||||
|
||||
// var qos = false;]
|
||||
@@ -10221,21 +10250,21 @@ function processMeshcastStats(UUID){
|
||||
}
|
||||
} else if (stat.type == "transport"){
|
||||
if ("bytesReceived" in stat) {
|
||||
if ("_bytesReceived" in session.rpcs[UUID].stats['Meshcast_Connection']){
|
||||
if (session.rpcs[UUID].stats['Meshcast_Connection']._timestamp){
|
||||
if ("_bytesReceived" in session.rpcs[UUID].stats['WHEP_Connection']){
|
||||
if (session.rpcs[UUID].stats['WHEP_Connection']._timestamp){
|
||||
if (stat.timestamp){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].total_recv_bitrate_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats['Meshcast_Connection']._bytesReceived)/(stat.timestamp - session.rpcs[UUID].stats['Meshcast_Connection']._timestamp));
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].total_recv_bitrate_kbps = parseInt(8*(stat.bytesReceived - session.rpcs[UUID].stats['WHEP_Connection']._bytesReceived)/(stat.timestamp - session.rpcs[UUID].stats['WHEP_Connection']._timestamp));
|
||||
}
|
||||
}
|
||||
}
|
||||
session.rpcs[UUID].stats['Meshcast_Connection']._bytesReceived = stat.bytesReceived;
|
||||
session.rpcs[UUID].stats['WHEP_Connection']._bytesReceived = stat.bytesReceived;
|
||||
}
|
||||
if ("timestamp" in stat) {
|
||||
session.rpcs[UUID].stats['Meshcast_Connection']._timestamp = stat.timestamp;
|
||||
if (!session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart = stat.timestamp;
|
||||
session.rpcs[UUID].stats['WHEP_Connection']._timestamp = stat.timestamp;
|
||||
if (!session.rpcs[UUID].stats['WHEP_Connection']._timestampStart){
|
||||
session.rpcs[UUID].stats['WHEP_Connection']._timestampStart = stat.timestamp;
|
||||
} else {
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].time_active_minutes = parseInt((stat.timestamp - session.rpcs[UUID].stats['Meshcast_Connection']._timestampStart)/600)/100;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].time_active_minutes = parseInt((stat.timestamp - session.rpcs[UUID].stats['WHEP_Connection']._timestampStart)/600)/100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10424,7 +10453,7 @@ function processMeshcastStats(UUID){
|
||||
|
||||
if (nominatedCandidate){
|
||||
if ("currentRoundTripTime" in nominatedCandidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].Round_Trip_Time_ms = nominatedCandidate.currentRoundTripTime*1000;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].Round_Trip_Time_ms = nominatedCandidate.currentRoundTripTime*1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10432,20 +10461,20 @@ function processMeshcastStats(UUID){
|
||||
if (candidates[nominatedCandidate.remoteCandidateId]){
|
||||
var candidate = candidates[nominatedCandidate.remoteCandidateId];
|
||||
if ("candidateType" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].remote_candidateType = candidate.candidateType;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].remote_candidateType = candidate.candidateType;
|
||||
if (candidate.candidateType === "relay"){
|
||||
if ("relayProtocol" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].remote_relay_protocol = candidate.relayProtocol;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].remote_relay_protocol = candidate.relayProtocol;
|
||||
}
|
||||
if ("ip" in candidate){session.rpcs[UUID].stats['Meshcast_Connection'].remote_relay_IP = candidate.ip;}
|
||||
if ("ip" in candidate){session.rpcs[UUID].stats['WHEP_Connection'].remote_relay_IP = candidate.ip;}
|
||||
} else {
|
||||
try {
|
||||
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP;
|
||||
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol;
|
||||
delete session.rpcs[UUID].stats['WHEP_Connection'].local_relay_IP;
|
||||
delete session.rpcs[UUID].stats['WHEP_Connection'].local_relay_protocol;
|
||||
} catch(e){}
|
||||
}
|
||||
if ("networkType" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].remote_networkType = candidate.networkType;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].remote_networkType = candidate.networkType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10454,22 +10483,22 @@ function processMeshcastStats(UUID){
|
||||
if (candidates[nominatedCandidate.localCandidateId]){
|
||||
var candidate = candidates[nominatedCandidate.localCandidateId];
|
||||
if ("candidateType" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].local_candidateType = candidate.candidateType;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].local_candidateType = candidate.candidateType;
|
||||
if (candidate.candidateType === "relay"){
|
||||
if ("relayProtocol" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol = candidate.relayProtocol;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].local_relay_protocol = candidate.relayProtocol;
|
||||
}
|
||||
if ("ip" in candidate){session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP = candidate.ip;}
|
||||
if ("ip" in candidate){session.rpcs[UUID].stats['WHEP_Connection'].local_relay_IP = candidate.ip;}
|
||||
} else {
|
||||
try {
|
||||
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_IP;
|
||||
delete session.rpcs[UUID].stats['Meshcast_Connection'].local_relay_protocol;
|
||||
delete session.rpcs[UUID].stats['WHEP_Connection'].local_relay_IP;
|
||||
delete session.rpcs[UUID].stats['WHEP_Connection'].local_relay_protocol;
|
||||
} catch(e){}
|
||||
}
|
||||
}
|
||||
|
||||
if ("networkType" in candidate){
|
||||
session.rpcs[UUID].stats['Meshcast_Connection'].local_networkType = candidate.networkType;
|
||||
session.rpcs[UUID].stats['WHEP_Connection'].local_networkType = candidate.networkType;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14433,7 +14462,7 @@ function publishWebcam(btn = false, miconly=false) {
|
||||
}, 200);
|
||||
};
|
||||
|
||||
if ((session.roomid === "") && ((!(session.view)) || (session.view === ""))) {
|
||||
if ((session.roomid === "") && ((!session.view) || (session.view === ""))) {
|
||||
// no room, no viewing, viewing disabled
|
||||
if (session.manual===null){
|
||||
session.manual = true;
|
||||
@@ -15117,7 +15146,7 @@ session.publishIFrame = function(iframeURL){
|
||||
}
|
||||
|
||||
if (session.roomid!==false){
|
||||
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
|
||||
if ((session.roomid==="") && ((!session.view) || (session.view===""))){
|
||||
|
||||
} else {
|
||||
log("ROOMID EANBLED");
|
||||
@@ -15159,7 +15188,7 @@ session.publishIFrame = function(iframeURL){
|
||||
updateMixer();
|
||||
} else if (session.roomid!==false){
|
||||
if (session.roomid===""){
|
||||
if (!(session.view) || (session.view==="")){
|
||||
if (!session.view || (session.view==="")){
|
||||
session.windowed = true;
|
||||
container.classList.add("vidcon");
|
||||
|
||||
@@ -16597,13 +16626,18 @@ function saveVideoFrameToClipboard(videoElement, e=false) {
|
||||
|
||||
canvas.toBlob(function(blob) {
|
||||
var link = document.createElement("a");
|
||||
link.download = (videoElement.id||"video")+"_"+parseInt(performance.now())+".png";
|
||||
if (e){
|
||||
link.download = (videoElement.id||"video")+"_"+parseInt(performance.now())+".png";
|
||||
} else {
|
||||
link.download = (videoElement.id||"video")+"_"+parseInt(Date.now())+".png";
|
||||
}
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
}, 'image/png');
|
||||
|
||||
popupMessage(e, "Saving current frame to disk");
|
||||
if (e){
|
||||
popupMessage(e, "Saving current frame to disk");
|
||||
}
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
}
|
||||
@@ -21835,16 +21869,6 @@ async function grabScreen(quality = 0, audio = true, videoOnEnd = false) {
|
||||
}
|
||||
|
||||
function toggleBufferSettings(UUID){
|
||||
//bufferSliderValue
|
||||
/* try {
|
||||
session.rpcs[taskItemInContext.dataset.UUID].buffer = parseInt(inputElement.value);
|
||||
inputElement.title = session.rpcs[taskItemInContext.dataset.UUID].buffer + " ms";
|
||||
getById("bufferSliderValue").innerText = session.rpcs[taskItemInContext.dataset.UUID].buffer + " ms";
|
||||
playoutdelay(taskItemInContext.dataset.UUID); // trigger
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
*/
|
||||
|
||||
|
||||
toggle(getById('bufferSettings'));
|
||||
if (getById('bufferSettings').style.display=="none"){
|
||||
@@ -21879,6 +21903,19 @@ function toggleBufferSettings(UUID){
|
||||
});
|
||||
playoutdelay(UUID); // trigger
|
||||
};
|
||||
ele.onkeyup = function(e){
|
||||
if (e.key === "Enter") {
|
||||
session.rpcs[UUID].buffer = parseInt(e.target.value);
|
||||
//getById("bufferSliderValue").innerText = session.rpcs[UUID].buffer + " ms";
|
||||
getById('bufferSettings').querySelectorAll("input").forEach(ele2=>{
|
||||
if (ele2!==e.target){
|
||||
ele2.value = parseInt(e.target.value);
|
||||
}
|
||||
ele2.title = parseInt(e.target.value) + " ms";
|
||||
});
|
||||
playoutdelay(UUID); // trigger
|
||||
}
|
||||
};
|
||||
ele.oninput = function(e){
|
||||
getById('bufferSettings').querySelectorAll("input").forEach(ele2=>{
|
||||
if (ele2!==e.target){
|
||||
@@ -22126,6 +22163,30 @@ function checkBasicStreamsExist(){
|
||||
resetupAudioOut(session.videoElement, true);
|
||||
}, { once: true });
|
||||
|
||||
session.videoElement.onpause = (event) => { // prevent things from pausing; human or other
|
||||
if (!((event.ctrlKey) || (event.metaKey) )){
|
||||
log("Video paused; auto playing");
|
||||
event.currentTarget.play().then(_ => {
|
||||
log("playing 10");
|
||||
}).catch(warnlog);
|
||||
}
|
||||
};
|
||||
|
||||
session.videoElement.addEventListener('error', function(event) {
|
||||
errorlog("video error");
|
||||
errorlog(event);
|
||||
setTimeout(function(){
|
||||
if (session.videoElement){
|
||||
log("Trying to re-load local preview, as it may have crashed");
|
||||
session.videoElement.load();
|
||||
}
|
||||
},1200);
|
||||
});
|
||||
|
||||
//session.videoElement.addEventListener('loadedmetadata', function(event) {
|
||||
// log("loadedmetadata");
|
||||
// log(event);
|
||||
//});
|
||||
}
|
||||
session.videoElement.srcObject = outboundAudioPipeline();
|
||||
toggleMute(true);
|
||||
@@ -22621,9 +22682,9 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
|
||||
applyMirror(session.mirrorExclude);
|
||||
|
||||
session.videoElement.play().then(() => {
|
||||
log("start play doublecheck");
|
||||
log("play doublecheck completed");
|
||||
});
|
||||
|
||||
|
||||
if ((eleName == "previewWebcam") && document.getElementById("previewWebcam")){
|
||||
if (session.autostart) {
|
||||
publishWebcam();
|
||||
@@ -22686,6 +22747,7 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
|
||||
if (getUserMediaRequestID !== gumid) { // new camera selected in this time.
|
||||
return;
|
||||
}
|
||||
|
||||
makeImages(true);
|
||||
|
||||
if (getById("popupSelector_constraints_loading")) {
|
||||
@@ -23040,7 +23102,12 @@ async function grabAudio(selector = "#audioSource", trackid = null, override = f
|
||||
log("TRACK EXCLUDED:" + trackid);
|
||||
|
||||
try {
|
||||
var audioSelect = document.querySelector(selector).querySelectorAll("input");
|
||||
var baseTest = document.querySelector(selector);
|
||||
if (!baseTest){
|
||||
errorlog("No audio source menu");
|
||||
return;
|
||||
}
|
||||
var audioSelect = baseTest.querySelectorAll("input");
|
||||
var audioExcludeList = [];
|
||||
for (var i = 0; i < audioSelect.length; i++) {
|
||||
try {
|
||||
@@ -24238,15 +24305,6 @@ session.publishStream = function(v){ // stream is used to generated an SDP
|
||||
|
||||
}
|
||||
|
||||
v.onpause = (event) => { // prevent things from pausing; human or other
|
||||
if (!((event.ctrlKey) || (event.metaKey) )){
|
||||
log("Video paused; auto playing");
|
||||
event.currentTarget.play().then(_ => {
|
||||
log("playing 10");
|
||||
}).catch(warnlog);
|
||||
}
|
||||
};
|
||||
|
||||
v.addEventListener('click', function(e) {
|
||||
log("click");
|
||||
try {
|
||||
@@ -24424,6 +24482,14 @@ session.postPublish = async function(){
|
||||
whepOut();
|
||||
}
|
||||
|
||||
if (session.motionRecord && session.videoElement && !session.motionDetectionInterval){
|
||||
session.motionDetectionInterval = setTimeout(function(){
|
||||
setInterval(function(){
|
||||
motionDetection(session.videoElement, session.motionRecord);
|
||||
},400);
|
||||
},2000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24575,7 +24641,7 @@ async function publishScreen2(constraints, audioList=[], audio=true, overrideFra
|
||||
|
||||
// OR, jump right in, and let user change from there
|
||||
if (session.roomid!==false){
|
||||
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
|
||||
if ((session.roomid==="") && ((!session.view) || (session.view===""))){
|
||||
|
||||
} else {
|
||||
getById("head3").classList.add('hidden');
|
||||
@@ -24673,7 +24739,7 @@ async function publishScreen2(constraints, audioList=[], audio=true, overrideFra
|
||||
setTimeout(function(){updateMixer();},1);
|
||||
} else if (session.roomid!==false){
|
||||
if (session.roomid===""){
|
||||
if (!(session.view) || (session.view==="")){
|
||||
if (!session.view || (session.view==="")){
|
||||
|
||||
getById("mutespeakerbutton").classList.add("hidden");
|
||||
|
||||
@@ -25108,7 +25174,7 @@ session.hostFile = function(ele, event){ // webcam stream is used to generated a
|
||||
}
|
||||
|
||||
if (session.roomid!==false){
|
||||
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
|
||||
if ((session.roomid==="") && ((!session.view) || (session.view===""))){
|
||||
|
||||
} else {
|
||||
log("ROOMID EANBLED");
|
||||
@@ -25279,7 +25345,7 @@ session.publishFile = function(ele, event){ // webcam stream is used to generate
|
||||
|
||||
|
||||
if (session.roomid!==false){
|
||||
if ((session.roomid==="") && ((!(session.view)) || (session.view===""))){
|
||||
if ((session.roomid==="") && ((!session.view) || (session.view===""))){
|
||||
|
||||
} else {
|
||||
log("ROOMID EANBLED");
|
||||
@@ -25375,7 +25441,7 @@ session.publishFile = function(ele, event){ // webcam stream is used to generate
|
||||
|
||||
} else if (session.roomid!==false){
|
||||
if (session.roomid===""){
|
||||
if (!(session.view) || (session.view==="")){
|
||||
if (!session.view || (session.view==="")){
|
||||
if (session.fullscreen){
|
||||
session.windowed = false;
|
||||
} else {
|
||||
@@ -29610,8 +29676,8 @@ function setupWebcamSelection(miconly=false) {
|
||||
activatedPreview = false;
|
||||
if (session.quality !== false) {
|
||||
grabVideo(session.quality);
|
||||
} else {
|
||||
session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
|
||||
} else if (document.getElementById("webcamquality")){
|
||||
session.quality_wb = parseInt(document.getElementById("webcamquality").elements.namedItem("resolution").value);
|
||||
grabVideo(session.quality_wb);
|
||||
}
|
||||
};
|
||||
@@ -29732,15 +29798,13 @@ function setupWebcamSelection(miconly=false) {
|
||||
if (session.autostart) {
|
||||
publishWebcam(); // no need to mirror as there is no video...
|
||||
return;
|
||||
} else {
|
||||
if (document.getElementById("gowebcam")) {
|
||||
document.getElementById("gowebcam").dataset.ready = "true";
|
||||
if (document.getElementById("gowebcam").dataset.audioready == "true"){
|
||||
document.getElementById("gowebcam").disabled = false;
|
||||
miniTranslate(document.getElementById("gowebcam"),"start");
|
||||
document.getElementById("gowebcam").focus();
|
||||
//document.getElementById("gowebcam").innerHTML = getTranslation("start");
|
||||
}
|
||||
} else if (document.getElementById("gowebcam")) {
|
||||
document.getElementById("gowebcam").dataset.ready = "true";
|
||||
if (document.getElementById("gowebcam").dataset.audioready == "true"){
|
||||
document.getElementById("gowebcam").disabled = false;
|
||||
miniTranslate(document.getElementById("gowebcam"),"start");
|
||||
document.getElementById("gowebcam").focus();
|
||||
//document.getElementById("gowebcam").innerHTML = getTranslation("start");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -29748,7 +29812,7 @@ function setupWebcamSelection(miconly=false) {
|
||||
activatedPreview = false;
|
||||
if (session.quality !== false) {
|
||||
grabVideo(session.quality);
|
||||
} else {
|
||||
} else if (document.getElementById("webcamquality")){
|
||||
session.quality_wb = parseInt(getById("webcamquality").elements.namedItem("resolution").value);
|
||||
grabVideo(session.quality_wb);
|
||||
}
|
||||
@@ -33824,10 +33888,10 @@ function updateIncomingVideoElement(UUID, video=true, audio=true){
|
||||
}
|
||||
});
|
||||
|
||||
if (session.motionSwitch && !session.rpcs[UUID].motionDetectionInterval){
|
||||
if ((session.motionSwitch || session.motionRecord) && !session.rpcs[UUID].motionDetectionInterval){
|
||||
session.rpcs[UUID].motionDetectionInterval = setTimeout(function(){
|
||||
setInterval(function(){
|
||||
motionDetection(session.rpcs[UUID].videoElement, session.motionSwitch);
|
||||
motionDetection(session.rpcs[UUID].videoElement, session.motionSwitch || session.motionRecord);
|
||||
},400);
|
||||
},2000);
|
||||
}
|
||||
@@ -36048,7 +36112,37 @@ function configureWhipOutSDP(description){ // THIS IS FOR WHIP-OUTPUT; it has
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
const scalabilityModes = [
|
||||
'L1T1',
|
||||
'L1T2',
|
||||
'L1T3',
|
||||
'L2T1',
|
||||
'L2T2',
|
||||
'L2T3',
|
||||
'L3T1',
|
||||
'L3T2',
|
||||
'L3T3',
|
||||
'L2T1h',
|
||||
'L2T2h',
|
||||
'L2T3h',
|
||||
'S2T1',
|
||||
'S2T2',
|
||||
'S2T3',
|
||||
'S2T1h',
|
||||
'S2T2h',
|
||||
'S2T3h',
|
||||
'S3T1',
|
||||
'S3T2',
|
||||
'S3T3',
|
||||
'S3T1h',
|
||||
'S3T2h',
|
||||
'S3T3h',
|
||||
'L2T2_KEY',
|
||||
'L2T3_KEY',
|
||||
'L3T2_KEY',
|
||||
'L3T3_KEY'
|
||||
];
|
||||
|
||||
|
||||
function whipOut(){
|
||||
log("whipOut");
|
||||
@@ -36079,59 +36173,64 @@ function whipOut(){
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var tracks = false;
|
||||
if (session.videoElement && session.videoElement.srcObject){
|
||||
tracks = session.videoElement.srcObject.getAudioTracks();
|
||||
}
|
||||
var streamsource = false;
|
||||
if (!tracks || !tracks.length){
|
||||
var audioCtx = new AudioContext();
|
||||
warnlog("No audio track; using a webaudio node instead");
|
||||
var destination = audioCtx.createMediaStreamDestination();
|
||||
streamsource = destination.stream;
|
||||
destination.stream.getAudioTracks().forEach(trk=>{
|
||||
tracks = trk;
|
||||
});
|
||||
} else {
|
||||
tracks = tracks[0];
|
||||
streamsource = session.videoElement.srcObject;
|
||||
}
|
||||
|
||||
if (session.audioContentHint && (tracks.kind === "audio")){
|
||||
try {
|
||||
tracks.contentHint = session.audioContentHint;
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (tracks){
|
||||
try {
|
||||
session.whipOut.addTransceiver(tracks, {
|
||||
streams: [ streamsource ],
|
||||
direction: 'sendonly'
|
||||
});
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
session.whipOut.addTrack(tracks);
|
||||
}
|
||||
}
|
||||
//}
|
||||
///////
|
||||
try {
|
||||
/// audio tracks
|
||||
|
||||
//// video tracks
|
||||
var tracks = false;
|
||||
if (session.videoElement && session.videoElement.srcObject){
|
||||
tracks = session.videoElement.srcObject.getVideoTracks();
|
||||
}
|
||||
////
|
||||
var tracks = false;
|
||||
if (session.videoElement && session.videoElement.srcObject){
|
||||
tracks = session.videoElement.srcObject.getAudioTracks();
|
||||
}
|
||||
var streamsource = false;
|
||||
if (!tracks || !tracks.length){
|
||||
var audioCtx = new AudioContext();
|
||||
warnlog("No audio track; using a webaudio node instead");
|
||||
var destination = audioCtx.createMediaStreamDestination();
|
||||
streamsource = destination.stream;
|
||||
destination.stream.getAudioTracks().forEach(trk=>{
|
||||
tracks = trk;
|
||||
});
|
||||
} else {
|
||||
tracks = tracks[0];
|
||||
streamsource = session.videoElement.srcObject;
|
||||
}
|
||||
|
||||
if (session.audioContentHint && (tracks.kind === "audio")){
|
||||
try {
|
||||
tracks.contentHint = session.audioContentHint;
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (tracks){
|
||||
try {
|
||||
session.whipOut.addTransceiver(tracks, {
|
||||
streams: [ streamsource ],
|
||||
direction: 'sendonly'
|
||||
});
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
session.whipOut.addTrack(tracks);
|
||||
}
|
||||
}
|
||||
} catch(e){errorlog(e);}
|
||||
|
||||
try {
|
||||
//// video tracks ///
|
||||
|
||||
var tracks = false;
|
||||
if (session.videoElement && session.videoElement.srcObject){
|
||||
tracks = session.videoElement.srcObject.getVideoTracks();
|
||||
}
|
||||
|
||||
//if (!tracks || !tracks.length){
|
||||
// tracks = getMeshcastCanvasTrack();
|
||||
//} else {
|
||||
// tracks = tracks[0];
|
||||
//}
|
||||
if (tracks && tracks.length){
|
||||
|
||||
//if (!tracks || !tracks.length){
|
||||
// tracks = getMeshcastCanvasTrack();
|
||||
//} else {
|
||||
tracks = tracks[0];
|
||||
//}
|
||||
tracks = tracks[0];
|
||||
|
||||
if (session.screenShareState && session.screenshareContentHint && (tracks.kind === "video")){
|
||||
try {
|
||||
@@ -36146,18 +36245,56 @@ function whipOut(){
|
||||
errorlog(e);
|
||||
}
|
||||
}
|
||||
if (tracks){
|
||||
try {
|
||||
session.whipOut.addTransceiver(tracks, {
|
||||
|
||||
try {
|
||||
var transceiverSetup = {
|
||||
streams: [ session.videoElement.srcObject ],
|
||||
direction: 'sendonly'
|
||||
});
|
||||
};
|
||||
if (session.scalabilityMode){
|
||||
|
||||
// might be a good time to validate the scalabilityMode at this point; check to see if requested codec is available,etc.
|
||||
try {
|
||||
transceiverSetup.sendEncodings = [{scalabilityMode: session.scalabilityMode}];
|
||||
} catch(e){
|
||||
errorlog("Invalid scalability mode provided");
|
||||
}
|
||||
|
||||
if (session.whipOutCodec && session.whipOutCodec.length){
|
||||
var svcCodecPref = [];
|
||||
for (var i = 0;i<session.whipOutCodec.length;i++){
|
||||
svcCodecPref.push({mimeType:"video/"+session.whipOutCodec[i].toUpperCase(), clockRate: 90000}); // is it ever not 90000?
|
||||
}
|
||||
|
||||
try{
|
||||
session.whipOut.addTransceiver(tracks, transceiverSetup);
|
||||
let transceiver = session.whipOut.getTransceivers().find(t => t.sender && t.sender.track === tracks);
|
||||
transceiver.setCodecPreferences(svcCodecPref);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
warnUser("Could not configure WHIP output based on specified video encoder/SVC settings.", 5000);
|
||||
var transceiverSetup = {
|
||||
streams: [ session.videoElement.srcObject ],
|
||||
direction: 'sendonly'
|
||||
};
|
||||
session.whipOut.addTransceiver(tracks, transceiverSetup);
|
||||
}
|
||||
} else {
|
||||
session.whipOut.addTransceiver(tracks, transceiverSetup);
|
||||
}
|
||||
} else {
|
||||
session.whipOut.addTransceiver(tracks, transceiverSetup);
|
||||
}
|
||||
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
try {
|
||||
session.whipOut.addTrack(tracks);
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
session.whipOut.addTrack(tracks);
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
session.whipOut.onnegotiationneeded = publish; // bug: https://groups.google.com/forum/#!topic/discuss-webrtc/3-TmyjQ2SeE
|
||||
|
||||
@@ -36174,7 +36311,6 @@ function whipOut(){
|
||||
}
|
||||
var publishing = false;
|
||||
|
||||
|
||||
function publish(event){
|
||||
if (publishing){
|
||||
log(event);
|
||||
@@ -36423,7 +36559,7 @@ function whipClient(){ // publish to whip.vdo.ninja with obs, to use. experimen
|
||||
var data = JSON.parse(event.data);
|
||||
|
||||
if ("sdp" in data){
|
||||
var resp = await processWHIP(data);
|
||||
var resp = await processWhipIn(data);
|
||||
if (resp){
|
||||
var ret = {};
|
||||
var get = data.get;
|
||||
@@ -36446,7 +36582,7 @@ function whipClient(){ // publish to whip.vdo.ninja with obs, to use. experimen
|
||||
connect();
|
||||
}
|
||||
|
||||
async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
|
||||
async function processWhipIn(data){ // LISTEN FOR REMOTE WHIP (from OBS?)
|
||||
var msg = {};
|
||||
msg.description = {};
|
||||
msg.description.type = "offer";
|
||||
@@ -36515,8 +36651,11 @@ async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
|
||||
|
||||
await promise;
|
||||
session.rpcs[msg.UUID].whipCallback = null;
|
||||
|
||||
sdpAnswer = session.rpcs[msg.UUID].localDescription.sdp;
|
||||
|
||||
// sdpAnswer = processSDPFromServer(sdpAnswer); // the sending server doesn't give a damn if we're in stereo or not.
|
||||
|
||||
var insertIce = "";
|
||||
iceBundle.forEach(ice=>{
|
||||
if (ice.candidate){
|
||||
@@ -36525,9 +36664,6 @@ async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
|
||||
});
|
||||
sdpAnswer = sdpAnswer.replace("a=ice-ufrag", insertIce+"a=ice-ufrag");
|
||||
|
||||
//if (session.stereo){
|
||||
// sdpAnswer = CodecsHandler.setOpusAttributes(sdpAnswer, {stereo:1}, true);
|
||||
//}
|
||||
|
||||
if (sdpAnswer.includes("sendrecv")){
|
||||
errorlog("Should not include sendrecv");
|
||||
@@ -36541,6 +36677,23 @@ async function processWHIP(data){ // LISTEN FOR REMOTE WHIP
|
||||
return sdpAnswer; // return SDP answer for the remote WHIP request
|
||||
}
|
||||
|
||||
function processSDPFromServer(sdp){ // not the description package; just the sdp
|
||||
try {
|
||||
if (session.mono && Firefox){ // chrome defaults to mono already, but we can force Firefox mono if needed
|
||||
sdp = CodecsHandler.setOpusAttributes(sdp, {stereo:0}, true);
|
||||
} else if (Firefox){
|
||||
// Let Firefox be Firefox.. else it might break the server. mediamtx err 400 response if mono
|
||||
} else if (session.stereo && (session.stereo==4)){ // pro audio only when viewing streams
|
||||
sdp = CodecsHandler.setOpusAttributes(sdp, {stereo:2}, true);
|
||||
} else if (session.stereo && !session.mono && (session.stereo!=3)){
|
||||
sdp = CodecsHandler.setOpusAttributes(sdp, {stereo:1}, true);
|
||||
}
|
||||
} catch(e){
|
||||
errorlog(e);
|
||||
}
|
||||
return sdp;
|
||||
}
|
||||
|
||||
async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLAY WHEP
|
||||
var candidates = [];
|
||||
var responseLocation = false;
|
||||
@@ -36574,11 +36727,12 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
|
||||
session.rpcs[UUID].lockedAudioBitrate = false;
|
||||
session.rpcs[UUID].manualBandwidth = false; // doesn't do anything, except maybe help keep track of pause/play states
|
||||
session.rpcs[UUID].motionDetectionInterval = false;
|
||||
session.rpcs[UUID].buffer = false;
|
||||
}
|
||||
var config = {...session.configuration};
|
||||
|
||||
if (whepInput.includes("cloudflare")){
|
||||
config.iceTransportPolicy = "relay"; // oof. Doesn't work with Cloudflare without this?
|
||||
config.iceTransportPolicy = "relay"; // oof. Doesn't work with Cloudflare without this? REVISIT
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -36642,7 +36796,11 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
|
||||
|
||||
session.rpcs[UUID].whep.onicecandidate = function(event){ //event
|
||||
if (event.candidate==null){
|
||||
log("END OF ICE CANDIDATES");
|
||||
warnlog("END OF ICE CANDIDATES");
|
||||
if (session.rpcs[UUID].whep.iceCompletedCallback){
|
||||
console.log("Fully collected all ice candidates in time. woohoo!");
|
||||
session.rpcs[UUID].whep.iceCompletedCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
//log(event.candidate);
|
||||
@@ -36665,11 +36823,26 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
|
||||
|
||||
try {
|
||||
session.rpcs[UUID].whep.createOffer().then(async function(offer){
|
||||
offer.sdp = processSDPFromServer(offer.sdp);
|
||||
//offer.sdp = CodecsHandler.setOpusAttributes(offer.sdp, {'stereo': 1});
|
||||
return session.rpcs[UUID].whep.setLocalDescription(offer);
|
||||
}).then(async function() {
|
||||
//log(session.rpcs[UUID].whep.localDescription);
|
||||
await sleep(6000);
|
||||
|
||||
console.log("Waiting up to 2-seconds for ice candidates to collect. At least 300ms recommended; at most 30-seconds.");
|
||||
try {
|
||||
let startTime = Date.now();
|
||||
const { promise, resolve } = sleepCancellable(2000); // I want to give the ICE / STUN / TURN time to collect.
|
||||
session.rpcs[UUID].whep.iceCompletedCallback = resolve; // Can complete earlier if possible.
|
||||
await promise; // pausing for a moment
|
||||
console.log("Finished waiting for ice candidates. Waited " + ((Date.now() - startTime)/1000)+ "-seconds");
|
||||
delete session.rpcs[UUID].whep.iceCompletedCallback;
|
||||
} catch(e){errorlog(e);}
|
||||
|
||||
var sdp = session.rpcs[UUID].whep.localDescription.sdp;
|
||||
sdp = processSDPFromServer(sdp);
|
||||
|
||||
//description.sdp = processSDPFromServer(description.sdp); // cause server to not send
|
||||
if (sdp.includes("sendrecv")){
|
||||
errorlog("Should not include sendrecv");
|
||||
sdp = sdp.replace("a=sendrecv","a=recvonly");
|
||||
@@ -36697,8 +36870,11 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
|
||||
jsep.type = "answer";
|
||||
|
||||
warnlog("Processing answer:");
|
||||
|
||||
jsep.sdp = processSDPFromServer(jsep.sdp); // setup stereo/mono
|
||||
|
||||
session.rpcs[UUID].whep.setRemoteDescription(jsep).then(function(){
|
||||
|
||||
warnlog("SHOULD BE CONNECTED?");
|
||||
/* var content = "";
|
||||
while (candidates.length){
|
||||
@@ -36730,7 +36906,7 @@ async function whepIn(whepInput=false, whepInputToken=false, UUID=false){ // PLA
|
||||
}
|
||||
} else {
|
||||
xhttp.open("POST", whepInput, true);
|
||||
}
|
||||
}
|
||||
xhttp.setRequestHeader('Content-Type', 'application/'+type);
|
||||
|
||||
if (whepInputToken){
|
||||
@@ -38156,10 +38332,14 @@ function getReceivers2(UUID){
|
||||
ssTracks = session.rpcs[UUID].screenIndexes;
|
||||
}
|
||||
|
||||
var receivers = session.rpcs[UUID].getReceivers();
|
||||
if (session.rpcs[UUID].getReceivers){
|
||||
var receivers = session.rpcs[UUID].getReceivers();
|
||||
} else {
|
||||
var receivers = [];
|
||||
}
|
||||
|
||||
try {
|
||||
if (session.rpcs[UUID].whep){ // used to be "mc", not "whep"
|
||||
if (session.rpcs[UUID].whep && session.rpcs[UUID].whep.getReceivers){ // used to be "mc", not "whep"
|
||||
try{
|
||||
receivers = receivers.concat(session.rpcs[UUID].whep.getReceivers());
|
||||
} catch(e){errorlog(e);}
|
||||
|
||||
37
main.js
37
main.js
@@ -408,7 +408,17 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (urlParams.has('svc') || urlParams.has('scalabilitymode')) { // Experiment with this feature here: https://webrtc.github.io/samples/src/content/extensions/svc/
|
||||
session.scalabilityMode = urlParams.get('svc') || urlParams.get('scalabilitymode') || "L1T3";
|
||||
if (!scalabilityModes.includes(session.scalabilityMode)){
|
||||
scalabilityModes.forEach(sca=>{
|
||||
if (sca.toLowerCase() === session.scalabilityMode.toLowerCase()){
|
||||
session.scalabilityMode = sca;
|
||||
log("Corrected the capitalization of the SVC value. just in case thats important");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (urlParams.get('whepplay')){
|
||||
@@ -748,6 +758,11 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
session.motionSwitch = parseInt(urlParams.get('motionswitch')) || parseInt(urlParams.get('motiondetection')) || 15; // threshold of motion needed to trigger
|
||||
}
|
||||
|
||||
if (urlParams.has('motionrecord') || urlParams.has('recordmotion')) { // switch OBS to this scene when there is motion, and "solo view" this video in the VDO.Ninja auto-mixer, if used
|
||||
session.motionRecord = parseInt(urlParams.get('motionrecord')) || parseInt(urlParams.get('recordmotion')) || 15; // threshold of motion needed to trigger
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (urlParams.has('locked')) {
|
||||
session.locked = urlParams.get('locked');
|
||||
@@ -1831,6 +1846,10 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
session.stereo = 4;
|
||||
} else if (session.stereo === "multi") {
|
||||
session.stereo = 4;
|
||||
} else if (session.stereo === "8") {
|
||||
session.stereo = 8;
|
||||
} else if (session.stereo === "surround") {
|
||||
session.stereo = 8;
|
||||
} else if (session.stereo === "2") {
|
||||
session.stereo = 2;
|
||||
} else if (session.stereo === "6") {
|
||||
@@ -2144,7 +2163,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
fakeElement.id = parseInt(Math.random() * 10000000000);
|
||||
session.fakeFeeds.push(fakeElement);
|
||||
}
|
||||
if (session.view!==false || session.scene!==false){
|
||||
if ((session.view!==false) || (session.scene!==false) || session.whepInput){
|
||||
setTimeout(function(){updateMixer();},1000);
|
||||
}
|
||||
}
|
||||
@@ -4435,7 +4454,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
getById("saveRoom").style.display = "none"; // don't let the user save the room if in OBS
|
||||
}
|
||||
if (session.cleanViewer){
|
||||
if (session.view && !session.director && session.permaid===false){
|
||||
if ((session.view || session.whepInput) && !session.director && session.permaid===false){
|
||||
session.cleanOutput = true;
|
||||
}
|
||||
}
|
||||
@@ -4708,7 +4727,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
getById("head3").classList.remove('hidden');
|
||||
getById("head3a").classList.remove('hidden');
|
||||
}
|
||||
} else if ((window.obsstudio) && (session.permaid === false) && (session.director === false) && (session.view) &&(session.roomid.length>0)) { // we already know roomid !== false
|
||||
} else if (window.obsstudio && (session.permaid === false) && (session.director === false) && (session.view || session.whepInput) && (session.roomid.length>0)) { // we already know roomid !== false
|
||||
updateURL("scene", true, false); // we also know it's not a scene, but we will assume it is in this specific case.
|
||||
}
|
||||
|
||||
@@ -4753,7 +4772,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
} else if (session.chatbutton === false) {
|
||||
getById("chatbutton").classList.add("hidden");
|
||||
}
|
||||
} else if (session.view && (session.permaid === false)) {
|
||||
} else if ((session.view || session.whepInput) && (session.permaid === false)) {
|
||||
//if (!session.activeSpeaker){
|
||||
session.audioMeterGuest = false;
|
||||
//}
|
||||
@@ -4814,7 +4833,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
getById("header").style.opacity = 0;
|
||||
}
|
||||
|
||||
if (session.view) {
|
||||
if (session.view || session.whepInput) {
|
||||
getById("main").className = "";
|
||||
getById("credits").style.display = 'none';
|
||||
try {
|
||||
@@ -4839,7 +4858,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
session.waitImage = urlParams.get('waitimage') || false;
|
||||
}
|
||||
|
||||
if (((session.view) && (session.roomid === false)) || (session.waitImage && (session.scene!==false))) {
|
||||
if (((session.view || session.whepInput) && (session.roomid === false)) || (session.waitImage && (session.scene!==false))) {
|
||||
|
||||
getById("container-4").className = 'column columnfade';
|
||||
getById("container-3").className = 'column columnfade';
|
||||
@@ -4870,7 +4889,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
session.waitImageTimeoutObject = setTimeout(function() {
|
||||
session.waitImageTimeoutObject = true;
|
||||
try {
|
||||
if ((session.view)) {
|
||||
if ((session.view || session.whepInput)) {
|
||||
if (document.getElementById("mainmenu")) {
|
||||
if (session.waitImage){
|
||||
getById("mainmenu").innerHTML += '<img id="retryimage"/>';
|
||||
@@ -6077,7 +6096,7 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
|
||||
// Warns user about network going down
|
||||
window.addEventListener("offline", function (e) {
|
||||
warnlog("connection lost");
|
||||
if ((session.view) && (session.permaid === false)) {
|
||||
if ((session.view || session.whepInput) && (session.permaid === false)) {
|
||||
log("VDO.Ninja has no network connectivity and can't work properly." );
|
||||
} else if (session.scene !== false) {
|
||||
log("VDO.Ninja has no network connectivity and can't work properly." );
|
||||
|
||||
54
thirdparty/CodecsHandler.js
vendored
54
thirdparty/CodecsHandler.js
vendored
@@ -533,14 +533,52 @@ var CodecsHandler = (function() {
|
||||
}
|
||||
|
||||
if (typeof params.stereo != 'undefined'){
|
||||
if (params.stereo==0){
|
||||
appendOpusNext += ';stereo=0;sprop-stereo=0'; // defaults to 0
|
||||
} else if (params.stereo==1){
|
||||
appendOpusNext += ';stereo=1;sprop-stereo=1'; // defaults to 0
|
||||
} else if (params.stereo==2){
|
||||
if (params.stereo==0){ // &stereo=0
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=0", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=0", "");
|
||||
|
||||
if (sdpLines[opusFmtpLineIndex].split(";stereo=0").length==1){
|
||||
appendOpusNext += ';stereo=0'; // defaults to 0
|
||||
}
|
||||
if (sdpLines[opusFmtpLineIndex].split(";sprop-stereo=0").length==1){
|
||||
appendOpusNext += ';sprop-stereo=0'; // defaults to 0
|
||||
}
|
||||
} else if (params.stereo==1){ // &stereo=1
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=0", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=0", "");
|
||||
|
||||
if (sdpLines[opusFmtpLineIndex].split(";stereo=1").length==1){
|
||||
appendOpusNext += ';stereo=1'; // defaults to 0
|
||||
}
|
||||
if (sdpLines[opusFmtpLineIndex].split(";sprop-stereo=1").length==1){
|
||||
appendOpusNext += ';sprop-stereo=1'; // defaults to 0
|
||||
}
|
||||
} else if (params.stereo==2){ // &stereo=4
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=0", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=0", "");
|
||||
|
||||
sdpLines[opusIndex] = sdpLines[opusIndex].replace("opus/48000/2", "multiopus/48000/6");
|
||||
appendOpusNext += ';channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'; // Multi-channel 5.1 audio
|
||||
if (sdpLines[opusFmtpLineIndex].split(";channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2").length==1){
|
||||
appendOpusNext += ';channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2'; // Multi-channel 5.1 audio
|
||||
}
|
||||
} else if (params.stereo==3){ // &stereo=8
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=1", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";stereo=0", "");
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].replace(";sprop-stereo=0", "");
|
||||
|
||||
sdpLines[opusIndex] = sdpLines[opusIndex].replace("opus/48000/2", "multiopus/48000/8");
|
||||
if (sdpLines[opusFmtpLineIndex].split(";channel_mapping=0,6,1,2,3,4,5,7;num_streams=5;coupled_streams=3").length==1){
|
||||
appendOpusNext += ';channel_mapping=0,6,1,2,3,4,5,7;num_streams=5;coupled_streams=3'; // Multi-channel 5.1 audio
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (typeof params.maxaveragebitrate != 'undefined') {
|
||||
@@ -577,10 +615,6 @@ var CodecsHandler = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
if (debug){
|
||||
console.log();
|
||||
}
|
||||
|
||||
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext);
|
||||
|
||||
if (debug){
|
||||
|
||||
233
whip.html
233
whip.html
@@ -33,6 +33,8 @@
|
||||
border:0;
|
||||
margin:0;
|
||||
outline:0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
button.glyphicon-button:focus,
|
||||
@@ -59,6 +61,7 @@
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
min-width: 50px;
|
||||
}
|
||||
.details{
|
||||
font-size: 14px;
|
||||
@@ -74,6 +77,7 @@
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
min-width: 50px;
|
||||
}
|
||||
#header{
|
||||
width:100%;
|
||||
@@ -104,7 +108,7 @@
|
||||
}
|
||||
|
||||
.container{
|
||||
font-size: min(14px, 2vh);
|
||||
font-size: 16px;
|
||||
align-self:center;
|
||||
max-width: 100%;
|
||||
width: 720px;
|
||||
@@ -164,7 +168,7 @@
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
margin: 4em;
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1030px) {
|
||||
@@ -186,50 +190,14 @@
|
||||
}
|
||||
|
||||
div.urlInput {
|
||||
padding: 0 0 4vh 0;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-height: 839px) {
|
||||
body{
|
||||
zoom: 0.74;
|
||||
-moz-transform: scale(0.74);
|
||||
-moz-transform-origin: 0 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 940px) {
|
||||
body{
|
||||
zoom: 0.74;
|
||||
-moz-transform: scale(0.74);
|
||||
-moz-transform-origin: 0 0;
|
||||
|
||||
}
|
||||
.container{
|
||||
max-width:99%;
|
||||
}
|
||||
div.urlInput {
|
||||
}
|
||||
div#audioOutputContainer, #history {
|
||||
margin: 2em;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 840px) {
|
||||
body{
|
||||
zoom: 0.64;
|
||||
-moz-transform: scale(0.64);
|
||||
-moz-transform-origin: 0 0;
|
||||
|
||||
}
|
||||
|
||||
padding: 0 0 1vh 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 639px) {
|
||||
div.urlInput {
|
||||
}
|
||||
div#audioOutputContainer, #history {
|
||||
margin: 2em;
|
||||
margin: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,26 +304,26 @@
|
||||
</div>
|
||||
|
||||
<div class="inputCombo" id="advanced" style="margin: 10px 0px 10px 10px;">
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="whipoutaudiobitrate" title="Which audio bitrate target would you prefer?" >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="whipoutaudiobitrate" title="Which audio bitrate target would you prefer? 128-kbps is fine for music." >
|
||||
<option value="0" selected>🎙️Default Audio Bitrate</option>
|
||||
<option value="32">🎙️32-kbps</option>
|
||||
<option value="64">🎙️64-kbps</option>
|
||||
<option value="128">🎙️128-kbps</option>
|
||||
<option value="256">🎙️256-kbps</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="vbrcbr" title="Which audio bitrate target would you prefer?" >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="vbrcbr" title="Whether the audio bitrate with be constant or variable" >
|
||||
<option value="cbr" selected>🎙️CBR</option>
|
||||
<option value="vbr">🎙️VBR</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="denoise" title="Which audio bitrate target would you prefer?" >
|
||||
<option value="1" selected>🎙️Denoise On</option>
|
||||
<option value="0">🎙️Denoise Off</option>
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="denoise" onchange="checkStereo()" title="Turn off to improve clarity, but you'll hear any background noise" >
|
||||
<option value="0" selected>🎙️Denoise Off</option>
|
||||
<option value="1">🎙️Denoise On</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="autogain" title="Which audio bitrate target would you prefer?" >
|
||||
<option value="1" selected>🎙️Auto Gain On</option>
|
||||
<option value="0">🎙️Auto Gain Off</option>
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="autogain" onchange="checkStereo()" title="Auto-controls the input volume; turn off to manage that yourself." >
|
||||
<option value="0" selected>🎙️Auto Gain Off</option>
|
||||
<option value="1">🎙️Auto Gain On</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="stereo" title="Which audio bitrate target would you prefer?" >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="stereo" title="Stereo is available only if auto-gain and noise-reduction is off." >
|
||||
<option value="1" selected>🎙️Stereo</option>
|
||||
<option value="0">🎙️Mono</option>
|
||||
</select >
|
||||
@@ -368,12 +336,15 @@
|
||||
<option value="6000">🎦6000-kbps</option>
|
||||
<option value="20000">🎦20000-kbps</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="codecGroupFlag" title="Which video codec would you prefer to be used if available?" >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="codecGroupFlag" onchange="updateSVC();" title="Which video codec would you prefer to be used if available?" >
|
||||
<option value="default" selected>🎦OpenH264</option>
|
||||
<option value="vp9">🎦VP9</option>
|
||||
<option value="h264">🎦H264</option>
|
||||
<option value="vp8">🎦VP8</option>
|
||||
<option value="av1">🎦AV1</option>
|
||||
<option value="vp9">🎦VP9</option>
|
||||
<option value="vp8">🎦VP8</option>
|
||||
<option value="h264">🎦H264</option>
|
||||
</select >
|
||||
<select style="border-radius:10px;margin-right:5px;width:unset!important;" class="changeText" id="svcGroupFlag" title="Which scalable video coding do you want to use?" >
|
||||
<option value="0" selected>🎦 SVC Off</option>
|
||||
</select >
|
||||
|
||||
</div>
|
||||
@@ -452,6 +423,38 @@ document.querySelector("#changeText1a").value = localStorage.getItem('changeText
|
||||
document.querySelector("#changeText2").value = localStorage.getItem('changeText2') || "";
|
||||
document.querySelector("#changeText3").value = localStorage.getItem('changeText3') || "";
|
||||
|
||||
const scalabilityModes = [
|
||||
'L1T1',
|
||||
'L1T2',
|
||||
'L1T3',
|
||||
'L2T1',
|
||||
'L2T2',
|
||||
'L2T3',
|
||||
'L3T1',
|
||||
'L3T2',
|
||||
'L3T3',
|
||||
'L2T1h',
|
||||
'L2T2h',
|
||||
'L2T3h',
|
||||
'S2T1',
|
||||
'S2T2',
|
||||
'S2T3',
|
||||
'S2T1h',
|
||||
'S2T2h',
|
||||
'S2T3h',
|
||||
'S3T1',
|
||||
'S3T2',
|
||||
'S3T3',
|
||||
'S3T1h',
|
||||
'S3T2h',
|
||||
'S3T3h',
|
||||
'L2T2_KEY',
|
||||
'L2T3_KEY',
|
||||
'L3T2_KEY',
|
||||
'L3T3_KEY'
|
||||
];
|
||||
|
||||
|
||||
function gohere1(){
|
||||
if (document.getElementById('changeText1').value){
|
||||
localStorage.setItem('changeText1', document.getElementById('changeText1').value);
|
||||
@@ -459,6 +462,7 @@ function gohere1(){
|
||||
|
||||
localStorage.setItem('bitrateGroupFlag', document.getElementById('bitrateGroupFlag').value);
|
||||
localStorage.setItem('codecGroupFlag', document.getElementById('codecGroupFlag').value);
|
||||
localStorage.setItem('svcGroupFlag', document.getElementById('svcGroupFlag').value);
|
||||
|
||||
localStorage.setItem('whipoutaudiobitrate', document.getElementById('whipoutaudiobitrate').value);
|
||||
localStorage.setItem('vbrcbr', document.getElementById('vbrcbr').value);
|
||||
@@ -486,15 +490,59 @@ function gohere1(){
|
||||
if (document.getElementById('codecGroupFlag').value!=="default"){
|
||||
codec = "&whipoutcodec="+document.getElementById('codecGroupFlag').value;
|
||||
}
|
||||
var svc = "";
|
||||
if (document.getElementById('svcGroupFlag').value!=="0"){
|
||||
svc = "&svc="+document.getElementById('svcGroupFlag').value;
|
||||
}
|
||||
|
||||
if (document.getElementById('changeText1a').value){
|
||||
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + "&whippushtoken=" + document.getElementById('changeText1a').value + codec + bitrate+whipoutaudiobitrate+vbrcbr+autogain+stereo+denoise;
|
||||
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + "&whippushtoken=" + document.getElementById('changeText1a').value + codec + bitrate+whipoutaudiobitrate+vbrcbr+autogain+stereo+denoise+svc;
|
||||
} else {
|
||||
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + codec + bitrate+whipoutaudiobitrate+vbrcbr+autogain+stereo+denoise;
|
||||
window.location = domain + "?push&whippush=" + encodeURIComponent(document.getElementById('changeText1').value) + codec + bitrate+whipoutaudiobitrate+vbrcbr+autogain+stereo+denoise+svc;;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkStereo(){
|
||||
if (parseInt(document.getElementById('autogain').value) || parseInt(document.getElementById('denoise').value)){
|
||||
document.getElementById('stereo').disabled = true;
|
||||
document.getElementById('stereo').title = "Noise reduction and auto-gain will prevent stereo audio from working";
|
||||
} else {
|
||||
document.getElementById('stereo').disabled = false;
|
||||
delete document.getElementById('stereo').disabled;
|
||||
document.getElementById('stereo').title = "Enable stereo 2.0 audio if available. Must be enabled on the viewer's end as well.";
|
||||
}
|
||||
}
|
||||
|
||||
function updateSVC(){
|
||||
|
||||
var codecName = document.getElementById('codecGroupFlag').value;
|
||||
var select = document.getElementById("svcGroupFlag");
|
||||
var selectedValue = "0";
|
||||
if (select.options && select.selectedIndex && select.options[select.selectedIndex]){
|
||||
selectedValue = select.options[select.selectedIndex].value;
|
||||
}
|
||||
select.innerHTML = "";
|
||||
|
||||
var option = document.createElement("option");
|
||||
option.text = "🎦 SVC Off";
|
||||
option.value = "0";
|
||||
select.add(option);
|
||||
select.selectedIndex = 0;
|
||||
|
||||
if (svcLUT[codecName]){
|
||||
svcLUT[codecName].forEach(opt=>{
|
||||
option = document.createElement("option");
|
||||
option.text = "🎦 "+opt;
|
||||
option.value = opt;
|
||||
select.add(option);
|
||||
if (opt == selectedValue){
|
||||
select.value = opt;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function gohere1t(){
|
||||
if (document.getElementById('changeText1t').value){
|
||||
localStorage.setItem('changeText1t', document.getElementById('changeText1t').value);
|
||||
@@ -527,6 +575,7 @@ function resetHistory(){
|
||||
document.querySelector("#changeText2").value = "";
|
||||
document.querySelector("#changeText3").value = "";
|
||||
document.querySelector("#changeText1t").value = "";
|
||||
checkStereo();
|
||||
}
|
||||
|
||||
(function (w) {
|
||||
@@ -555,12 +604,86 @@ function enterPressed(event, callback){
|
||||
}
|
||||
}
|
||||
|
||||
checkStereo();
|
||||
|
||||
var isMobile = false;
|
||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)){ // does not detect iPad Pros.
|
||||
isMobile=true; // if iOS, default to H264? meh. let's not.
|
||||
}
|
||||
var Firefox = navigator.userAgent.indexOf("Firefox")>=0;
|
||||
if (Firefox){
|
||||
Firefox = parseInt(navigator.userAgent.split("irefox/").pop()) || true;
|
||||
}
|
||||
|
||||
var capabilityType = Firefox ? "transmission" : "webrtc";
|
||||
var codecs = RTCRtpSender.getCapabilities('video').codecs;
|
||||
var svcLUT = {};
|
||||
var svcDefault = {};
|
||||
|
||||
function getCommonValues(obj) {
|
||||
if (obj.default){
|
||||
delete obj.default;
|
||||
}
|
||||
let commonValues = [];
|
||||
let firstKey = Object.keys(obj)[0];
|
||||
let firstArray = obj[firstKey];
|
||||
for (let i = 0; i < firstArray.length; i++) {
|
||||
let currentValue = firstArray[i];
|
||||
let isCommonValue = true;
|
||||
for (let key in obj) {
|
||||
if (!obj[key].includes(currentValue)) {
|
||||
isCommonValue = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isCommonValue) {
|
||||
commonValues.push(currentValue);
|
||||
}
|
||||
}
|
||||
return commonValues
|
||||
}
|
||||
|
||||
async function processCodecs(){
|
||||
await codecs.forEach(async codec => {
|
||||
var codecName = codec.mimeType.replace("video/","").toLowerCase();
|
||||
if (['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) {
|
||||
return;
|
||||
} else if (svcLUT[codecName]){ // already done
|
||||
return;
|
||||
}
|
||||
|
||||
svcLUT[codecName] = [];
|
||||
var capabilityPromises = [];
|
||||
for (const mode of scalabilityModes) {
|
||||
capabilityPromises.push(navigator.mediaCapabilities.encodingInfo({
|
||||
type: capabilityType,
|
||||
video: {
|
||||
contentType: codec.mimeType,
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
bitrate: 10000,
|
||||
framerate: 29.97,
|
||||
scalabilityMode: mode
|
||||
}
|
||||
}));
|
||||
}
|
||||
var capabilityResults = await Promise.all(capabilityPromises);
|
||||
for (var i = 0;i<capabilityResults.length;i++){
|
||||
if (capabilityResults[i].supported){
|
||||
svcLUT[codecName].push(scalabilityModes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
svcLUT['default'] = getCommonValues(svcLUT);
|
||||
updateSVC();
|
||||
});
|
||||
console.log("available codecs");
|
||||
console.log(svcLUT);
|
||||
|
||||
|
||||
}
|
||||
|
||||
processCodecs();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user