369 lines
9.2 KiB
HTML
369 lines
9.2 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Studio Dashboard</title>
|
|
<style>
|
|
.wrapper {
|
|
display: flex;
|
|
margin: 0 2.5%;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.left-column {
|
|
flex-basis: 80%;
|
|
}
|
|
|
|
.left-column svg {
|
|
max-width: 100vw;
|
|
max-height: 98vh;
|
|
width: auto;
|
|
height: auto;
|
|
display: block;
|
|
margin: 0 auto;
|
|
font-family: dotmatrix;
|
|
}
|
|
|
|
.right-column {
|
|
flex-basis: 20%;
|
|
}
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.content {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.mic {
|
|
font-family: play, sans-serif;
|
|
font-size: 2vw;
|
|
}
|
|
|
|
#mic-status {
|
|
width: 70%;
|
|
min-height: 15px;
|
|
max-height: 100px;
|
|
min-width: 45px;
|
|
max-width: 300px;
|
|
height: 100%;
|
|
border-radius: 5px;
|
|
background-color: red;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: white;
|
|
font-family: play, sans-serif;
|
|
font-size: 2vw;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
#mic-select {
|
|
width: 70%;
|
|
min-height: 20px;
|
|
max-height: 30px;
|
|
min-width: 45px;
|
|
max-width: 300px;
|
|
height: 100%;
|
|
font-family: play, sans-serif;
|
|
font-size: 1vw;
|
|
}
|
|
|
|
#mic-status.active {
|
|
background-color: green;
|
|
}
|
|
|
|
#mic-status.active:before {
|
|
content: '';
|
|
display: none;
|
|
}
|
|
|
|
#mic-status.muted:before {
|
|
content: '';
|
|
display: none;
|
|
}
|
|
|
|
.stream {
|
|
font-family: play, sans-serif;
|
|
font-size: 2vw;
|
|
}
|
|
|
|
#stream-status {
|
|
width: 70%;
|
|
min-height: 15px;
|
|
max-height: 100px;
|
|
min-width: 45px;
|
|
max-width: 300px;
|
|
height: 100%;
|
|
border-radius: 5px;
|
|
background-color: red;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
color: white;
|
|
font-family: play, sans-serif;
|
|
font-size: 2vw;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
#streamer {
|
|
width: 70%;
|
|
min-height: 20px;
|
|
max-height: 30px;
|
|
min-width: 45px;
|
|
max-width: 300px;
|
|
height: 100%;
|
|
font-family: play, sans-serif;
|
|
font-size: 1vw;
|
|
}
|
|
|
|
#stream-status.green {
|
|
background-color: green;
|
|
}
|
|
|
|
#stream-status.red {
|
|
background-color: red;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: dotmatrix;
|
|
src: url(dotrice.regular.otf);
|
|
}
|
|
|
|
body {
|
|
background-color: black;
|
|
}
|
|
|
|
.left-column svg .markers {
|
|
fill: none;
|
|
stroke-width: 7;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: bevel;
|
|
}
|
|
|
|
.left-column svg .five {
|
|
stroke: #228B22;
|
|
stroke-dasharray: 0, 91.6297857297;
|
|
}
|
|
|
|
.left-column svg .sixty {
|
|
stroke: #880808;
|
|
stroke-dasharray: 0, 16.7551608191;
|
|
}
|
|
|
|
.left-column svg .mask {
|
|
stroke: white;
|
|
stroke-dasharray: 1005.3096491488;
|
|
transform: rotateZ(-90deg);
|
|
transform-origin: center;
|
|
-webkit-animation: offset 60s linear infinite;
|
|
animation: offset 60s linear infinite;
|
|
}
|
|
|
|
@-webkit-keyframes offset {
|
|
from {
|
|
stroke-dashoffset: 1013.6872295584;
|
|
}
|
|
to {
|
|
stroke-dashoffset: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes offset {
|
|
from {
|
|
stroke-dashoffset: 1013.6872295584;
|
|
}
|
|
to {
|
|
stroke-dashoffset: 0;
|
|
}
|
|
}
|
|
|
|
svg .hours.minutes, svg .seconds, svg .date {
|
|
fill: #FF0000;
|
|
font-size: 62pt;
|
|
font-family: play, sans-serif;
|
|
text-anchor: middle;
|
|
letter-spacing: -0.05em;
|
|
}
|
|
|
|
svg .seconds {
|
|
font-size:52pt;
|
|
}
|
|
|
|
svg .date {
|
|
font-size:28pt;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="wrapper">
|
|
<div class="left-column">
|
|
<!-- Clock -->
|
|
<svg version="1.1" viewBox="0 0 400 400" preserveAspectRatio="xMidYMax meet">
|
|
<defs>
|
|
<mask id="mask60s">
|
|
<circle class="markers mask" cx="200" cy="200" r="160"/>
|
|
</mask>
|
|
</defs>
|
|
<circle class="markers five" cx="200" cy="200" r="175"/>
|
|
<circle class="markers sixty" cx="200" cy="200" r="160" mask="url(#mask60s)"/>
|
|
<text class="date" x="200" y="135" dx="8">Dec 31</text>
|
|
<text class="hours minutes" x="200" y="225" dx="8"></text>
|
|
<text class="seconds" x="200" y="308" dx="8"></text>
|
|
</svg>
|
|
</div>
|
|
|
|
<div class="right-column">
|
|
<!-- Mic selector and status -->
|
|
<select id="mic-select" class=""></select>
|
|
<p id="mic-status" class="muted">MUTED</p>
|
|
|
|
<!-- Stream selector and status -->
|
|
<input type="text" id="streamer" class="" value="CJ_Games_Live"><br>
|
|
<button id="streamer-conf" class="" onclick="updateStatus()">Confirm</button>
|
|
<p id="stream-status">OFFLINE</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Get the microphone selection dropdown
|
|
const micSelect = document.getElementById("mic-select");
|
|
|
|
// Get the status element
|
|
const micStatus = document.getElementById("mic-status");
|
|
|
|
// Populate the microphone selection dropdown
|
|
navigator.mediaDevices.enumerateDevices().then(devices => {
|
|
const audioDevices = devices.filter(device => device.kind === "audioinput");
|
|
audioDevices.forEach(device => {
|
|
const option = document.createElement("option");
|
|
option.value = device.deviceId;
|
|
option.text = device.label || `Microphone ${micSelect.options.length + 1}`;
|
|
micSelect.appendChild(option);
|
|
});
|
|
});
|
|
|
|
// Check microphone status continuously
|
|
const checkMicStatus = () => {
|
|
const selectedMic = micSelect.value;
|
|
navigator.mediaDevices.getUserMedia({ audio: { deviceId: selectedMic } }).then(stream => {
|
|
// Check if microphone is muted
|
|
const audioCtx = new AudioContext();
|
|
const source = audioCtx.createMediaStreamSource(stream);
|
|
const analyser = audioCtx.createAnalyser();
|
|
source.connect(analyser);
|
|
analyser.fftSize = 32;
|
|
const bufferLength = analyser.frequencyBinCount;
|
|
const dataArray = new Uint8Array(bufferLength);
|
|
const updateStatus = () => {
|
|
analyser.getByteFrequencyData(dataArray);
|
|
const isMuted = dataArray.every(val => val === 0);
|
|
|
|
// Update status text and color
|
|
if (isMuted) {
|
|
micStatus.innerText = "MUTED";
|
|
micStatus.classList.add("muted");
|
|
micStatus.classList.remove("active");
|
|
} else {
|
|
micStatus.innerText = "ACTIVE";
|
|
micStatus.classList.add("active");
|
|
micStatus.classList.remove("muted");
|
|
}
|
|
};
|
|
setInterval(updateStatus, 100);
|
|
}).catch(error => {
|
|
console.error(error);
|
|
});
|
|
};
|
|
|
|
micSelect.addEventListener("change", checkMicStatus);
|
|
checkMicStatus();
|
|
|
|
function checkStatus(streamer) {
|
|
// Fetch the status of the webpage
|
|
fetch(`https://decapi.me/twitch/uptime/${streamer}`)
|
|
.then((response) => response.text())
|
|
.then((data) => {
|
|
// Update the UI based on the status
|
|
if (data.includes("offline")) {
|
|
document.getElementById("stream-status").style.backgroundColor = "red";
|
|
document.getElementById("stream-status").innerHTML = "Offline";
|
|
} else if (data.includes("Error")) {
|
|
document.getElementById("stream-status").style.backgroundColor = "#FAD02C";
|
|
document.getElementById("stream-status").innerHTML = "Error";
|
|
} else {
|
|
document.getElementById("stream-status").style.backgroundColor = "green";
|
|
document.getElementById("stream-status").innerHTML = "Online";
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error:", error);
|
|
});
|
|
}
|
|
|
|
// Check status on page load
|
|
checkStatus(document.getElementById("streamer").value);
|
|
|
|
// Call the checkStatus function every minute
|
|
setInterval(() => {
|
|
checkStatus(document.getElementById("streamer").value);
|
|
}, 60000);
|
|
|
|
function updateStatus() {
|
|
const streamer = document.getElementById("streamer").value;
|
|
checkStatus(streamer);
|
|
}
|
|
|
|
// month names
|
|
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
const weekdayNames = ["Sun","Mon","Tues","Wed","Thur","Fri","Sat"];
|
|
// inital offsets
|
|
const oneSecond = 1000;
|
|
const now = new Date();
|
|
const offsetSeconds = now.getSeconds();
|
|
const offsetMilliSeconds = now.getMilliseconds();
|
|
const initialOffset = offsetSeconds + offsetMilliSeconds / oneSecond;
|
|
// set mask animation delay to account for the inital offset
|
|
document.querySelector( '.markers.mask' ).style.animationDelay = `-${initialOffset}s`
|
|
// update
|
|
const pad = num => `00${num}`.substr(-2);
|
|
const update = () => {
|
|
const now = new Date();
|
|
const hours = now.getHours();
|
|
const minutes = now.getMinutes();
|
|
const seconds = now.getSeconds();
|
|
const weekday = weekdayNames[now.getDay()];
|
|
const day = now.getDate();
|
|
const month = monthNames[now.getMonth()];
|
|
const date = weekday + ' ' + day + ' ' + month;
|
|
const $date = document.querySelector('svg .date');
|
|
const time = `${pad(hours)}:${pad(minutes)}`;
|
|
const $time = document.querySelector('svg .hours.minutes');
|
|
const secs = pad(seconds);
|
|
const $secs = document.querySelector('svg .seconds');
|
|
if (time !== $time.textContent)
|
|
$time.textContent = time;
|
|
if (secs !== $secs.textContent)
|
|
$secs.textContent = secs;
|
|
if (date !== $date.textContent)
|
|
$date.textContent = date;
|
|
};
|
|
// quick update
|
|
update();
|
|
// then start updating at the right time
|
|
setTimeout(
|
|
() => setInterval(update, oneSecond),
|
|
oneSecond - offsetMilliSeconds);
|
|
|
|
</script>
|
|
</body>
|
|
</html> |