a simple web player for subsonic
tinysub.devins.page
subsonic
navidrome
javascript
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 <title>tinysub</title>
7 <link rel="manifest" href="manifest.json" />
8 <link rel="icon" href="static/tinysub.svg" type="image/svg+xml" />
9 <link rel="stylesheet" href="css/base.css" />
10 <link rel="stylesheet" href="css/components.css" />
11 </head>
12 <body>
13 <!-- icons cache -->
14 <!-- this serves as a lookup table for the build process, which inlines the imgs into a single html -->
15 <div id="icon-cache" style="display: none">
16 <img id="icon-play" src="static/famfamfam-silk/control_play_blue.png" />
17 <img id="icon-pause" src="static/famfamfam-silk/control_pause_blue.png" />
18 <img
19 id="icon-play-next"
20 src="static/famfamfam-silk/control_fastforward_blue.png"
21 />
22 <img id="icon-add" src="static/famfamfam-silk/add.png" />
23 <img id="icon-favorite" src="static/famfamfam-silk/heart.png" />
24 <img id="icon-move-up" src="static/famfamfam-silk/arrow_up.png" />
25 <img id="icon-move-down" src="static/famfamfam-silk/arrow_down.png" />
26 <img id="icon-remove" src="static/famfamfam-silk/cross.png" />
27 <img id="icon-tinysub" src="static/tinysub.svg" />
28 </div>
29 <!-- auth modal -->
30 <div id="auth-modal" class="modal hidden">
31 <div class="modal-content">
32 <h2>tinysub</h2>
33 <form id="login-form">
34 <div class="form-group">
35 <label for="server">server URL:</label>
36 <input
37 type="url"
38 id="server"
39 placeholder="http://localhost:4040"
40 required
41 />
42 </div>
43 <div class="form-group">
44 <label for="username">username:</label>
45 <input id="username" autocomplete="username" required />
46 </div>
47 <div class="form-group">
48 <label for="password">password:</label>
49 <input
50 type="password"
51 id="password"
52 autocomplete="current-password"
53 required
54 />
55 </div>
56 <p>
57 <a href="https://tangled.org/devins.page/tinysub">source code</a>
58 </p>
59 <p id="auth-error" class="danger"></p>
60 <div class="modal-actions">
61 <button type="submit">connect</button>
62 </div>
63 </form>
64 </div>
65 </div>
66 <!-- settings modal -->
67 <div id="settings-modal" class="modal hidden">
68 <div class="modal-content">
69 <h2>settings</h2>
70 <div class="form-group">
71 <label>
72 <input type="checkbox" id="scrobbling-toggle" />
73 scrobbling
74 </label>
75 </div>
76 <div class="form-group">
77 <label>
78 <input type="checkbox" id="dynamic-favicon-toggle" />
79 dynamic favicon
80 </label>
81 </div>
82 <div class="form-group">
83 <label
84 >artist art size: <span id="artist-size-display">16</span>px</label
85 >
86 <input
87 type="range"
88 id="artist-size"
89 min="0"
90 max="256"
91 step="4"
92 value="16"
93 />
94 </div>
95 <div class="form-group">
96 <label
97 >album art size: <span id="album-size-display">32</span>px</label
98 >
99 <input
100 type="range"
101 id="album-size"
102 min="0"
103 max="256"
104 step="4"
105 value="32"
106 />
107 </div>
108 <div class="form-group">
109 <label>song art size: <span id="song-size-display">16</span>px</label>
110 <input
111 type="range"
112 id="song-size"
113 min="0"
114 max="256"
115 step="4"
116 value="16"
117 />
118 </div>
119 <div class="form-group">
120 <label
121 >now playing art size:
122 <span id="now-playing-size-display">128</span>px</label
123 >
124 <input
125 type="range"
126 id="now-playing-size"
127 min="0"
128 max="256"
129 step="32"
130 value="128"
131 />
132 </div>
133 <div class="modal-actions">
134 <button id="clear-db-settings-btn">clear dbs</button>
135 <button id="logout-settings-btn" class="danger">logout</button>
136 <button id="close-settings-btn">close</button>
137 </div>
138 </div>
139 </div>
140 <!-- keyboard help modal -->
141 <div id="keyboard-help-modal" class="modal hidden">
142 <div class="modal-content">
143 <h2>keyboard help</h2>
144 <div class="shortcuts-grid">
145 <div class="shortcut-section-group">
146 <h3>general</h3>
147 <div class="shortcuts-items-grid">
148 <kbd>Ctrl+,</kbd><span>open settings</span> <kbd>?</kbd
149 ><span>open keyboard help</span>
150 </div>
151 </div>
152 <div class="shortcut-section-group">
153 <h3>playback</h3>
154 <div class="shortcuts-items-grid">
155 <kbd>Space</kbd><span>play/pause</span> <kbd>J / L</kbd
156 ><span>seek ±10s</span> <kbd>Alt+J / Alt+L</kbd
157 ><span>previous / next</span> <kbd>Shift+R</kbd
158 ><span>toggle loop</span>
159 </div>
160 </div>
161 <div class="shortcut-section-group">
162 <h3>library</h3>
163 <div class="shortcuts-items-grid">
164 <kbd>↑ / ↓</kbd><span>navigate rows</span> <kbd>Home / End</kbd
165 ><span>first / last row</span> <kbd>Page Up / Page Down</kbd
166 ><span>jump ±10 rows</span> <kbd>Enter</kbd
167 ><span>expand/collapse</span> <kbd>P</kbd
168 ><span>add to queue</span> <kbd>Alt+P</kbd><span>play next</span>
169 <kbd>Esc</kbd><span>clear selection</span>
170 </div>
171 </div>
172 <div class="shortcut-section-group">
173 <h3>queue</h3>
174 <div class="shortcuts-items-grid">
175 <kbd>↑ / ↓</kbd><span>navigate rows</span> <kbd>Home / End</kbd
176 ><span>first / last row</span> <kbd>Page Up / Page Down</kbd
177 ><span>jump ±10 rows</span> <kbd>Alt + ↑ / ↓</kbd
178 ><span>move selected rows</span> <kbd>Enter</kbd
179 ><span>play selected row</span> <kbd>Delete</kbd
180 ><span>remove selected</span> <kbd>Menu</kbd
181 ><span>context menu</span><kbd>Ctrl+A</kbd><span>select all</span
182 ><kbd>Shift + ↑ / ↓</kbd><span>extend selection</span>
183 <kbd>Esc</kbd><span>clear selection</span>
184 </div>
185 </div>
186 </div>
187 <div class="modal-actions">
188 <button id="close-keyboard-help-btn">close</button>
189 </div>
190 </div>
191 </div>
192
193 <header id="playback">
194 <audio id="player" crossorigin="anonymous"></audio>
195 <button id="prev-btn" aria-label="previous">
196 <img
197 src="static/famfamfam-silk/control_rewind_blue.png"
198 alt="previous"
199 />
200 </button>
201 <button id="play-btn" aria-label="play">
202 <img src="static/famfamfam-silk/control_play_blue.png" alt="play" />
203 </button>
204 <button id="next-btn" aria-label="next">
205 <img
206 src="static/famfamfam-silk/control_fastforward_blue.png"
207 alt="next"
208 />
209 </button>
210 <button id="loop-btn" aria-label="loop">
211 <img src="static/famfamfam-silk/control_repeat.png" alt="loop" />
212 </button>
213 <input type="range" id="progress" min="0" max="100" value="0" />
214 <span id="time-display">0:00 / 0:00</span>
215 </header>
216
217 <aside id="sidebar">
218 <div id="library">
219 <h2>
220 <a class="section-toggle" data-section="artists">▸ artists</a>
221 </h2>
222 <div id="artists-tree"></div>
223 <h2>
224 <a class="section-toggle" data-section="playlists">▸ playlists</a>
225 </h2>
226 <div id="playlists-tree"></div>
227 </div>
228 <div id="now-playing">
229 <img
230 id="cover-art"
231 src="static/tinysub.svg"
232 alt="cover"
233 loading="lazy"
234 />
235 <div id="track-info">
236 <div id="track-title">no track playing</div>
237 <div id="track-artist"></div>
238 <div id="track-lyric"></div>
239 </div>
240 </div>
241 </aside>
242
243 <main id="queue">
244 <table id="queue-table">
245 <thead>
246 <tr>
247 <th></th>
248 <th>title</th>
249 <th>artist</th>
250 <th>album</th>
251 <th>duration</th>
252 <th><span id="queue-count">0</span> songs</th>
253 </tr>
254 </thead>
255 <tbody id="queue-list"></tbody>
256 </table>
257 </main>
258
259 <footer id="footer">
260 <span
261 ><a href="https://tangled.org/devins.page/tinysub">tinysub</a>
262 1.8.3</span
263 >
264 <div id="actions">
265 <button id="clear-btn" aria-label="clear">
266 <img src="static/famfamfam-silk/cross.png" alt="clear" />
267 <span>clear</span>
268 </button>
269 <button id="sort-btn" aria-label="sort">
270 <img src="static/famfamfam-silk/arrow_switch.png" alt="sort" />
271 <span>sort</span>
272 </button>
273 <button id="settings-btn" aria-label="settings">
274 <img src="static/famfamfam-silk/cog.png" alt="settings" />
275 <span>settings</span>
276 </button>
277 </div>
278 </footer>
279
280 <script src="js/strings/en.js"></script>
281 <script src="js/constants.js"></script>
282 <script src="js/validation.js"></script>
283 <script src="js/selection.js"></script>
284 <script src="js/api.js"></script>
285 <script src="js/state.js"></script>
286 <script src="js/image-cache.js"></script>
287 <script src="js/queue-storage.js"></script>
288 <script src="js/virtual-scroll.js"></script>
289 <script src="js/queue.js"></script>
290 <script src="js/library.js"></script>
291 <script src="js/modal.js"></script>
292 <script src="js/input.js"></script>
293 <script src="js/contextmenu.js"></script>
294 <script src="js/ui.js"></script>
295 <script src="js/settings.js"></script>
296 <script src="js/auth.js"></script>
297 <script src="js/spark-md5.js"></script>
298 <script src="js/player.js"></script>
299 <script src="js/draggable.js"></script>
300 <script src="js/lyrics.js"></script>
301 <script src="js/events.js"></script>
302 </body>
303</html>