Files
RadioPlayer/modules/web/index.html
T
2026-05-10 10:13:21 +02:00

274 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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)}
.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>