&contenthint and examples added; & pt-br

This commit is contained in:
Steve Seguin 2022-08-25 17:10:28 -04:00
parent dbf1f286aa
commit 494db979cd
7 changed files with 297 additions and 125 deletions

View File

@ -0,0 +1,54 @@
<html>
<body>
<button onclick="togglePlayer('STREAM123a')" >toggle video 1</button>
<button onclick="togglePlayer('STREAM123b')" >toggle video 2</button>
<iframe id="scene"
style="position: relative; display:block; width:100%; height: calc(100vh - 50px);"
allow="document-domain;encrypted-media;sync-xhr;cross-origin-isolated;accelerometer;midi;autoplay;fullscreen;picture-in-picture;display-capture;"
src="https://vdo.ninja/alpha/?room=ROOMHERE123&cleanoutput&transparent&noaudio&controls=0&noap&optimize=0&scale=100&scene&manual&b64css=dmlkZW97CiAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICBsZWZ0OiAwOwogICAgdG9wOiAwOwp9"
></iframe>
<script>
// you can remotely switch between video streams A and B, instead of using the toggle buttons. You'll need your own API service to switch, but that's up to you.
// https://vdo.ninja/alpha/?room=ROOMHERE123&push=STREAM123a&view <= invite guest-a
// https://vdo.ninja/alpha/?room=ROOMHERE123&push=STREAM123b&view <= invite guest-b
// &b64css=dmlkZW97CiAgICBwb3NpdGlvbjogYWJzb2x1dGU7CiAgICBsZWZ0OiAwOwogICAgdG9wOiAwOwp9" -- makes sure all videos added align to the top-left corner, overlapping other videos if needed
// we do not use &fadein=0, as that can cause flicker
// &manual disables the auto mixer for scene=0, so we can manually add/remove elements to the scene via the IFRAME API instead
var scene = document.getElementById("scene");
//////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) {
if (scene && e.source == scene.contentWindow){
if (e.data.action === 'view-connection') {
console.log(e.data);
} else if (e.data.action === 'end-view-connection') {
console.log(e.data);
}
}
})
var activeVideo = null;
async function togglePlayer(video){
activeVideo = video;
scene.contentWindow.postMessage({
add: true,
target: activeVideo
}, '*');
setTimeout(function(){
scene.contentWindow.postMessage({
replace: true, // this replaces all videos in the current scene with the target stream ID. We coudl use `remove` instead, but that requires specifying the streamID to remove
target: activeVideo
}, '*');
},500);
}
</script>
</body>
</html>

View File

@ -54,10 +54,7 @@
iframeExample.append(exampleHeader, exampleBody);
container.prepend(iframeExample);
//////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
var media = {};
media.tracks = {};
@ -67,6 +64,11 @@
console.error(e);
});
//////////// LISTEN FOR EVENTS
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function (e) {
if (e.source != iframe.contentWindow){return} // reject messages send from other iframes

View File

@ -82,7 +82,7 @@
<link itemprop="url" href="./media/vdoNinja_logo_full.png" />
</span>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/CodecsHandler.js?ver=37"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=503"></script>
<script type="text/javascript" crossorigin="anonymous" src="./webrtc.js?ver=508"></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">
@ -2118,7 +2118,8 @@
<li><a onclick="changeLg('en');toggle(document.getElementById('languages'));" style="cursor: pointer;">English</a></li>
<li><a onclick="changeLg('ru');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Moscow">Russian</a></li>
<li><a onclick="changeLg('fr');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Paris">French</a></li>
<li><a onclick="changeLg('pt');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Lisbon;America/Araguaina">Portuguese</a></li>
<li><a onclick="changeLg('pt');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Lisbon;">Portuguese (Europe)</a></li>
<li><a onclick="changeLg('pt-br');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="America/Araguaina">Portuguese (Brazil)</a></li>
<li><a onclick="changeLg('it');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Rome">Italian</a></li>
<li><a onclick="changeLg('de');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Berlin">German</a></li>
<li><a onclick="changeLg('es');toggle(document.getElementById('languages'));" style="cursor: pointer;" data-tz="Europe/Madrid">Spanish</a></li>
@ -2236,11 +2237,11 @@
// session.defaultBackgroundImages = ["./media/bg_sample1.webp", "./media/bg_sample2.webp"]; // for &effects=5 (virtual backgrounds)
</script>
<script type="text/javascript" crossorigin="anonymous" src="./thirdparty/aes.js"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=406"></script>
<script type="text/javascript" crossorigin="anonymous" id="lib-js" src="./lib.js?ver=408"></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=417"></script>
<script type="text/javascript" crossorigin="anonymous" id="main-js" src="./main.js?ver=418"></script>
</body>
</html>

194
lib.js
View File

@ -12999,7 +12999,19 @@ async function createDirectorOnlyBox() {
var newScene = document.createElement("div");
newScene.innerHTML = '<button style="margin: 0 5px 10px 5px;" data-sid="'+session.streamID+'" data-action-type="addToScene" data-scene="'+scene+'" title="Add to Scene '+scene+'" onclick="directEnable(this, event);"><span ><i class="las la-plus-square" style="color:#060"></i> Scene: '+scene+'</span></button>';
newScene.classList.add("customScene");
getById("container_director").appendChild(newScene);
//getById("container_director").appendChild(newScene);
var added = false;
getById("container_director").querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_director").appendChild(newScene);
}
}
}
});
@ -15188,9 +15200,7 @@ async function getAudioOnly(selector, trackid = null, override = false) {
log("CONSTRAINT");
log(constraint);
var stream = await navigator.mediaDevices.getUserMedia(constraint).then(function(stream2) {
pokeIframeAPI("local-microphone-event");
return stream2;
}).catch(function(err) {
warnlog(err);
@ -16834,9 +16844,21 @@ async function grabVideo(quality = 0, eleName = 'previewWebcam', selector = "sel
if (session.mc && session.mc.getSenders){
session.mc.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "video") {
session.mc.canvasStream.getVideoTracks().forEach(trk=>{
sender.replaceTrack(trk); // replace may not be supported by all browsers. eek.
});
var trk = getMeshcastCanvasTrack();
if (session.screenShareState && session.screenshareContentHint && (trk.kind === "video")){
try {
trk.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (trk.kind === "video")){
try {
trk.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
sender.replaceTrack(trk); // replace may not be supported by all browsers. eek.
}
});
}
@ -17365,6 +17387,28 @@ function pushOutVideoTrack(track){
}
return;
}
if (session.audioContentHint && (track.kind === "audio")){
try {
track.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
}
if (session.screenShareState && session.screenshareContentHint && (track.kind === "video")){
try {
track.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (track.kind === "video")){
try {
track.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
if (session.mc && session.mc.getSenders){ // should only be 0 or 1 video sender, ever.
//var added = false;
@ -17780,6 +17824,16 @@ function senderAudioUpdate(callback=false){
if (session.videoElement.srcObject.getAudioTracks()) {
var tracks = session.videoElement.srcObject.getAudioTracks();
if (session.audioContentHint && tracks.length){
tracks.forEach(trk=>{
try {
trk.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
});
}
if (session.mc && session.mc.getSenders && tracks.length){
session.mc.getSenders().forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (sender.track && sender.track.kind == "audio") {
@ -19212,7 +19266,6 @@ session.publishFile = function(ele, event){ // webcam stream is used to generate
v.src = fileURL;
try {
if (Firefox){
session.streamSrc = v.mozCaptureStream();
@ -19244,44 +19297,12 @@ session.publishFile = function(ele, event){ // webcam stream is used to generate
session.streamSrc = vid.captureStream(); // gaaaaaaaaaaaahhhhhhhh!
}
toggleMute(true);
session.streamSrc.getTracks().forEach(function(track){ // I'm making an exception I guess -- reversing the role?
for (UUID in session.pcs){
if ("realUUID" in session.pcs[UUID]){continue;}
var senders = getSenders2(UUID);
log(track);
if (track.kind == "video"){
try {
if ((session.pcs[UUID].guest==true) && (session.roombitrate===0)) {
log("room rate restriction detected. No videos will be published to other guests");
} else if (session.pcs[UUID].allowVideo==true){ // allow
// for any connected peer, update the video they have if connected with a video already.
var added=false;
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (added) {
return;
}
if (sender.track && sender.track.kind == "video"){
sender.replaceTrack(track); // replace may not be supported by all browsers. eek.
added=true;
}
});
if (added==false){
session.pcs[UUID].addTrack(track, session.streamSrc);
setTimeout(function(uuid){session.optimizeBitrate(uuid);},session.rampUpTime, UUID); // 3 seconds lets us ramp up the quality a bit and figure out the total bandwidth quicker
}
}
} catch (e){
errorlog(e);
}
} else {
session.pcs[UUID].addTrack(track, session.streamSrc);
}
}
});
session.refreshScale();
var tracks = session.streamSrc.getVideoTracks();
if (tracks.length){
pushOutVideoTrack(tracks[0]);
}
var tracks = session.streamSrc.getAudioTracks();
senderAudioUpdate();
}
session.applySoloChat(); // mute streams that should be muted if a director
@ -22817,6 +22838,16 @@ function createScreenShareURL(transparent=true){
extras += "&smallshare";
}
if (session.screenshareContentHint){
extras += "&sshint="+session.screenshareContentHint;
} else if (session.contentHint){
extras += "&sshint="+session.contentHint;
}
if (session.audioContentHint){
extras += "&audiohint="+session.audioContentHint;
}
if (session.meshcastScreenShareCodec){
extras += "&mccodec="+session.meshcastScreenShareCodec;
} else if (session.meshcastCodec){
@ -23467,7 +23498,18 @@ function initSceneList(UUID){
var newScene = document.createElement("div");
newScene.innerHTML = '<button style="margin: 0 5px 10px 5px;" data-sid="'+session.rpcs[UUID].streamID+'" data--u-u-i-d="'+UUID+'" data-action-type="addToScene" data-scene="'+scene+'" title="Add to Scene '+scene+'" onclick="directEnable(this, event);"><span ><i class="las la-plus-square" style="color:#060"></i> Scene: '+scene+'</span></button>';
newScene.classList.add("customScene");
getById("container_" + UUID).appendChild(newScene);
var added = false;
getById("container_" + UUID).querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_" + UUID).appendChild(newScene);
}
});
}
@ -23484,15 +23526,37 @@ function updateSceneList(scene){
var newScene = document.createElement("div");
newScene.innerHTML = '<button style="margin: 0 5px 10px 5px;" data-sid="'+session.rpcs[UUID].streamID+'" data--u-u-i-d="'+UUID+'" data-action-type="addToScene" data-scene="'+scene+'" title="Add to Scene '+scene+'" onclick="directEnable(this, event);"><span ><i class="las la-plus-square" style="color:#060"></i> Scene: '+scene+'</span></button>';
newScene.classList.add("customScene");
getById("container_" + UUID).appendChild(newScene);
var added = false;
getById("container_" + UUID).querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
log(ele);
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_" + UUID).appendChild(newScene);
}
}
if (session.showDirector){
if (document.getElementById("container_director")){
var newScene = document.createElement("div");
newScene.innerHTML = '<button style="margin: 0 5px 10px 5px;" data-sid="'+session.streamID+'" data-action-type="addToScene" data-scene="'+scene+'" title="Add to Scene '+scene+'" onclick="directEnable(this, event);"><span ><i class="las la-plus-square" style="color:#060"></i> Scene: '+scene+'</span></button>';
newScene.classList.add("customScene");
getById("container_director").appendChild(newScene);
//getById("container_director").appendChild(newScene);
var added = false;
getById("container_director").querySelectorAll('.customScene>[data-scene]').forEach(ele=>{
if (!added && ele.dataset.scene>scene+""){
ele.parentNode.parentNode.insertBefore(newScene, ele.parentNode);
added = true;
}
});
if (!added){
getById("container_director").appendChild(newScene);
}
}
}
}
@ -27960,8 +28024,42 @@ function createSecondStream2(UUID){
}
}
/* if (session.audioContentHint && tracks.length){
tracks.forEach(trk=>{
try {
trk.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
});
} */
var senders = getSenders2(UUID+"_screen");
session.screenStream.getTracks().forEach(function(track){
if (session.audioContentHint && (track.kind === "audio")){
try {
track.contentHint = session.audioContentHint;
} catch(e){
errorlog(e);
}
}
if (session.screenshareContentHint && (track.kind === "video")){
try {
track.contentHint = session.screenshareContentHint;
} catch(e){
errorlog(e);
}
} else if (session.contentHint && (track.kind === "video")){
try {
track.contentHint = session.contentHint;
} catch(e){
errorlog(e);
}
}
var added = false;
senders.forEach((sender) => { // I suppose there could be a race condition between negotiating and updating this. if joining at the same time as changnig streams?
if (added){return;}

139
main.js
View File

@ -2263,6 +2263,18 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
}
}
if (urlParams.has('contenthint') || urlParams.has('contenttype') || urlParams.has('content') || urlParams.has('hint')) {
session.contentHint = urlParams.get('contenthint') || urlParams.get('contenttype') || urlParams.get('content') || urlParams.get('hint') || "detail";
}
if (urlParams.has('audiocontenthint') || urlParams.has('audiocontenttype') || urlParams.has('audiocontent') || urlParams.has('audiohint')) {
session.audioContentHint = urlParams.get('audiocontenthint') || urlParams.get('audiocontenttype') || urlParams.get('audiocontent') || urlParams.get('audiohint') || "music";
}
if (urlParams.has('screensharecontenthint') || urlParams.has('sscontenthint') || urlParams.has('screensharecontenttype') || urlParams.has('sscontent') || urlParams.has('sshint')) {
session.screenshareContentHint = urlParams.get('screensharecontenthint') || urlParams.get('sscontenthint') || urlParams.get('screensharecontenttype') || urlParams.get('sscontent') || urlParams.get('sshint') || "detail";
}
if (urlParams.has('codec')) {
log("CODEC CHANGED");
@ -4199,77 +4211,81 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
reloadRequested(); // location.reload();, but with no user prompt (force reload)
}
if ("getStats" in e.data) {
if (("getStats" in e.data)){
var stats = {};
stats.total_outbound_connections = Object.keys(session.pcs).length;
stats.total_inbound_connections = Object.keys(session.rpcs).length;
stats.inbound_stats = {};
for (var i in session.rpcs) {
stats.inbound_stats[session.rpcs[i].streamID] = session.rpcs[i].stats;
}
try {
stats.inbound_stats = {};
stats.total_outbound_connections = Object.keys(session.pcs).length;
stats.total_inbound_connections = Object.keys(session.rpcs).length;
for (var i in session.rpcs) {
stats.inbound_stats[session.rpcs[i].streamID] = session.rpcs[i].stats;
}
for (var uuid in session.pcs) {
setTimeout(function(UUID) {
session.pcs[UUID].getStats().then(function(stats) {
stats.forEach(stat => {
if (stat.type == "outbound-rtp") {
if (stat.kind == "video") {
for (var uuid in session.pcs) {
setTimeout(function(UUID) {
session.pcs[UUID].getStats().then(function(stats) {
stats.forEach(stat => {
if (stat.type == "outbound-rtp") {
if (stat.kind == "video") {
if ("qualityLimitationReason" in stat) {
if ("qualityLimitationReason" in stat) {
session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
session.pcs[UUID].stats.quality_limitation_reason = stat.qualityLimitationReason;
}
if ("framesPerSecond" in stat) {
session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
}
if ("encoderImplementation" in stat) {
session.pcs[UUID].stats.encoder = stat.encoderImplementation;
}
}
if ("framesPerSecond" in stat) {
session.pcs[UUID].stats.resolution = stat.frameWidth + " x " + stat.frameHeight + " @ " + stat.framesPerSecond;
} else if (stat.type == "remote-candidate") {
if ("relayProtocol" in stat) {
if ("ip" in stat) {
session.pcs[UUID].stats.remote_relay_IP = stat.ip;
}
session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
}
if ("encoderImplementation" in stat) {
session.pcs[UUID].stats.encoder = stat.encoderImplementation;
if ("candidateType" in stat) {
session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
}
}
} else if (stat.type == "remote-candidate") {
if ("relayProtocol" in stat) {
if ("ip" in stat) {
session.pcs[UUID].stats.remote_relay_IP = stat.ip;
} else if (stat.type == "local-candidate") {
if ("relayProtocol" in stat) {
if ("ip" in stat) {
session.pcs[UUID].stats.local_relayIP = stat.ip;
}
session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
}
session.pcs[UUID].stats.remote_relayProtocol = stat.relayProtocol;
}
if ("candidateType" in stat) {
session.pcs[UUID].stats.remote_candidateType = stat.candidateType;
}
} else if (stat.type == "local-candidate") {
if ("relayProtocol" in stat) {
if ("ip" in stat) {
session.pcs[UUID].stats.local_relayIP = stat.ip;
if ("candidateType" in stat) {
session.pcs[UUID].stats.local_candidateType = stat.candidateType;
}
session.pcs[UUID].stats.local_relayProtocol = stat.relayProtocol;
}
if ("candidateType" in stat) {
session.pcs[UUID].stats.local_candidateType = stat.candidateType;
}
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if ("availableOutgoingBitrate" in stat){
session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
}
if ("totalRoundTripTime" in stat){
if ("responsesReceived" in stat){
session.pcs[UUID].stats.average_roundTripTime_ms = parseInt((stat.totalRoundTripTime/stat.responsesReceived)*1000);
}
} else if ((stat.type == "candidate-pair" ) && (stat.nominated)) {
if ("availableOutgoingBitrate" in stat){
session.pcs[UUID].stats.available_outgoing_bitrate_kbps = parseInt(stat.availableOutgoingBitrate/1024);
}
if ("totalRoundTripTime" in stat){
if ("responsesReceived" in stat){
session.pcs[UUID].stats.average_roundTripTime_ms = parseInt((stat.totalRoundTripTime/stat.responsesReceived)*1000);
}
}
}
}
return;
});
return;
});
return;
});
}, 0, uuid);
}, 0, uuid);
}
} catch(e){
// disconnected probably
}
setTimeout(function() {
stats.outbound_stats = {};
for (var i in session.pcs) {
stats.outbound_stats[i] = session.pcs[i].stats;
}
try {
for (var i in session.pcs) {
stats.outbound_stats[i] = session.pcs[i].stats;
}
} catch(e){}
parent.postMessage({
"stats": stats
}, session.iframetarget);
@ -4555,9 +4571,16 @@ async function main(){ // main asyncronous thread; mostly initializes the user s
session.rpcs[i].videoElement.parentNode.parentNode.removeChild(session.rpcs[i].videoElement.parentNode);
} catch (e) {}
}
} else if ("replace" in e.data) { // should allow for a cleaner cut between two video streams.
try {
getById("gridlayout").appendChild(session.rpcs[i].videoElement);
getById("gridlayout").childNodes.forEach(ele=>{
if ((!ele.id) || (ele.id !== session.rpcs[i].videoElement.id)){
getById("gridlayout").removeChild(ele);
}
});
} catch(e){}
}
// video and audio bitrate handled else where
} catch (e) {
errorlog(e);
}

View File

@ -31,11 +31,7 @@
<canvas id="packetloss-graph"></canvas>
</div>
</div>
<div id="log" onclick="copyFunction(this.innerText)">
<h2>Log <i class="las la-clipboard"></i></h2>
<ul></ul>
</div>
<div id="explanation">
<h2>How to use</h2>
<ol>
@ -155,12 +151,10 @@
return out;
}
var logged = [];
function logData(type, data) {
var log = document.getElementById("log").getElementsByTagName("ul")[0];
var entry = document.createElement('li');
entry.textContent =
"[" + new Date().toLocaleTimeString() + "] " + type + " : " + data;
log.prepend(entry);
data.timestamp = new Date().now();
logged.push(data);
}
function reloadTurn(){

File diff suppressed because one or more lines are too long