fuzzyworld

hawk / files / public / js / controls.js

controls.js

Blame: the commit that last changed each line · file · history

2c1835af 2023-10-19 Pavel Safronov1let pageStatus = {
2 text: '',
3 isError: false
4};
a95a53d1 2023-10-17 Pavel Safronov5setInterval(() => {
6 const statusDiv = document.getElementById('status');
2c1835af 2023-10-19 Pavel Safronov7 statusDiv.innerText = pageStatus.text;
8 if (pageStatus.isError) {
9 statusDiv.classList.add('error');
10 } else {
11 statusDiv.classList.remove('error');
12 }
a95a53d1 2023-10-17 Pavel Safronov13}, 100);
14
2c1835af 2023-10-19 Pavel Safronov15function reportStatus(text, isError = false) {
16 pageStatus.text = text;
17 pageStatus.isError = isError;
a95a53d1 2023-10-17 Pavel Safronov18}
19
20async function pauseOrPlay() {
21 const currentTrack = await getPlayingTrack();
22 if (currentTrack && currentTrack.is_playing) {
23 await pause();
24 } else {
25 await play();
26 }
27 setCurrentPlayingSong();
28}
29
30async function playChosenSong(trackName, trackId) {
2c1835af 2023-10-19 Pavel Safronov31 // clear status
32 reportStatus('')
33
34 // get select element
35 const devicesList = document.getElementById('devices');
36 const deviceId = devicesList.value;
37 if (!deviceId) {
38 reportStatus('Cannot play track, no device selected', true);
39 return;
40 }
41
a95a53d1 2023-10-17 Pavel Safronov42 try {
43 await playTrack(trackId);
44 } catch (e) {
45 console.log("Error playing track")
46 console.log(e);
47 reportStatus(e);
48 }
49 setCurrentPlayingSong();
50}
51
660845f9 2023-10-20 Pavel Safronov52function setTime(minutes, seconds) {
53 document.getElementById('minutes-input').value = minutes;
54 document.getElementById('seconds-input').value = seconds;
55}
56
a95a53d1 2023-10-17 Pavel Safronov57async function searchForMatch() {
58 const searchButton = document.getElementById('search');
59 searchButton.setAttribute('disabled', 'disabled');
60 try {
61 const minutes = document.getElementById('minutes-input').value;
62 const seconds = document.getElementById('seconds-input').value;
63 const tracks = await findMatchingTracks(minutes, seconds);
64 const resultsDiv = document.getElementById('results');
65 if (tracks.length == 0) {
66 resultsDiv.innerHTML = 'No songs found for that duration.';
67 } else {
68 resultsDiv.innerHTML = '';
69 tracks.forEach(track => {
c1610592 2023-10-17 Pavel Safronov70 const album = track.album;
71 const images = album.images;
bd4c370c 2023-10-20 Pavel Safronov72 let largestImage = images[0];
73 const albumArtUrl = largestImage.url;
1e82c571 2023-10-17 Pavel Safronov74 const params = {
a95a53d1 2023-10-17 Pavel Safronov75 name: track.name,
c1610592 2023-10-17 Pavel Safronov76 album: track.album.name,
a95a53d1 2023-10-17 Pavel Safronov77 artist: track.artists[0].name,
78 duration: formatDuration(track.duration_ms),
c1610592 2023-10-17 Pavel Safronov79 albumArtUrl: albumArtUrl,
0c3c57b6 2023-10-18 Pavel Safronov80 songUrl: track.external_urls.spotify,
81 albumUrl: album.external_urls.spotify,
82 artistUrl: track.artists[0].external_urls.spotify,
a95a53d1 2023-10-17 Pavel Safronov83 trackId: track.id,
84 trackHref: track.external_urls.spotify
1e82c571 2023-10-17 Pavel Safronov85 };
86
87 const html = createTemplatedHtml('found-song-item', params);
a95a53d1 2023-10-17 Pavel Safronov88 resultsDiv.innerHTML += html;
89 });
90 }
91 } catch (e) {
92 console.error(e);
93 reportStatus(`Encountered error while searching: ${e}`);
94 } finally {
95 searchButton.removeAttribute('disabled');
96 }
97}
98
99async function findMatchingTracks(minutes, seconds) {
100 reportStatus(`Retrieving albums`);
101 const albums = await getAlbums((state) => {
102 const { processedCount, totalCount } = state;
103 if (totalCount <= 0) {
104 totalCount = "???"
105 }
106 reportStatus(`Retrieving albums: ${processedCount} of ${totalCount}`);
107 });
108
109 const checkTrack = function (track) {
110 const duration = track.duration_ms;
111 const durationInSeconds = Math.floor(duration / 1000);
112 const durationInMinutes = Math.floor(durationInSeconds / 60);
113 const durationInRemainingSeconds = durationInSeconds % 60;
114
115 const isMatching = (durationInMinutes == minutes && durationInRemainingSeconds == seconds);
116 return isMatching;
117 }
118
119 const tracks = await batchGetTracks(albums.map(album => album.album.id), null)
120 const foundTracks = tracks.filter(track => checkTrack(track));
121
122 reportStatus(`Found ${foundTracks.length} track(s)`);
123 return foundTracks;
124}
125
126async function clearDb() {
127 await deleteDb();
128 window.location.reload();
129}
130
131const hideOrShow = function (elementClass, show) {
132 const elements = document.getElementsByClassName(elementClass);
133 for (let i = 0; i < elements.length; i++) {
134 const element = elements[i];
135 if (show) {
136 element.setAttribute('style', 'display: block');
137 } else {
138 element.setAttribute('style', 'display: none');
139 }
140 }
141}
142
143function formatDuration(ms) {
144 // Convert milliseconds to total seconds
145 let totalSeconds = Math.floor(ms / 1000);
146
147 // Calculate minutes and seconds
148 let minutes = Math.floor(totalSeconds / 60);
149 let seconds = totalSeconds % 60;
150
151 // Ensure seconds are always 2 digits
152 let formattedSeconds = seconds.toString().padStart(2, '0');
153
154 // Return formatted string
155 return `${minutes}:${formattedSeconds}`;
156}
157
158async function setCurrentPlayingSong() {
159 const currentSongSpan = document.getElementById('current-song');
160 const currentTrack = await getPlayingTrack();
161 let html;
162 if (!currentTrack) {
1e82c571 2023-10-17 Pavel Safronov163 html = createTemplatedHtml('no-song-playing', {})
a95a53d1 2023-10-17 Pavel Safronov164 } else {
660845f9 2023-10-20 Pavel Safronov165 if (!currentTrack || !currentTrack.item) {
166 return;
167 }
a95a53d1 2023-10-17 Pavel Safronov168 const album = currentTrack.item.album;
169 const item = currentTrack.item;
170 const name = item.name;
171 const progressPercent = currentTrack.progress_ms / item.duration_ms * 100;
172 const images = album.images;
bd4c370c 2023-10-20 Pavel Safronov173 let largestImage = images[0];
174 const albumArtUrl = largestImage.url;
a95a53d1 2023-10-17 Pavel Safronov175 const songUrl = item.external_urls.spotify;
176 const progress = formatDuration(currentTrack.progress_ms);
177 const totalDuration = formatDuration(item.duration_ms);
1e82c571 2023-10-17 Pavel Safronov178 const params = {
a95a53d1 2023-10-17 Pavel Safronov179 name: name,
180 artist: album.artists[0].name,
c1610592 2023-10-17 Pavel Safronov181 album: album.name,
a95a53d1 2023-10-17 Pavel Safronov182 albumArtUrl: albumArtUrl,
183 songUrl: songUrl,
184 progressPercent: progressPercent,
185 progress: progress,
0c3c57b6 2023-10-18 Pavel Safronov186 duration: totalDuration,
187 songUrl: item.external_urls.spotify,
188 albumUrl: album.external_urls.spotify,
189 artistUrl: album.artists[0].external_urls.spotify,
1e82c571 2023-10-17 Pavel Safronov190 };
191
192 html = createTemplatedHtml('playing-song', params)
a95a53d1 2023-10-17 Pavel Safronov193 }
194 currentSongSpan.innerHTML = html;
195}
2c1835af 2023-10-19 Pavel Safronov196
197async function configureDevicesList() {
198 // get select element
199 const devicesList = document.getElementById('devices');
200
201 await updateDevicesList(devicesList);
202 setInterval(async () => {
203 await updateDevicesList(devicesList);
204 }, 1000 * 5);
205 devicesList.addEventListener('change', async () => {
206 const deviceId = devicesList.value;
207 saveDeviceId(deviceId);
208 if (deviceId) {
209 console.info(`Setting device to ${deviceId}`);
210 await transferPlayback(deviceId);
211 }
212 });
213}
214
215async function updateDevicesList(devicesList) {
216 // get available devices
217 const devices = await getAvailableDevices();
218
219 // add all devices to the select as options
220 let activeDeviceId = "";
221 for (let i = 0; i < devices.length; i++) {
222 const device = devices[i];
223 const ignoreDevice = device.is_restricted || device.is_private_session;
224
225 // if the device is active, save its id
226 if (device.is_active) {
227 activeDeviceId = device.id;
228 }
229
230 // check if the device is already in the list
231 const existingOption = devicesList.querySelector(`option[value="${device.id}"]`);
232 if (existingOption) {
233 if (ignoreDevice) {
234 // remove the device from the list
235 devicesList.removeChild(existingOption);
236 } else {
237 // update the name of the device, if it has changed
238 if (existingOption.text != device.name) {
239 existingOption.text = device.name;
240 }
241 // enable the option, if it was disabled
242 if (existingOption.hasAttribute('disabled')) {
243 existingOption.removeAttribute('disabled');
244 }
245 }
246 } else if (!ignoreDevice) {
247 // add the device to the list
248 const option = document.createElement('option');
249 option.value = device.id;
250 option.text = device.name;
251 devicesList.appendChild(option);
252 }
253 }
254
255 // disable options that are no longer available
256 const options = devicesList.querySelectorAll('option');
257 for (let i = 0; i < options.length; i++) {
258 const option = options[i];
259 const existingDevice = devices.find(device => device.id == option.value);
260 if (!existingDevice) {
261 option.setAttribute('disabled', 'disabled');
262 if (option.value == devicesList.value) {
263 devicesList.value = "";
264 devicesList.dispatchEvent(new Event('change'));
265 }
266 }
267 }
268
269 // if there is an active device, select it and save the id
270 if (activeDeviceId) {
271 devicesList.value = activeDeviceId;
272 saveDeviceId(activeDeviceId);
273 } else {
274 const savedDeviceId = getSavedDeviceId();
275 if (savedDeviceId) {
276 devicesList.value = savedDeviceId;
277 }
278 }
279}
280
281function saveDeviceId(deviceId) {
282 localStorage.setItem('active_device_id', deviceId);
283}
284function getSavedDeviceId() {
285 return localStorage.getItem('active_device_id');
286}
660845f9 2023-10-20 Pavel Safronov287
288function configureTimesList() {
289 timesUl = document.getElementById('times');
290 const createLi = (minutes, seconds) => {
291 const li = document.createElement('li');
292 const a = document.createElement('a');
293 a.href = '#';
294 a.onclick = () => {
295 setTime(minutes, seconds);
296 };
297 a.innerText = `${minutes}:${seconds.toFixed(2).toString().padStart(5, '0').slice(0, 2)}`;
298 li.appendChild(a);
299 return li;
300 }
301 timesUl.appendChild(createLi(0, 30));
302 timesUl.appendChild(createLi(1, 0));
303 timesUl.appendChild(createLi(2, 0));
304 timesUl.appendChild(createLi(3, 0));
305 timesUl.appendChild(createLi(4, 0));
306 timesUl.appendChild(createLi(5, 0));
307}