- Don't sort if a player is fullscreened. Fixes #12 - Force a sort when exiting fullscreen - Only sort when the player list changes, not any resize
249 lines
7.2 KiB
JavaScript
249 lines
7.2 KiB
JavaScript
// Get our initial stream list and create streams
|
|
xhr = new XMLHttpRequest();
|
|
xhr.onreadystatechange = function() {
|
|
if (this.readyState == 4 && this.status == 200) {
|
|
processStreamList(JSON.parse(this.responseText));
|
|
}
|
|
}
|
|
|
|
disabledPlayers = [];
|
|
playerVolumeSettings = {};
|
|
|
|
function webcallFrameSort() {
|
|
// If we're full screening a player this will break it, so don't
|
|
if (document.fullscreenElement) { return }
|
|
|
|
// Sort the players alphabetically by ID
|
|
Array.from(document.body.querySelectorAll(".frame"))
|
|
.sort((a, b) => a.id > b.id ? 1 : -1)
|
|
.forEach(node => document.body.appendChild(node))
|
|
}
|
|
|
|
// Auto-resize frames in a webcall interface
|
|
function webcallFrameResize() {
|
|
// Figure out how many Frames are visible
|
|
div_count = 0;
|
|
|
|
document.querySelectorAll(".frame").forEach(
|
|
function(element) {
|
|
div_count += 1;
|
|
}
|
|
)
|
|
|
|
// If none are visible, show placeholder text and bail
|
|
if (div_count < 1) {
|
|
document.querySelector("#placeholder").style.display = "block";
|
|
return;
|
|
}
|
|
|
|
// Hide placeholder if any players are visible
|
|
document.querySelector("#placeholder").style.display = "none";
|
|
|
|
// Get player frame aspect ratio for fitting purposes
|
|
const player_ar = 16 / 9;
|
|
|
|
// Try arrangements until the best fit is found
|
|
// Take the first column count that doesn't overflow height
|
|
cols = 0
|
|
for (let i = 1; i <= div_count; i++) {
|
|
const frame_width = window.innerWidth / i;
|
|
const frame_height = frame_width / player_ar;
|
|
|
|
if (frame_height * Math.ceil(div_count / i) <= window.innerHeight) {
|
|
cols = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set frames to the appropriate width
|
|
if (cols) {
|
|
w = `${Math.floor(100 / cols)}%`;
|
|
} else {
|
|
w = `${Math.floor(window.innerHeight * player_ar)}px`;
|
|
}
|
|
|
|
document.querySelectorAll(".frame").forEach(
|
|
function(element) {
|
|
element.style.width = w;
|
|
}
|
|
)
|
|
}
|
|
|
|
function createPlayer(stream, muted, volume) {
|
|
// Create frame
|
|
var outer_div = document.createElement("div");
|
|
outer_div.classList.add("frame");
|
|
outer_div.id = `frame_${stream}`;
|
|
|
|
// If we're putting name frames around players, make them
|
|
// Also provide close buttons to nuke this player
|
|
if (named_frames) {
|
|
var tab_div = document.createElement("div");
|
|
tab_div.classList.add("frame_tabs");
|
|
outer_div.appendChild(tab_div);
|
|
|
|
var name_div = document.createElement("div");
|
|
name_div.classList.add("frame_name");
|
|
name_div.innerHTML = `<a href="${stream}"> ${stream} </a>`;
|
|
tab_div.appendChild(name_div);
|
|
|
|
var button_div = document.createElement("div");
|
|
button_div.classList.add("frame_buttons");
|
|
button_div.innerHTML = `<a href="#close" onclick="closePlayer('${stream}'); return false"> ✖ </a>`;
|
|
tab_div.appendChild(button_div);
|
|
}
|
|
|
|
// Put the player div in the container
|
|
var player_div = document.createElement("div");
|
|
player_div.classList.add("player");
|
|
player_div.id = stream;
|
|
outer_div.appendChild(player_div);
|
|
|
|
// Create a throbber for dead streams
|
|
// We had this in the player div before, but it blinks every time the player reconnects
|
|
// This hides under a backgroundless player so it's only visible when nothing's up
|
|
throbber_div = document.createElement("div");
|
|
throbber_div.id = "throbber";
|
|
outer_div.appendChild(throbber_div);
|
|
|
|
// Put container in document
|
|
document.body.appendChild(outer_div);
|
|
|
|
// Initialize OvenPlayer
|
|
// We want as little interface stuff as possible, but we need to keep
|
|
// volume controls around so that can't be hidden.
|
|
player = OvenPlayer.create(stream, {
|
|
currentProtocolOnly: true,
|
|
showBigPlayButton: false,
|
|
aspect: "16:9",
|
|
autoStart: true,
|
|
mute: muted,
|
|
volume: volume,
|
|
sources: [
|
|
{
|
|
label: stream,
|
|
type: 'webrtc',
|
|
file: `wss://${domain}:3334/${app_name}/${stream}`
|
|
}
|
|
]
|
|
});
|
|
|
|
// Set player up to auto-restart if it stops
|
|
// If a streamer goes offline, the player will be reaped
|
|
player.stateManager = manageState;
|
|
player.on("stateChanged", player.stateManager);
|
|
|
|
// Run a resize and sort
|
|
if (named_frames) {
|
|
webcallFrameResize();
|
|
webcallFrameSort();
|
|
}
|
|
}
|
|
|
|
function manageState(data) {
|
|
// This serves one purpose: keep attempting to start a live player if it stops
|
|
if (data.newstate == "error") {
|
|
setTimeout(this.setCurrentSource, 3000, 0);
|
|
}
|
|
}
|
|
|
|
function closePlayer(containerId) {
|
|
// Close the player
|
|
destroyPlayerById(containerId);
|
|
|
|
// Add this ID to the closed player list so we don't reopen it
|
|
if (!disabledPlayers.includes(containerId)) {
|
|
disabledPlayers.push(containerId);
|
|
}
|
|
}
|
|
|
|
function destroyPlayerById(containerId) {
|
|
player = OvenPlayer.getPlayerByContainerId(containerId);
|
|
|
|
// Get our volume settings to save for re-use if this player comes back
|
|
playerVolumeSettings[containerId] = [player.getMute(), player.getVolume()];
|
|
|
|
// Tear down player
|
|
player.remove();
|
|
|
|
// Delete frame
|
|
document.getElementById(`frame_${containerId}`).remove();
|
|
|
|
// Run our resize and sort
|
|
if (named_frames) {
|
|
webcallFrameResize();
|
|
webcallFrameSort();
|
|
}
|
|
}
|
|
|
|
function processStreamList(streams) {
|
|
// Remove any closed player from the list
|
|
disabledPlayers.forEach((i, index) => {
|
|
var removeIndex = streams.indexOf(i);
|
|
if (removeIndex !== -1) {
|
|
streams.splice(removeIndex, 1);
|
|
}
|
|
})
|
|
|
|
// Create any player in the list that doesn't have one
|
|
streams.forEach((i, index) => {
|
|
if (OvenPlayer.getPlayerByContainerId(i) == null) {
|
|
// Check if we have volume settings for this player
|
|
var muted = true;
|
|
var volume = 100;
|
|
if (i in playerVolumeSettings) {
|
|
muted = playerVolumeSettings[i][0];
|
|
volume = playerVolumeSettings[i][1];
|
|
}
|
|
|
|
// Create the player with noted settings or defaults
|
|
createPlayer(i, muted, volume);
|
|
}
|
|
})
|
|
|
|
// Destroy any player not in the list
|
|
var players = OvenPlayer.getPlayerList();
|
|
players.forEach((p, index) => {
|
|
if (!streams.includes(p.getContainerId())) {
|
|
destroyPlayerById(p.getContainerId());
|
|
}
|
|
})
|
|
}
|
|
|
|
function requestStreamList() {
|
|
xhr.open("GET", `https://${domain}/status/default/${app_name}/`)
|
|
xhr.send()
|
|
}
|
|
|
|
// Set up each embed
|
|
function EmprexSetup() {
|
|
// Pre-populate our disabled list if we have a param for it
|
|
window.location.search.substr(1).split("&").forEach((s, index) => {
|
|
tmp = s.split("=");
|
|
if (tmp[0] == "disabled") {
|
|
disabledPlayers = decodeURIComponent(tmp[1]).split(",");
|
|
}
|
|
})
|
|
|
|
// Placeholder if nothing is live
|
|
placeholder = document.createElement("div");
|
|
placeholder.id = "placeholder";
|
|
placeholder.innerHTML = "<img src='/assets/errorlogo.gif'><br />Waiting for a stream to start...";
|
|
document.body.appendChild(placeholder);
|
|
|
|
// Also resize elements to fill the frame
|
|
// We try to debounce this so we're not editing the DOM 100x a second on resize
|
|
resize_timeout = false;
|
|
addEventListener("resize", function() {
|
|
clearTimeout(resize_timeout);
|
|
resize_timeout = setTimeout(webcallFrameResize, 250);
|
|
})
|
|
|
|
// If we enter or exit full screen, call the webcall frame sort
|
|
// The sort will refuse to run if we're in full screen. So we need to sort once we leave it
|
|
addEventListener("fullscreenchange", webcallFrameSort);
|
|
|
|
// Update streams every 5 seconds, and immediately
|
|
requestStreamList();
|
|
setInterval(requestStreamList, 5000);
|
|
}
|