jitsi-stream-client/script.js

553 lines
16 KiB
JavaScript

// DISABLE RIGHTCLICK
document.addEventListener('contextmenu', event => event.preventDefault());
const options = {
serviceUrl: 'https://meet.theater.digital/http-bind',
hosts: {
domain: 'meet.theater.digital',
muc: 'conference.meet.theater.digital'
}
};
const confOptions = {
openBridgeChannel: true
};
const videoSize = {
width: 624,
height: 351
};
let connection = null;
let room = null;
let muted = false;
// zu mappende namen.
let remoteMappingName = new Array(9);
// alle verfügbaren tracks mit id -> {audio: track, video: track, position: number}
let tracks = {};
let useLocalVideo = false;
let isJoined = false;
/**
* HTML Builder
* a bit hacky.. but hey, i needed it fast.
*/
function initHtml(rows, cols) {
// calculate video element sizes
let windowHeight = window.innerHeight;
let windowWidth = window.innerWidth;
console.log(windowHeight);
let margin = 6; // 3px margin on each side
let maxWidth = (windowWidth-12) / cols - margin
let maxHeight = (windowHeight-12) / rows - margin - 24 - 28 - 6; // 24 -> size added for nametag; 28px per row for control elements; idontknow where the 6px appear xD
if(maxWidth/16*9 <= maxHeight) {
videoSize.width = maxWidth;
videoSize.height = maxWidth/16*9;
} else {
videoSize.width = maxHeight*16/9;
videoSize.height = maxHeight;
}
// clear video container
let rootElem = $('#video-container')
rootElem.empty();
let elemCounter = 0 // for the element container id's
// populate video elements
for(let i = 0; i < rows; i++) {
let rowElem = $(document.createElement("div"));
rowElem.addClass("video-row");
for(let j = 0; j < cols; j++) {
let colElem = $(document.createElement("div"));
colElem.addClass(`video video-${elemCounter++}`);
colElem.width(videoSize.width + margin);
colElem.height(videoSize.height + margin + 24);
colElem.css("display", "inline-block");
colElem.append(`
<video autoplay='autoplay' width="${videoSize.width}" height="${videoSize.height}"></video>
<audio autoplay='autoplay'></audio>
<span class="name"></span>
`)
rowElem.append(colElem)
}
rootElem.append(rowElem)
}
// populate audio controls
elemCounter = 0;
for(let i = 0; i < rows; i++) {
let rowElem = $(document.createElement("div"))
rowElem.addClass("row");
for(let j = 0; j < cols; j++) {
let colElem = $(document.createElement("div"))
colElem.addClass("volume-slider col volume-" + elemCounter);
colElem.append(`
<span class="volume">70%</span>
<input type="range" min="0" max="1" step="0.01" value="0.7" oninput="setLevel(${elemCounter}, this.value)">
<input type="text" onchange="setName(${elemCounter}, this.value)" placeholder="username1">
<button onclick="reload(${elemCounter})">Reload</button>
`)
elemCounter++;
rowElem.append(colElem);
}
rootElem.append(rowElem);
}
rootElem.append(`
<button onclick="leave()">Leave Conference</button>
<p class="available-users"></p>
`)
}
/**
*
* MIDI SECTION
*/
let midi, data;
// start talking to MIDI controller
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess({
sysex: false
}).then(onMIDISuccess);
} else {
console.warn("No MIDI support in your browser")
}
// on success
function onMIDISuccess(midiData) {
// this is all our MIDI data
midi = midiData;
let allInputs = midi.inputs.values();
// loop over all available inputs and listen for any MIDI input
for (let input = allInputs.next(); input && !input.done; input = allInputs.next()) {
// when a MIDI value is received call the onMIDIMessage function
input.value.onmidimessage = (messageData ) => {
if(messageData.data[0] != 176) return;
if(messageData.data[1] > 7) messageData.data[1] -= 8;
setLevel(messageData.data[1], messageData.data[2]/127);
};
}
}
/**
*
* END MIDI SECTION
*
*/
/**
* Selects all shown participants to receive high quality video. Otherwise Jitsi will only show thumbnail size video to the Streamer
*/
function selectParticipants() {
let part = [];
for(let i in tracks) {
if(tracks.hasOwnProperty(i)) {
if(tracks[i].position >= 0) {
part.push(i)
}
}
}
room.selectParticipants(part);
}
/**
* Handles remote tracks
* @param track JitsiTrack object
*/
function onRemoteTrack(track) {
if (track.isLocal()) {
return;
}
const participant = track.getParticipantId();
if (tracks[participant] == null)
return;
tracks[participant][track.getType()] = track;
console.log(`mapping ${track.getType()} track from ${participant}`);
let displayName = room.getParticipantById(participant).getDisplayName() ? room.getParticipantById(participant).getDisplayName().toLowerCase(): undefined;
if(!displayName) {
return;
}
let desiredPosition = remoteMappingName.indexOf(displayName);
if(tracks[participant].audio && tracks[participant].video && desiredPosition >= 0) {
attachUser(participant, desiredPosition);
}
}
/**
* Event wenn ein Mediastream entfernt wurde.
* Räumt die Track tracker objekte auf und entfernt - wenn vorhanden - die entsprechende zuordnung zum media element
* @param track
*/
function onRemoteTrackRemove(track) {
const participant = track.getParticipantId();
const type = track.getType();
if(tracks[participant] != null && tracks[participant][type]) {
console.log(`detaching ${type} track from ${participant}`);
tracks[participant][type].detach($(`.video-${tracks[participant].position} ${type}`)[0]);
}
delete tracks[participant][type];
}
/**
*
* @param id
* @param user
*/
function onUserJoin(id, user) {
let displayName = user.getDisplayName() ? user.getDisplayName().toLowerCase() : undefined;
console.log(`user join - ${displayName}`);
tracks[id] = {position: -1};
console.log(tracks[id]);
if(displayName && remoteMappingName.indexOf(displayName) >= 0) {
selectParticipants();
}
updateParticipantList();
}
/**
* Event für Namensänderungen, ordnet den user korrekt an.
* @param participant
* @param displayName
*/
function onNameChange(participant, displayName) {
// detach this user from current position
detachUser(participant);
let position = -1;
if(displayName) {
position = remoteMappingName.indexOf(displayName.toLowerCase());
}
if(position >= 0 && tracks[participant]) {
// detach user in the new position
for(let i in tracks) {
if(tracks[i].position === position) {
detachUser(i);
break;
}
}
attachUser(participant, position)
}
selectParticipants();
updateParticipantList();
}
/**
* Event wenn ein User den Raum verlässt
* Entfernt die Verbindung der Tracks mit den Mediaelementen wenn vorhanden und räumt die Track tracker objekte auf.
* @param id
*/
function onUserLeft(id) {
console.log('user left');
detachUser(id);
delete tracks[id];
updateParticipantList();
selectParticipants()
}
/**
* Detaches the Tracks of participant with ID from it's position.
* @param id
*/
function detachUser(id) {
if (tracks[id] != null) {
console.log("detaching user " + id);
if(tracks[id].video) {
tracks[id].video.detach($(`.video-${tracks[id].position} video`)[0]);
}
if(tracks[id].audio) {
tracks[id].audio.detach($(`.video-${tracks[id].position} audio`)[0]);
}
tracks[id].position = -1;
}
}
/**
* Attaches participant with ID to POSITION
* @param id
* @param position
*/
function attachUser(id, position) {
// check if track exists, and is not attached to another element already.
if (tracks[id] != null && (tracks[id].position < 0 || tracks[id].position === position)) {
console.log(`attaching user ${id} to ${position}`);
// check if there is already some participant attached to this position and detach the tracks
if(tracks[id].position !== position) {
for(let i in tracks) {
if(tracks.hasOwnProperty(i) && tracks[i].position === position) {
detachUser(i);
}
}
tracks[id].position = position;
}
// finally attach new participant to position
if(tracks[id].video && tracks[id].video.containers.length === 0) {
console.log(`attaching video from user ${id} to ${position}`);
tracks[id].video.attach($(`.video-${tracks[id].position} video`)[0]);
}
if(tracks[id].audio && tracks[id].audio.containers.length === 0) {
console.log(`attaching audio from user ${id} to ${position}`);
tracks[id].audio.attach($(`.video-${tracks[id].position} audio`)[0]);
}
}
}
/**
* Enables the connect button, so that we can join a room
*/
function onConnectionSuccess() {
$('#room-name-button').prop('disabled', false);
}
/**
* Resizes the video element according to maximum available width and height. Respects input aspect ratio.
*/
function onVideoResize(event) {
console.log(event)
const video = $(event.target)[0];
console.log(video)
const width = video.videoWidth;
const height = video.videoHeight;
console.log("video sizes", width, height)
let targetWidth = videoSize.width;
let targetHeight = videoSize.height;
console.log("target sizes", targetWidth, targetHeight)
if(width > videoSize.width) {
targetHeight *= videoSize.width / width
}
if(height > targetHeight) {
targetWidth *= targetHeight / height
}
video.width = targetWidth;
video.height = targetHeight;
}
/**
* This function is called when we disconnect. It removes the Listeners.
*/
function disconnect() {
console.log('disconnect!');
connection.removeEventListener(
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
onConnectionSuccess);
connection.removeEventListener(
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
disconnect);
}
/**
* Connects the interface to a conference room, sets up the listeners..
*/
function connect(e) {
e.preventDefault();
const roomName = $('#room-name').val();
if($('#use-local-video').is(":checked")) {
useLocalVideo = true
} else {
useLocalVideo = false
}
initHtml($('#rows').val(), $('#cols').val())
room = connection.initJitsiConference(roomName, confOptions);
room.setDisplayName("Streamer");
room.on(JitsiMeetJS.events.conference.TRACK_ADDED, onRemoteTrack);
room.on(JitsiMeetJS.events.conference.TRACK_REMOVED, onRemoteTrackRemove);
room.on(JitsiMeetJS.events.conference.USER_JOINED, onUserJoin);
room.on(JitsiMeetJS.events.conference.USER_LEFT, onUserLeft);
room.on(JitsiMeetJS.events.conference.CONFERENCE_JOINED, onConferenceJoined);
room.on(JitsiMeetJS.events.conference.DISPLAY_NAME_CHANGED, onNameChange);
room.join($('#room-password').val());
$('#room-selector').hide();
$('#room').show();
// initiaize fields with values
for(let i = 0; i<remoteMappingName.length; i++) {
$('.volume-' + i + ' input[type="text"]').val(remoteMappingName[i]);
$('.volume-' + i + ' input[type="range"]').val(0.7);
$('.video-' + i + ' video').on('resize', onVideoResize).width(videoSize.width).height(videoSize.height);
$('.video-' + i + ' span').text(remoteMappingName[i]);
}
}
function onConferenceJoined() {
isJoined = true;
for (let i = 0; i < localTracks.length; i++) {
addLocalTrack(localTracks[i]);
}
}
/**
* Disconnects everything
*/
function unload() {
room.leave();
connection.disconnect();
return null;
}
/**
* Sets the Outputlevel of an audio source
* @param id
* @param level
*/
function setLevel(id, level) {
if(id < remoteMappingName.length) {
let track = $(`.video-${id} audio`)[0];
$('.volume-' + id + ' .volume').text(Math.round(level*100) + "%");
$('.volume-' + id + ' input[type="range"]').val(level);
track.volume = level;
}
}
/**
* Sets the name of a position
* @param position
* @param name
*/
function setName(position, name) {
name = name.toLowerCase();
console.log(`setting name of ${position} to ${name}`);
remoteMappingName[position] = name;
$('.video-' + position + ' .name').text(name);
for(let i in tracks) {
if(tracks.hasOwnProperty(i) && tracks[i].position === position) {
detachUser(i);
break;
}
}
let participants = room.getParticipants();
for(let i = 0; i < participants.length; i++) {
if(participants[i].getDisplayName() && participants[i].getDisplayName().toLowerCase() === name) {
attachUser(participants[i].getId(), position);
break;
}
}
selectParticipants();
window.localStorage.setItem('remoteMappingName', JSON.stringify(remoteMappingName));
}
function reload(position) {
for(let i in tracks) {
if(tracks.hasOwnProperty(i) && tracks[i].position === position) {
detachUser(i);
attachUser(i, position);
}
}
}
function leave() {
room.leave();
isJoined = false;
$('#room').hide();
$('#room-selector').show();
for(let i in localTracks) {
room.removeTrack(localTracks[i]);
}
}
function toggleMute() {
let audioElems = $("audio");
muted = !muted;
audioElems.prop("muted", muted)
if(muted) {
$('#muted-status').text("Muted")
} else {
$('#muted-status').text("Unmuted")
}
}
function updateParticipantList() {
$('.available-users').text(room.getParticipants().map(p => p.getDisplayName() || 'Fellow Jitster').join(', '));
}
/**
* Sets the output to the selected outputsource
* @param selected
*/
function changeAudioOutput(selected) { // eslint-disable-line no-unused-vars
JitsiMeetJS.mediaDevices.setAudioOutputDevice(selected.value);
}
window.onbeforeunload = unload;
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.WARN);
JitsiMeetJS.init({});
connection = new JitsiMeetJS.JitsiConnection(null, null, options);
connection.addEventListener(
JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
onConnectionSuccess);
connection.addEventListener(
JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
disconnect);
connection.connect();
if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
const audioOutputDevices
= devices.filter(d => d.kind === 'audiooutput');
if (audioOutputDevices.length > 1) {
$('#audioOutputSelect').html(
audioOutputDevices
.map(
d =>
`<option value="${d.deviceId}">${d.label}</option>`)
.join('\n'));
$('#audioOutputSelectWrapper').show();
}
});
}
if(window.localStorage.getItem('remoteMappingName') !== null) {
remoteMappingName = JSON.parse(window.localStorage.getItem('remoteMappingName'));
}
let localTracks = [];
function onLocalTracks(tracks) {
localTracks = tracks;
for (let i = 0; i < localTracks.length; i++) {
if (localTracks[i].getType() === 'video') {
localTracks[i].attach($(`#localVideo`)[0]);
}
if(isJoined) {
addLocalTrack(localTracks[i]);
}
}
}
let camera = "";
navigator.mediaDevices.enumerateDevices()
.then(function(devices) {
devices.forEach(function(device) {
console.log(device.kind + ": " + device.label +
" id = " + device.deviceId);
if(device.label.indexOf("NDI") != -1) {
camera = device.deviceId;
}
});
})
.catch(function(err) {
console.log(err.name + ": " + err.message);
});
JitsiMeetJS.createLocalTracks({ devices: [ 'video' ], cameraDeviceId: camera })
.then(onLocalTracks)
.catch(error => {
throw error;
});
function addLocalTrack(track) {
if(useLocalVideo) {
room.addTrack(track);
}
}