mirror of
https://github.com/radio95-rnt/RadioPlayer.git
synced 2026-07-29 23:39:15 +02:00
276 lines
16 KiB
HTML
276 lines
16 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<meta name="theme-color" content="#0b1220">
|
||
<link rel="preconnect" href="/ws">
|
||
<title>Radio95 Track Monitor</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||
<style>
|
||
:root{
|
||
--panel:#11161c; --muted:#98a0a8; --accent:#3ea6ff; --card:#0b1014;
|
||
--success:#47d18b; --danger:#ff6b6b; --glass: rgba(255,255,255,0.1);
|
||
font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
||
font-weight: 400;
|
||
}
|
||
html,body{height:100%; margin:0; background:linear-gradient(180deg,#071018,#0b1220 60%); color:#eef2f6; overflow: hidden;}
|
||
.container{max-width:1640px; margin:12px auto; padding:14px; display:flex; flex-direction:column; gap:8px}
|
||
.layout{display:flex; gap:10px; height:calc(100vh - 100px);}
|
||
.panel{background:var(--panel); border-radius:10px; padding:6px; box-shadow: 0 6px 18px rgba(2,8,16,0.6); display:flex; flex-direction:column; overflow:hidden}
|
||
.left{flex:1; min-width:420px}
|
||
.right{flex:1.2; display:flex; gap:4px}
|
||
|
||
.frame-title-nc{ font-size:16px; color:var(--muted); margin-bottom:6px; display: flex; justify-content: space-between; align-items: center; user-select: none; padding: 2px 0; transition: color 0.2s ease; }
|
||
.frame-title{ font-size:16px; color:var(--muted); margin-bottom:4px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; padding: 2px 0; transition: color 0.2s ease; }
|
||
.frame-title:hover { color: #fff; }
|
||
.frame-title::after { content: '▼'; font-size: 10px; transition: transform 0.3s ease; opacity: 0.5; margin-left: 8px; }
|
||
|
||
.box {display:flex; flex-direction:column; gap:6px; flex-grow: 1; overflow: hidden; transition: flex 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
|
||
.box.collapsed { flex-grow: 0 !important; flex-shrink: 0; flex-basis: auto !important; }
|
||
.box.collapsed .collapsible-content { display: none; }
|
||
.box.collapsed .frame-title::after { transform: rotate(90deg); }
|
||
.box.collapsed .frame-title { margin-bottom: 0; }
|
||
|
||
.now .track, .next .track {font-weight:400; font-size:14px; word-break:break-all}
|
||
.progress-wrap { display:flex; gap:10px; align-items:center}
|
||
.progress-bar { flex:1; height:10px; background:var(--glass); border-radius:8px; overflow:hidden; position:relative }
|
||
.progress-fill { height:100%; width:0%; background:linear-gradient(45deg,var(--accent), #2aa2ff); }
|
||
.progress-fill.init { opacity: 0;}
|
||
|
||
button, .btn{ background:transparent; border:1px solid rgba(255,255,255,0.06); padding:8px 12px; border-radius:8px; color:inherit; cursor:pointer; transition: background-color 0.15s ease, transform 0.15s ease; }
|
||
button:hover, .btn:hover { background-color: var(--glass); }
|
||
.btn.activated { background:linear-gradient(90deg, #9ed3ff46, #5bbbff2a); border-color: #5bbbff55; }
|
||
.btn.primary{background:linear-gradient(90deg,var(--accent), #6cc7ff); color:#001; border:none; font-weight:600}
|
||
.btn.primary:hover { background:linear-gradient(90deg, #3399ee, #5bbaff); }
|
||
|
||
ul.playlist{list-style:none; padding:6px; margin:0; font-family: "Consolas",monospace; font-size:13px;}
|
||
ul.playlist li{ padding:4px 6px; border-radius:6px; display:flex; justify-content:space-between; gap:8px; transition: background-color 0.2s ease, transform 0.15s ease; }
|
||
ul.playlist li:hover{ background:rgba(255,255,255,0.04); }
|
||
ul.playlist li.current{background:rgba(62,166,255,0.08)}
|
||
ul.playlist li.selected{background:rgba(62, 165, 255, 0.305)}
|
||
ul.playlist li.pointer{background:rgba(255,255,255,0.04)}
|
||
|
||
#queue-ul li:hover {text-decoration: line-through;}
|
||
|
||
.controls{display:flex; gap:4px; margin-top:4px}
|
||
|
||
.listbox{
|
||
height:100%;
|
||
overflow:auto;
|
||
border-radius:6px;
|
||
background:var(--card);
|
||
padding:8px;
|
||
font-size:14px;
|
||
}
|
||
.listbox::-webkit-scrollbar { display: none; }
|
||
|
||
.listbox div.item{ padding:4px; border-radius:6px; transition: background-color 0.2s ease; }
|
||
.listbox div.item:hover{background:rgba(255,255,255,0.04); cursor:pointer}
|
||
.listbox div.item.selected{background:rgba(62, 165, 255, 0.305)}
|
||
|
||
#section-playlist { flex: 2 1; overflow: hidden;}
|
||
#section-queue { flex: 1; display: flex; flex-direction: column; }
|
||
#section-queue .collapsible-content { flex-grow: 1; }
|
||
#section-queue.collapsed .collapsible-content { display: none; }
|
||
#section-playlist.collapsed + #section-queue { flex: 1 1 auto !important; }
|
||
|
||
.small{font-size:12px; color:var(--muted)}
|
||
.footer{display:flex; justify-content:space-between; gap:5px; align-items:center; margin-top:8px; background:var(--panel); border-radius:10px; padding:8px; box-shadow: 0 8px 18px rgba(2,8,16,0.6);}
|
||
.directory-grid {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-grow: 1;
|
||
overflow: hidden;
|
||
align-items: stretch;
|
||
}
|
||
.directory-grid > .box {
|
||
flex: 1;
|
||
min-width: 0; /* important for overflow handling */
|
||
}
|
||
.directory-grid > .box.collapsed {
|
||
flex: 0 0 28px;
|
||
align-self: stretch;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
}
|
||
.directory-grid > .box.collapsed .frame-title {
|
||
writing-mode: vertical-lr;
|
||
transform: rotate(180deg);
|
||
height: 100%;
|
||
margin-bottom: 0;
|
||
justify-content: flex-end; /* arrow at top, text flows down */
|
||
}
|
||
|
||
.directory-grid > .box.collapsed .frame-title::after {
|
||
transform: rotate(90deg); /* arrow still points "right" = open */
|
||
margin-left: 0;
|
||
margin-bottom: 8px;
|
||
}
|
||
#keybinds {display: flex; gap: 10px; align-items: center; white-space: nowrap; }
|
||
#keybinds kbd {background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 4px; padding: 2px 4px; font-family: "Consolas", monospace; font-size: 12px; }
|
||
|
||
#clear-btn { margin: 8px; }
|
||
|
||
#whep-section { flex-shrink: 0; }
|
||
#whep-section .collapsible-content {
|
||
overflow: hidden;
|
||
max-height: 200px;
|
||
transition: max-height 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
#whep-section.collapsed .collapsible-content { max-height: 0; }
|
||
#whep-section.collapsed .frame-title::after { transform: rotate(90deg); }
|
||
.whep-row { display: flex; gap: 8px; align-items: center; }
|
||
#whep-url-input {
|
||
flex: 1; font-size: 12px; font-family: "Consolas", monospace;
|
||
background: var(--card); border: 1px solid rgba(255,255,255,0.08);
|
||
border-radius: 6px; color: #eef2f6; padding: 5px 8px;
|
||
outline: none;
|
||
}
|
||
#whep-url-input:focus { border-color: rgba(62,166,255,0.4); }
|
||
.whep-status-dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: var(--muted); flex-shrink: 0; transition: background 0.3s;
|
||
}
|
||
.whep-status-dot.connecting { background: #f0c040; }
|
||
.whep-status-dot.connected { background: var(--success); box-shadow: 0 0 6px rgba(71,209,139,0.5); }
|
||
.whep-status-dot.error { background: var(--danger); }
|
||
.whep-vol-row { display: flex; align-items: center; gap: 3px; }
|
||
#whep-vol {
|
||
flex: 1; -webkit-appearance: none; appearance: none;
|
||
height: 6px; background: rgba(255,255,255,0.12); border-radius: 4px; outline: none;
|
||
}
|
||
#whep-vol::-webkit-slider-thumb {
|
||
-webkit-appearance: none; width: 6px; height: 6px; border-radius: 4px;
|
||
background: var(--accent);
|
||
}
|
||
#whep-vol::-moz-range-thumb {
|
||
width: 6px; height: 6px; border-radius: 4px;
|
||
background: var(--accent); border: none;
|
||
}
|
||
#whep-log {
|
||
font-family: "Consolas", monospace; font-size: 11px;
|
||
background: var(--card); border-radius: 6px;
|
||
padding: 6px 8px; max-height: 90px; overflow-y: auto;
|
||
scrollbar-width: none; line-height: 1.6;
|
||
}
|
||
#whep-log::-webkit-scrollbar { display: none; }
|
||
#whep-log:empty { display: none; }
|
||
.wlog-ok { color: var(--success); }
|
||
.wlog-err { color: var(--danger); }
|
||
.wlog-info { color: var(--muted); }
|
||
|
||
@media (max-width:800px){
|
||
html, body{overflow: auto; height: auto}
|
||
.container{padding: 10px; margin: 0}
|
||
.layout{flex-direction: column; height: auto}
|
||
.left{min-width:auto}
|
||
.directory-grid {grid-template-columns: 1fr; height: auto; overflow: visible}
|
||
button, .btn {padding: 10px 14px}
|
||
.panel.right { height: auto; }
|
||
#section-playlist, #section-queue { flex: 1 1 auto; }
|
||
#keybinds {display: none;}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="layout">
|
||
<div class="panel left">
|
||
<div class="now">
|
||
<div class="frame-title-nc" style="cursor: default; pointer-events: none;">Now Playing</div>
|
||
<div class="track" id="now-track">Loading...</div>
|
||
<div class="progress-wrap" style="margin-top:8px">
|
||
<div class="progress-bar" title="progress"><div class="progress-fill init" id="prog-fill"></div></div>
|
||
<div class="small" id="time-label">00:00 / 00:00 (00:00)</div>
|
||
</div>
|
||
<div class="small" id="rds-text" style="margin-top:6px; color:var(--muted); font-family:'Consolas',monospace;"></div>
|
||
<div class="controls" style="margin-top:10px">
|
||
<button id="skip-btn" class="btn">⏭ Skip</button>
|
||
<div style="display:flex;align-items:center;gap:4px;border:1px solid rgba(255,255,255,0.06);border-radius:8px;padding:2px 4px">
|
||
<button id="skpn-dec" class="btn" style="padding:4px 10px;border:none">−</button>
|
||
<span style="font-size:13px;min-width:60px;text-align:center">Skip next: <b id="skpn-count">0</b></span>
|
||
<button id="skpn-inc" class="btn" style="padding:4px 10px;border:none">+</button>
|
||
</div>
|
||
<button id="jingle-btn" class="btn">🕭 Jingle</button>
|
||
<button id="skipidx-btn" class="btn">⏭+ Skip in playlist</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="next" style="margin-top:12px">
|
||
<div class="frame-title-nc">Next Track</div>
|
||
<div class="track" id="next-track">Loading...</div>
|
||
</div>
|
||
|
||
<div style="margin-top:12px; display:flex; flex-direction: column; flex-grow: 1; overflow: hidden; gap: 12px;">
|
||
<div class="box" id="section-playlist">
|
||
<div class="frame-title" onclick="toggleSection('section-playlist')">Playlist Viewer</div>
|
||
<div class="collapsible-content listbox" id="playlist-box" style="height: 100%; flex-grow:1">
|
||
<ul class="playlist" id="playlist-ul"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="box" id="section-queue">
|
||
<div class="frame-title" id="queue-title"><div>Queue<button id="clear-btn" class="btn">✖ Clear</button></div></div>
|
||
<div class="collapsible-content listbox" id="queue-box" style="flex-grow: 1;">
|
||
<ul class="playlist" id="queue-ul"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel right">
|
||
<div id="whep-section">
|
||
<div class="frame-title" onclick="toggleSection('whep-section')">
|
||
<span style="display:flex;align-items:center;gap:8px;">
|
||
<span id="whep-dot" class="whep-status-dot"></span>
|
||
Live Player
|
||
</span>
|
||
</div>
|
||
<div class="collapsible-content">
|
||
<div class="whep-row">
|
||
<input id="whep-url-input" type="text" value="https://webrtc.flerken.pl.eu.org:5000/radio/whep" spellcheck="false" />
|
||
<button id="whep-btn" class="btn" onclick="whepToggle()" style="white-space:nowrap;padding:6px 12px;">▶ Connect</button>
|
||
</div>
|
||
<div class="whep-vol-row">
|
||
<input type="range" id="whep-vol" min="0" max="1" step="0.001" value="0.8" oninput="whepSetVol(this.value)" />
|
||
<span class="small" id="whep-vol-out" style="min-width:30px;text-align:right">80.0%</span>
|
||
</div>
|
||
<div id="whep-log"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="directory-grid">
|
||
<div class="box" id="section-dirs">
|
||
<div class="frame-title" onclick="toggleSection('section-dirs')">Directory Browser</div>
|
||
<div class="collapsible-content listbox" id="dirs-box"></div>
|
||
</div>
|
||
|
||
<div class="box" id="section-subdir">
|
||
<div class="frame-title" onclick="toggleSection('section-subdir')">Subdirectory Files</div>
|
||
<div class="collapsible-content listbox" id="subdir-box"></div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:8px; display: flex; gap: 8px;">
|
||
<button id="add-to-queue-btn" class="btn primary" style="flex: 1;">Add to Bottom ▼</button>
|
||
<button id="add-to-queue2-btn" class="btn primary" style="flex: 1;">Add to Top ▲</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer small">
|
||
<div><i class="fa-solid fa-server" style="margin-right: 2px;"></i><span id="server-status">connecting...</span></div>
|
||
<div><i class="fa-solid fa-user" style="margin-right: 2px;"></i><span id="user-count">-</span></div>
|
||
<div class="small" id="keybinds">
|
||
<span><kbd>S</kbd> skip</span>
|
||
<span><kbd>N</kbd> +skip</span>
|
||
<span><kbd>M</kbd> -skip</span>
|
||
<span><kbd>J</kbd> jingle</span>
|
||
<span><kbd>ENTER</kbd> add file</span>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="web.js"></script>
|
||
</body>
|
||
</html>
|