Upload files to "/"
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# Studio <a href='https://ko-fi.com/cjgameslive' target='_blank'><img height='35' align='right' style='border:0px;height:46px;' src='https://storage.ko-fi.com/cdn/kofi3.png?v=6' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
|
||||
This was originally a project to create a studio style clock you would find in a radio or television studio.
|
||||
|
||||
Once the basic clock was designed I decided to add an indicator for if the microphone is muted or not, as the browser does not have access to the audio panel it polls the microphone looking for sound and reports when sound is detected as an green `ACTIVE` status and red `MUTED` when it does not detect sound. Your browser should prompt you for access to the microphone to perform this check, my browser only presents the default microphone but there is a drop down in case more than one is available to it.
|
||||
|
||||
As the clock is designed for a studio status I also included a green `LIVE` and red `OFF AIR` status indicator, this polls `https://decapi.me/twitch/uptime/${streamer}` on page load and every 60 seconds there after to checks if `offline` is contained within the response, if it is the status will report a red `OFF AIR`.
|
||||
|
||||
As different iterations were made a text box to type the streamers handle was added, to handle if there are any errors the script will also check if `Error` is returned (if the streamer does not exist the website will respond `[Error from Twitch API] 400: Bad Request - Malformed query params.`) and will respond with a yellow `ERROR` status indicator.
|
||||
|
||||
If `Error` and `offline` are not detected it will respond with a green `LIVE` status indicator (the website returns how long the stream has been live at the point it is checked, such as `1 hour, 17 minutes, 45 seconds`). If the streamer is live then the script polls `https://decapi.me/twitch/game/${streamer}` to get the current category and place this below the `LIVE` status indicator, it then passes this to `https://static-cdn.jtvnw.net/ttv-boxart/` to obtain the box art Twitch presents and place this below the category.
|
||||
|
||||
You can find information on the different iterations and testing below along with some documentation on how you can personalise the page to make it your own. If you find any of this useful and would like to buy us a coffee please use the link at the top of this page.
|
||||
|
||||
## [Clock](Clock.html)
|
||||
A studio style clock with coloured dots at five minute intervals, showing the date and time.
|
||||
|
||||
To change the 5 minute interval dot colour update the `stroke` on row 32 to your desired colour:
|
||||
```
|
||||
stroke: #228B22;
|
||||
```
|
||||
|
||||
To change the seconds interval dot colour update the `stroke` on row 36 to your desired colour:
|
||||
```
|
||||
stroke: #880808;
|
||||
```
|
||||
|
||||
To change the time and date colour update the `fill` on row 64:
|
||||
```
|
||||
fill: #FF0000;
|
||||
font-size: 62pt;
|
||||
font-family: play, sans-serif;
|
||||
text-anchor: middle;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
```
|
||||
|
||||
To change the time and date font update the `font-family` on row 66:
|
||||
```
|
||||
font-family: play, sans-serif;
|
||||
```
|
||||
|
||||
If you would like seporate colours, sizes, or fonts for the time and date you will need to split the above into two:
|
||||
```
|
||||
svg .hours.minutes, svg .seconds {
|
||||
fill: #FF0000;
|
||||
font-size: 62pt;
|
||||
font-family: play, sans-serif;
|
||||
text-anchor: middle;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
svg .date {
|
||||
fill: #FF0000;
|
||||
font-size: 62pt;
|
||||
font-family: play, sans-serif;
|
||||
text-anchor: middle;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
```
|
||||

|
||||
|
||||
## [Mic Check](Mic%20Check.html)
|
||||
A webpage to check if sound is being recieved from your mic or not.
|
||||
|
||||
This code will check if audio is being detected, not if the microphone is muted. It uses the MediaStream API to get access to the user's microphone and listens for audio data using the AnalyserNode interface. If audio is being detected, the volume variable will be greater than the threshold value. If no audio is detected then `MUTED` is displayed, when audio is detected `ACTIVE` is displayed on the page.
|
||||
|
||||

|
||||

|
||||
|
||||
## [Hard Coded Stream Status](Hard%20Coded%20Stream%20Status.html)
|
||||
Display whether a Twitch streamer is online or offline using decapi.
|
||||
|
||||

|
||||

|
||||
|
||||
To change the stream being monitored update row 37 to your desired Twitch streamers handle:
|
||||
```
|
||||
https://decapi.me/twitch/uptime/<Twitch_Streamer>
|
||||
```
|
||||
|
||||
To poll more/less frequently update row 56 to your desired interval in milliseconds (default to once a minute):
|
||||
```
|
||||
setInterval(checkStatus, 60000)
|
||||
```
|
||||
|
||||
## [Selected Streamer Stream Status](Selected%20Streamer%20Stream%20Status.html)
|
||||
Display whether a Twitch streamer is online or offline using decapi, users can enter their own stream to monitor.
|
||||
|
||||
To change the default stream being monitored update `value=` on row 72 to your desired Twitch streamers handle:
|
||||
```
|
||||
<input type="text" id="streamer" value="<Twitch_Streamer>">
|
||||
```
|
||||
|
||||
To poll more/less frequently update row 62 to your desired interval in milliseconds (default to once a minute):
|
||||
```
|
||||
}, 60000);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## [Studio Dashboard V1](Studio%20Dashboard.html)
|
||||
This web page combines [Clock](Clock.html), [Mic Check](Mic%20Check.html), and [Selected Streamer Stream Status](Selected%20Streamer%20Stream%20Status.html) to display a studio clock in the main body of the page, and a mic/stream indicators on the right of the page.
|
||||
|
||||
To change the 5 minute interval dot colour update the `stroke` on row 156 to your desired colour:
|
||||
```
|
||||
stroke: #228B22;
|
||||
```
|
||||
|
||||
To change the seconds interval dot colour update the `stroke` on row 161 to your desired colour:
|
||||
```
|
||||
stroke: #880808;
|
||||
```
|
||||
|
||||
To change the time and date colour update the `fill` on row 193:
|
||||
```
|
||||
fill: #FF0000;
|
||||
```
|
||||
|
||||
To change the time and date font update the `font-family` on row 195:
|
||||
```
|
||||
font-family: play, sans-serif;
|
||||
```
|
||||
|
||||
To change the default stream being monitored update `value=` on row 234 to your desired Twitch streamers handle:
|
||||
```
|
||||
<input type="text" id="streamer" class="" value="<Twitch_Streamer>"><br>
|
||||
```
|
||||
|
||||
To poll more/less frequently update row 322 to your desired interval in milliseconds (default to once a minute):
|
||||
```
|
||||
}, 60000);
|
||||
```
|
||||
|
||||

|
||||
|
||||
To hide the mic selector and streamer selector update their class to `hidden` on rows 230:
|
||||
```
|
||||
<select id="mic-select" class="hidden"></select>
|
||||
```
|
||||
234 and 235:
|
||||
```
|
||||
<input type="text" id="streamer" class="hidden" value="CJ_Games_Live"><br>
|
||||
<button id="streamer-conf" class="hidden" onclick="updateStatus()">Confirm</button>
|
||||
```
|
||||

|
||||
|
||||
## [Studio Dashboard V2](Studio%20Dashboard%20V2.html)
|
||||
This web page combines [Clock](Clock.html), [Mic Check](Mic%20Check.html), and [Selected Streamer Stream Status](Selected%20Streamer%20Stream%20Status.html) to display a studio clock in the main body of the page, and a mic/stream indicators on the right of the page. V2 also adds a button to show or hide the mic select and streamer inputs.
|
||||
|
||||
To change the 5 minute interval dot colour update the `stroke` on row 206 to your desired colour:
|
||||
```
|
||||
stroke: #228B22;
|
||||
```
|
||||
|
||||
To change the seconds interval dot colour update the `stroke` on row 211 to your desired colour:
|
||||
```
|
||||
stroke: #880808;
|
||||
```
|
||||
|
||||
To change the time and date colour update the `fill` on row 243:
|
||||
```
|
||||
fill: #FF0000;
|
||||
```
|
||||
|
||||
To change the time and date font update the `font-family` on row 245:
|
||||
```
|
||||
font-family: play, sans-serif;
|
||||
```
|
||||
|
||||
To change the default stream being monitored update `value=` on row 289 to your desired Twitch streamers handle:
|
||||
```
|
||||
<input type="text" id="streamer" class="hidden" value="<Twitch_Streamer>" style="display: none;">
|
||||
```
|
||||
|
||||
To poll more/less frequently update row 376 to your desired interval in milliseconds (default to once a minute):
|
||||
```
|
||||
}, 60000);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
## [Studio Dashboard V3.2](Studio%20Dashboard%20V3.2.html)
|
||||
This web page combines [Clock](Clock.html), [Mic Check](Mic%20Check.html), and [Selected Streamer Stream Status](Selected%20Streamer%20Stream%20Status.html) to display a studio clock in the main body of the page, and a mic/stream indicators on the right of the page. There is a button to show or hide the mic select and streamer inputs. V3 adds the stream category and art to the dashboard.
|
||||
|
||||
Version 3.1 cleared up some of the CSS from version 3 and ensures the art work resizes with the window. Version 3.2 embeds the fonts in the webpage, changes some of the resizing fonts, and removes some superfluous code from previous iterations.
|
||||
|
||||
To change the 5 minute interval dot colour update the `stroke` on row 218 to your desired colour:
|
||||
```
|
||||
stroke: #4eff4e;
|
||||
```
|
||||
|
||||
To change the seconds interval dot colour update the `stroke` on row 223 to your desired colour:
|
||||
```
|
||||
stroke: #FF0000;
|
||||
```
|
||||
|
||||
To change the time and date colour update the `fill` on row 255:
|
||||
```
|
||||
fill: #FF0000;
|
||||
```
|
||||
|
||||
To change the time and date font update the `font-family` on row 258:
|
||||
```
|
||||
font-family: 'LED Dot-Matrix', sans-serif;
|
||||
```
|
||||
If this font is removed you can also remove rows 4 and 8:
|
||||
```
|
||||
<link href="https://fonts.cdnfonts.com/css/led-dot-matrix" rel="stylesheet">
|
||||
```
|
||||
```
|
||||
@import url('https://fonts.cdnfonts.com/css/led-dot-matrix');
|
||||
```
|
||||
|
||||
To change the default stream being monitored update `value=` on row 305 to your desired Twitch streamers handle:
|
||||
```
|
||||
<input type="text" id="streamer" value="<Twitch_Streamer>" style="display: none;">
|
||||
```
|
||||
|
||||
To poll more/less frequently update row 409 to your desired interval in milliseconds (default to once a minute):
|
||||
```
|
||||
}, 60000);
|
||||
```
|
||||
|
||||
If you want to replace the `Play` font you will need to change rows 46, 76, 105, 111, 118, 142, 155, and 175:
|
||||
```
|
||||
font-family: 'Play', sans-serif;
|
||||
```
|
||||
You can also remove rows 5 and 9:
|
||||
```
|
||||
<link href="https://fonts.googleapis.com/css2?family=Play&display=swap" rel="stylesheet">
|
||||
```
|
||||
```
|
||||
@import url('https://fonts.googleapis.com/css2?family=Play&display=swap');
|
||||
```
|
||||
|
||||
### Large Screen
|
||||

|
||||

|
||||
|
||||
### Small Screen
|
||||

|
||||

|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Status Checker</title>
|
||||
<style>
|
||||
#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;
|
||||
}
|
||||
#status.green {
|
||||
background-color: green;
|
||||
}
|
||||
#status.red {
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
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("status").style.backgroundColor = "red";
|
||||
document.getElementById("status").innerHTML = "Offline";
|
||||
} else if (data.includes("Error")) {
|
||||
document.getElementById("status").style.backgroundColor = "yellow";
|
||||
document.getElementById("status").innerHTML = "Error";
|
||||
} else {
|
||||
document.getElementById("status").style.backgroundColor = "green";
|
||||
document.getElementById("status").innerHTML = "Online";
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
// Check default streamers status in 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);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<label for="streamer">Enter Streamer Name:</label>
|
||||
<input type="text" id="streamer" value="CJ_Games_Live">
|
||||
<button onclick="updateStatus()">Confirm</button>
|
||||
<div id="status">OFFLINE</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Studio Dashboard</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.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;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#settings-button {
|
||||
font-family: play, sans-serif;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#settings-button:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.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: #880808;
|
||||
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: #228B22;
|
||||
}
|
||||
|
||||
#mic-status.active:before {
|
||||
content: '';
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mic-status.muted:before {
|
||||
content: '';
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stream {
|
||||
font-family: play, sans-serif;
|
||||
font-size: 2vw;
|
||||
}
|
||||
|
||||
#title {
|
||||
color: white;
|
||||
font-family: play, sans-serif;
|
||||
font-size: 2vw;
|
||||
}
|
||||
|
||||
#streamer-conf {
|
||||
font-family: play, sans-serif;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
font-size: 1vw;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#stream-status {
|
||||
width: 70%;
|
||||
min-height: 15px;
|
||||
max-height: 100px;
|
||||
min-width: 45px;
|
||||
max-width: 300px;
|
||||
height: 100%;
|
||||
border-radius: 5px;
|
||||
background-color: #880808;
|
||||
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, #streamer {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: 1vw;
|
||||
font-family: play, sans-serif;
|
||||
width: auto;
|
||||
height: 40px;
|
||||
max-width: 100%;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#mic-select {
|
||||
background-color: #f2f2f2;
|
||||
color: #444;
|
||||
max-height: 500px;
|
||||
line-height: 0.75 !important;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
#streamer {
|
||||
background-color: #f2f2f2;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#stream-status.green {
|
||||
background-color: #228B22;
|
||||
}
|
||||
|
||||
#stream-status.red {
|
||||
background-color: #880808;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: dotmatrix;
|
||||
src: url(dotrice.regular.otf);
|
||||
}
|
||||
|
||||
.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">
|
||||
|
||||
<button id="settings-button">Show Settings</button>
|
||||
|
||||
<!-- Mic selector and status -->
|
||||
<p id="title">Mic Status</p>
|
||||
<select id="mic-select" class="hidden" style="display: none;"></select>
|
||||
<p id="mic-status" class="muted">MUTED</p>
|
||||
|
||||
<!-- Stream selector and status -->
|
||||
<p id="title">Stream Status</p>
|
||||
<input type="text" id="streamer" class="hidden" value="CJ_Games_Live" style="display: none;">
|
||||
<button id="streamer-conf" class="hidden" style="display: none;" 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 = "#880808";
|
||||
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 = "#228B22";
|
||||
document.getElementById("stream-status").innerHTML = "Online";
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Settings
|
||||
const settingsButton = document.getElementById("settings-button");
|
||||
const settingsInputs = [document.getElementById("mic-select"), document.getElementById("streamer"), document.getElementById("streamer-conf")];
|
||||
|
||||
settingsButton.addEventListener("click", () => {
|
||||
for (let i = 0; i < settingsInputs.length; i++) {
|
||||
if (settingsInputs[i].style.display === "none") {
|
||||
settingsInputs[i].style.display = "block";
|
||||
settingsButton.textContent = "Hide Settings";
|
||||
} else {
|
||||
settingsInputs[i].style.display = "none";
|
||||
settingsButton.textContent = "Show Settings";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,474 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.cdnfonts.com/css/led-dot-matrix" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Play&display=swap" rel="stylesheet">
|
||||
<title>Studio Dashboard</title>
|
||||
<style>
|
||||
@import url('https://fonts.cdnfonts.com/css/led-dot-matrix');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Play&display=swap');
|
||||
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
margin: 0 2.5%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
flex-basis: 80%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.left-column svg {
|
||||
max-width: 100vw;
|
||||
max-height: 98vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.right-column {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#settings-button {
|
||||
font-family: 'Play', sans-serif;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
font-size: clamp(7px, 1vh, 100%);
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#settings-button:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#mic-status {
|
||||
width: 80%;
|
||||
max-height: 75px;
|
||||
max-width: 300px;
|
||||
height: 5%;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
background-color: #880808;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-family: 'Play', sans-serif;
|
||||
font-size: clamp(0.5rem, -1rem + 7vw, 3rem);
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
#mic-select {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#mic-status.active {
|
||||
background-color: #228B22;
|
||||
}
|
||||
|
||||
#mic-status.active:before {
|
||||
content: '';
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mic-status.muted:before {
|
||||
content: '';
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stream {
|
||||
font-family: 'Play', sans-serif;
|
||||
font-size: clamp(12px, 2vh, 100%);
|
||||
}
|
||||
|
||||
#title {
|
||||
color: white;
|
||||
font-family: 'Play', sans-serif;
|
||||
font-size: clamp(12px, 1.5vw, 100%);
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#streamer-conf {
|
||||
font-family: 'Play', sans-serif;
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
font-size: clamp(7px, 1vh, 100%);
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#stream-status {
|
||||
width: 80%;
|
||||
max-height: 75px;
|
||||
max-width: 300px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
height: 5%;
|
||||
background-color: #880808;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-family: 'Play', sans-serif;
|
||||
font-size: clamp(0.5rem, -1rem + 7vw, 3rem);
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#streaming-category {
|
||||
color: white;
|
||||
font-family: 'Play', sans-serif;
|
||||
font-size: clamp(12px, 1.5vw, 100%);
|
||||
margin-top: 0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#game-art {
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#mic-select, #streamer {
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
font-size: clamp(10px, 1vh, 100%);
|
||||
font-family: 'Play', sans-serif;
|
||||
width: auto;
|
||||
width: 80%;
|
||||
max-height: 75px;
|
||||
max-width: 300px;
|
||||
padding: 10px 15px;
|
||||
line-height: 2;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#mic-select {
|
||||
background-color: #f2f2f2;
|
||||
color: #444;
|
||||
max-height: 500px;
|
||||
line-height: 0.75 !important;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#streamer {
|
||||
background-color: #f2f2f2;
|
||||
color: #444;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#stream-status.green {
|
||||
background-color: #228B22;
|
||||
}
|
||||
|
||||
#stream-status.red {
|
||||
background-color: #880808;
|
||||
}
|
||||
|
||||
.left-column svg .markers {
|
||||
fill: none;
|
||||
stroke-width: 7;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: bevel;
|
||||
}
|
||||
|
||||
.left-column svg .five {
|
||||
stroke: #4eff4e;
|
||||
stroke-dasharray: 0, 91.6297857297;
|
||||
}
|
||||
|
||||
.left-column svg .sixty {
|
||||
stroke: #FF0000;
|
||||
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: 'LED Dot-Matrix', 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">
|
||||
<button id="settings-button">Show Settings</button>
|
||||
|
||||
<!-- Adds line breaks between settings button and statuses -->
|
||||
<br>
|
||||
<br>
|
||||
<!-- Mic selector and status -->
|
||||
<p id="title">Mic Status</p>
|
||||
<select id="mic-select" style="display: none;"></select>
|
||||
<p id="mic-status" class="muted">MUTED</p>
|
||||
|
||||
<!-- Stream selector and status -->
|
||||
<p id="title">Stream Status</p>
|
||||
<input type="text" id="streamer" value="CJ_Games_Live" style="display: none;">
|
||||
<button id="streamer-conf" style="display: none;" onclick="updateStatus()">Confirm</button>
|
||||
<p id="stream-status">OFFLINE</p>
|
||||
<p id="streaming-category"></p>
|
||||
<img src="" id="game-art"></a>
|
||||
</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 = "#880808";
|
||||
document.getElementById("stream-status").innerHTML = "Off Air";
|
||||
document.getElementById("streaming-category").innerHTML = "";
|
||||
document.getElementById("game-art").src = "";
|
||||
} else if (data.includes("Error")) {
|
||||
document.getElementById("stream-status").style.backgroundColor = "#FAD02C";
|
||||
document.getElementById("stream-status").innerHTML = "Error";
|
||||
document.getElementById("streaming-category").innerHTML = "";
|
||||
document.getElementById("game-art").src = "";
|
||||
} else {
|
||||
document.getElementById("stream-status").style.backgroundColor = "#228B22";
|
||||
document.getElementById("stream-status").innerHTML = "Live";
|
||||
fetch(`https://decapi.me/twitch/game/${streamer}`)
|
||||
.then((response) => response.text())
|
||||
.then((data) => {
|
||||
document.getElementById("streaming-category").innerHTML = data;
|
||||
var game = data;
|
||||
var encoded = `https://static-cdn.jtvnw.net/ttv-boxart/` + encodeURI(game) + `-272x380.jpg`;
|
||||
document.getElementById("game-art").src = encoded;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Settings
|
||||
const settingsButton = document.getElementById("settings-button");
|
||||
const settingsInputs = [document.getElementById("mic-select"), document.getElementById("streamer"), document.getElementById("streamer-conf")];
|
||||
|
||||
settingsButton.addEventListener("click", () => {
|
||||
for (let i = 0; i < settingsInputs.length; i++) {
|
||||
if (settingsInputs[i].style.display === "none") {
|
||||
settingsInputs[i].style.display = "block";
|
||||
settingsButton.textContent = "Hide Settings";
|
||||
} else {
|
||||
settingsInputs[i].style.display = "none";
|
||||
settingsButton.textContent = "Show Settings";
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,369 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user