StudioClock.svelte 9.39 KB
Newer Older
1
<svelte:options tag="aura-clock"/>
David Trattnig's avatar
David Trattnig committed
2
3
<script>
	import { onMount } from 'svelte';
4
5
6
7
	export let api = "http://localhost:3333/api/v1";
	export let name = "Studio Clock";
	export let logo = "https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png";
	export let logosize  = "100px";
8
	export let noScheduleMessage = "Nothing scheduled!";
9
10

	let time = new Date(); 
11
	let queryCurrent = "/clock";
12
13
14
15

	let data;
	let currentTrack = null;
	let timeLeft;
David Trattnig's avatar
David Trattnig committed
16
17
18
19
20
21
22
23


	// these automatically update when `time`
	// changes, because of the `$:` prefix
	$: hours = time.getHours();
	$: minutes = time.getMinutes();
	$: seconds = time.getSeconds();

24
25
26
	data = fetchApi(queryCurrent);


David Trattnig's avatar
David Trattnig committed
27
28
29
	onMount(() => {
		const interval = setInterval(() => {
			time = new Date();
30
31
			timeLeft -= 1;

32
			if (timeLeft <= 0 || data == null) {
33
34
35
36
				currentTrack = null;
				data = null;
				data = fetchApi(queryCurrent);
			}
37

David Trattnig's avatar
David Trattnig committed
38
39
40
41
42
43
		}, 1000);

		return () => {
			clearInterval(interval);
		};
	});
44
45


46
	async function fetchApi(query) {
47
		let response;
48
49
		let data;

50
		try {
51
			response = await fetch(api+query);
52
53
54
55
		} catch {
			throw new Error("Cannot connect to Engine!");
		}

56
57
58
59
60
61
		try {
			data = await response.json();
		} catch(e) {
			console.log("Error while converting response to JSON!", e);
			throw new Error(response.statusText);
		}
62
63
64
65

		if (response.ok) {
			return data;
		} else {
66
67
68
69
70
			console.log("Error:", data);
			throw new Error(data.message);
		}
	}

71
72
73
	function setCurrentTrack(info) {
		if (currentTrack == null && info != null && info.track != null) {
			currentTrack = info;
74
			
75
			let t = time - Date.parse(info.track_start);
76
77
			t = parseInt(t/1000);

78
			timeLeft = info.track.duration - t - 3; /* FIXME improve timings in coordination with scheduler/trackservice/LQS */
79
			console.log("Current Data", info);
80
81
82
83
		}
		return "";
	}

84
85
	function displayTitle(track) {
		if (track != null) {
86
			let artist = "";
87
88
89
			if (track.artist != "")
				artist = track.artist + " - ";
			return artist + track.title;
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
		}
		return "";
	}

	function formatTime(seconds) {
		if (seconds != null && Number.isInteger(seconds)) {
			let d = new Date(null);
			d.setSeconds(seconds);

			let s;
			if (seconds > 3600)
				s = d.toISOString().substr(11, 8);
			else
				s = d.toISOString().substr(14, 5);
			return s;
		}
		return "";
	}

109
110
111
	function displayShowName(show) {
		let name = ""
		if (show == null || show.name == null) {
112
			name = '<span class="error">'+noScheduleMessage+'</span>';
113
114
		} else {
			name = show.name;
115
		}
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
		return name;
	}

	function displayShowSchedule(schedule) {
		let str = "";

		if (schedule != null && schedule.schedule_start != null) {
			let scheduleStart = ""
			let scheduleEnd = "";

			if (schedule.schedule_start != null) {
				let scheduleStart = new Date(Date.parse(schedule.schedule_start));
				scheduleStart = scheduleStart.toLocaleTimeString(navigator.language, {
					hour: '2-digit',
					minute:'2-digit'
				});
				str = "(" + scheduleStart;
			}
			if (schedule.schedule_end != null) {
				scheduleEnd = new Date(Date.parse(schedule.schedule_end));
				scheduleEnd = scheduleEnd.toLocaleTimeString(navigator.language, {
					hour: '2-digit',
					minute:'2-digit'
				});
				str = str + " - " + scheduleEnd + ")";
			} else {
				str += ")";
			}

		}
		return str;
147
148
	}

149
150
	function isActive(entry, currentTrack) {
		if (currentTrack != null && entry.id == currentTrack.id) {
151
152
			// Scroll to current playlist entry
			location.hash = "#current-playlist-entry";
153
154
155
156
157
			return true;
		}
		return false;
	}

158

159
	
David Trattnig's avatar
David Trattnig committed
160
161
162
</script>

<style>
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

	#station-header {
		width: 100%;
		height: 50px;
		/* margin: 20px 100px; */
		padding: 40px 100px;
	}

	#station-name {
		margin: 0;
    	font-size: 3em;
		line-height: 80px;
	}

	#station-logo {
		align-content: left;
		text-align: right;
		margin: 0 40px 0 10px;
		opacity: 0.5;
		filter: invert(100%);
	}

185
186
	#studio-clock {
		width: calc(100% - 200px);
187
188
		height: calc(100% - 500px);
		margin: 100px;
189
190
191
		display: -webkit-flex;
		display: -ms-flexbox;
		display: flex;
192
		/* border: 2px solid #333; */
193
194
195
196
197
198
199
200
201
202
		flex-direction: row;
	}

	#left-column {
		width: 30%;
		padding: 25px;
	}

	#right-column {
		width: 70%;
203
		padding: 25px 25px 25px 50px;
204
205
	}

206
207
208
209
210
	#current-schedule,
	#next-schedule {
		margin: 0 0 40px 20px;
	}

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
	#next-schedule {
		background-color:rgb(24, 24, 24);
		margin-right: 20px;
		padding: 12px;
	}

	#current-schedule .schedule-title {
		color: #ccc;
		font-size: 3.5em;
	}
	#next-schedule .schedule-title {
		color: gray !important;
		font-size: 2em;
	}

226
	#playlist {
227
228
229
		border: 2px solid #333;
		margin: 20px 20px 40px 20px;
		padding: 10px;
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
		height: calc(80% - 100px);
		overflow-y: auto;
		scroll-behavior: smooth; 
		background-color: #111;
		display: flex;
   		align-items: center;
	}

	#playlist::-webkit-scrollbar-track
	{
		border-radius: 10px;
		-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
		background-color: rgb(77, 73, 73);
	}

	#playlist::-webkit-scrollbar
	{
		width: 12px;
		background-color: rgb(0, 0, 0);
	}

	#playlist::-webkit-scrollbar-thumb
	{
		border-radius: 10px;
		-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
		background-color: rgb(34, 32, 32);
256
257
	}

258
	.playlist-entry {
259
		font-size: 1.5em;
260
261
262
		padding-left: 53px;
	}

263
264
265
266
267
	#current-track * {
		font-size: 1.75em;
		/* margin: 20% 0; */
	}

268
269
	.play-icon,
	.track-time-left {
270
		margin: 25px 50px;
271
272
	}

273
274
275
276
277
	.is-active {
		color: green;
		padding-left: 0;
	}

278
279
	.is-active .track-title::before {
		content: "\00a0\00a0▶\00a0\00a0\00a0";
280
		font-size: larger; 
281
282
283
		color: green;
	}

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
	.is-active .track-time-left {
		color: rgb(43, 241, 36);
		background-color: #222;
		padding: 5px 15px;
	}

	.error {
		font-size: 1.3em;
		color:red;
		height:100%;
		display : flex;
		align-items : center;
		justify-content: center;
	}

David Trattnig's avatar
David Trattnig committed
299
300
301
302
303
304
	svg {
		width: 100%;
		height: 100%;
	}

	.clock-face {
305
306
		stroke: rgb(66, 66, 66);
		fill: black;
David Trattnig's avatar
David Trattnig committed
307
308
309
	}

	.minor {
310
		stroke: rgb(132, 132, 132);
David Trattnig's avatar
David Trattnig committed
311
312
313
314
		stroke-width: 0.5;
	}

	.major {
315
		stroke: rgb(162, 162, 162);
David Trattnig's avatar
David Trattnig committed
316
317
318
319
		stroke-width: 1;
	}

	.hour {
320
		stroke: rgba(255, 255, 255, 0.705);
David Trattnig's avatar
David Trattnig committed
321
322
323
	}

	.minute {
324
		stroke: rgba(255, 255, 255, 0.705);
David Trattnig's avatar
David Trattnig committed
325
326
327
328
329
330
331
	}

	.second, .second-counterweight {
		stroke: rgb(180,0,0);
	}

	.second-counterweight {
332
		/* stroke-width: 3; */
David Trattnig's avatar
David Trattnig committed
333
	}
David Trattnig's avatar
David Trattnig committed
334
335
336
337
338
339

	footer {
		width: 100%;
		text-align: center;
		font-size: 0.8em;
		color: gray;
340
		opacity: 0.5;
David Trattnig's avatar
David Trattnig committed
341
	}
342
343
344
345
346
347
348
349
350
351
352
353
354

	footer a {
		color: gray;
		text-decoration: underline;
	}

	footer #aura-logo {
		/* opacity: 0.5; */
		filter: invert(100%);
		width: 75px;
		margin: 0 0 20px 0;
	}

David Trattnig's avatar
David Trattnig committed
355
356
</style>

357
358
359


<div id="station-header">
360
361
	<img id="station-logo" src="{logo}" style="width:{logosize}" alt="Radio Station" align="left" />
	<h1 id="station-name">{name}</h1>
362
363
</div>

364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
<div id="studio-clock">
	<div id="left-column" class="column">
		<svg viewBox='-50 -50 100 100'>
			<circle class='clock-face' r='48'/>

			<!-- markers -->
			{#each [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55] as minute}
				<line
					class='major'
					y1='35'
					y2='45'
					transform='rotate({30 * minute})'
				/>

				{#each [1, 2, 3, 4] as offset}
					<line
						class='minor'
						y1='42'
						y2='45'
						transform='rotate({6 * (minute + offset)})'
					/>
				{/each}
			{/each}

			<!-- hour hand -->
			<line
				class='hour'
				y1='2'
				y2='-20'
				transform='rotate({30 * hours + minutes / 2})'
			/>
David Trattnig's avatar
David Trattnig committed
395

396
			<!-- minute hand -->
David Trattnig's avatar
David Trattnig committed
397
			<line
398
399
400
401
				class='minute'
				y1='4'
				y2='-30'
				transform='rotate({6 * minutes + seconds / 10})'
David Trattnig's avatar
David Trattnig committed
402
			/>
403
404
405
406
407
408
409
410
411
412
413

			<!-- second hand -->
			<g transform='rotate({6 * seconds})'>
				<line class='second' y1='10' y2='-38'/>
				<line class='second-counterweight' y1='10' y2='2'/>
			</g>
		</svg>
	</div>

	<div id="right-column" class="column">

414
		{#await data}
415
416
417
418
			<div class="spinner-border mt-5" role="status">
				<span class="sr-only">Loading...</span>
			</div>
		{:then value}
419
420
			{setCurrentTrack(value)}
			<!-- <h2>&#9654; Playing</h2> -->
421
422
423
424
425
			
			{#if value.current.show}

			<div id="current-schedule">
				<h1 class="schedule-title">{@html displayShowName(value.current.show)} {displayShowSchedule(value.current)}</h1>
426
427
428
				<!-- <div class="schedule-details">
					<b>Type:</b> {value.current.show.type}, <b>Host:</b> {value.current.show.host}</div>-->
			</div> 
429
430
431
432
433
434
435
			<div id="playlist">

				{#if value.current.playlist}
					<ol>
					{#each value.current.playlist.entries as entry, index}
						{#if isActive(entry, value.track)}
						
436
						<li id="current-playlist-entry" class="playlist-entry is-active">
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
							<!-- <span class="play-icon">&#9654;</span> -->
							<span class="track-title">{displayTitle(entry)}</span>
							<span class="track-time-left">({formatTime(timeLeft)})</span>
						</li>

						{:else}

						<li class="playlist-entry">
							<span class="track-title">{displayTitle(entry)}</span>
							<span class="track-duration">({formatTime(entry.duration)})</span>
						</li>	

						{/if}

					{/each}
					</ol>
				{:else}

455
					<div id="current-track" class="is-active">
456
						<h2>
457
458
							<span class="track-title">{displayTitle(value.track)}</span>
							<span class="track-time-left">{formatTime(timeLeft)}</span>
459
460
461
						</h2>
					</div>
				{/if}
462
463
			</div>
			<div id="next-schedule">
464
				<h3 class="schedule-title">Next: {@html displayShowName(value.next.show)} {displayShowSchedule(value)}</h3>
465
			</div>
466
467

			{/if}
468
		{:catch error}
469
			<div class="error"><p>{error}</p></div>
470
		{/await}
471

472
473
	</div>

David Trattnig's avatar
David Trattnig committed
474
475
</div>

476
477
478
479
480
<footer>
	<a href="https://gitlab.servus.at/aura/meta"><img id="aura-logo" src="https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png" alt="Aura Logo" /></a>
	<br/>
	Studio Clock is powered by <a href="https://gitlab.servus.at/autoradio">Aura Engine</a>
</footer>