View difference between Paste ID: P9yg5pz1 and Rc1PCzLH
SHOW: | | - or go back to the newest paste.
1
local api_base_url = "https://ipod-2to6magyna-uc.a.run.app/"
2
local version = "2.1"
3
4
local width, height = term.getSize()
5
local tab = 1
6
7
local waiting_for_input = false
8
local last_search = nil
9
local last_search_url = nil
10
local search_results = nil
11
local search_error = false
12
local in_search_result = false
13
local clicked_result = nil
14
15
local playing = false
16
local queue = {}
17
local now_playing = nil
18
local looping = 0
19
local volume = 1.5
20
21
local playing_id = nil
22
local last_download_url = nil
23
local playing_status = 0
24
local is_loading = false
25
local is_error = false;
26
27
local player_handle = nil
28
local start = nil
29
local pcm = nil
30
local size = nil
31
local decoder = require "cc.audio.dfpwm".make_decoder()
32
local needs_next_chunk = 0
33
local buffer
34
35
local speakers = { peripheral.find("speaker") }
36
if #speakers == 0 then
37
	error("No speakers attached. You need to connect a speaker to this computer. If this is an Advanced Noisy Pocket Computer, then this is a bug, and you should try restarting your Minecraft game.", 0)
38
end
39
40
function redrawScreen()
41
	if waiting_for_input then
42
		return
43
	end
44
45
	term.setCursorBlink(false)  -- Make sure cursor is off when redrawing
46
	-- Clear the screen
47
	term.setBackgroundColor(colors.black)
48
	term.clear()
49
50
	--Draw the three top tabs
51
	term.setCursorPos(1,1)
52
	term.setBackgroundColor(colors.gray)
53
	term.clearLine()
54
	
55
	tabs = {" Now Playing ", " Search "}
56
	
57
	for i=1,#tabs,1 do
58
		if tab == i then
59
			term.setTextColor(colors.black)
60
			term.setBackgroundColor(colors.white)
61
		else
62
			term.setTextColor(colors.white)
63
			term.setBackgroundColor(colors.gray)
64
		end
65
		
66
		term.setCursorPos((math.floor((width/#tabs)*(i-0.5)))-math.ceil(#tabs[i]/2)+1, 1)
67
		term.write(tabs[i])
68
	end
69
70
	if tab == 1 then
71
		drawNowPlaying()
72
	elseif tab == 2 then
73
		drawSearch()
74
	end
75
end
76
77
function drawNowPlaying()
78
	if now_playing ~= nil then
79
		term.setBackgroundColor(colors.black)
80
		term.setTextColor(colors.white)
81
		term.setCursorPos(2,3)
82
		term.write(now_playing.name)
83
		term.setTextColor(colors.lightGray)
84
		term.setCursorPos(2,4)
85
		term.write(now_playing.artist)
86
	else
87
		term.setBackgroundColor(colors.black)
88
		term.setTextColor(colors.lightGray)
89
		term.setCursorPos(2,3)
90
		term.write("Not playing")
91
	end
92
93
	if is_loading == true then
94
		term.setTextColor(colors.gray)
95
		term.setBackgroundColor(colors.black)
96
		term.setCursorPos(2,5)
97
		term.write("Loading...")
98
	elseif is_error == true then
99
		term.setTextColor(colors.red)
100
		term.setBackgroundColor(colors.black)
101
		term.setCursorPos(2,5)
102
		term.write("Network error")
103
	end
104
105
	term.setTextColor(colors.white)
106
	term.setBackgroundColor(colors.gray)
107
108
	if playing then
109
		term.setCursorPos(2, 6)
110
		term.write(" Stop ")
111
	else
112
		if now_playing ~= nil or #queue > 0 then
113
			term.setTextColor(colors.white)
114
			term.setBackgroundColor(colors.gray)
115
		else
116
			term.setTextColor(colors.lightGray)
117
			term.setBackgroundColor(colors.gray)
118
		end
119
		term.setCursorPos(2, 6)
120
		term.write(" Play ")
121
	end
122
123
	if now_playing ~= nil or #queue > 0 then
124
		term.setTextColor(colors.white)
125
		term.setBackgroundColor(colors.gray)
126
	else
127
		term.setTextColor(colors.lightGray)
128
		term.setBackgroundColor(colors.gray)
129
	end
130
	term.setCursorPos(2 + 7, 6)
131
	term.write(" Skip ")
132
133
	if looping ~= 0 then
134
		term.setTextColor(colors.black)
135
		term.setBackgroundColor(colors.white)
136
	else
137
		term.setTextColor(colors.white)
138
		term.setBackgroundColor(colors.gray)
139
	end
140
	term.setCursorPos(2 + 7 + 7, 6)
141
	if looping == 0 then
142
		term.write(" Loop Off ")
143
	elseif looping == 1 then
144
		term.write(" Loop Queue ")
145
	else
146
		term.write(" Loop Song ")
147
	end
148
149
	term.setCursorPos(2,8)
150
	paintutils.drawBox(2,8,25,8,colors.gray)
151
	local width = math.floor(24 * (volume / 3) + 0.5)-1
152
	if not (width == -1) then
153
		paintutils.drawBox(2,8,2+width,8,colors.white)
154
	end
155
	if volume < 0.6 then
156
		term.setCursorPos(2+width+2,8)
157
		term.setBackgroundColor(colors.gray)
158
		term.setTextColor(colors.white)
159
	else
160
		term.setCursorPos(2+width-3-(volume == 3 and 1 or 0),8)
161
		term.setBackgroundColor(colors.white)
162
		term.setTextColor(colors.black)
163
	end
164
	term.write(math.floor(100 * (volume / 3) + 0.5) .. "%")
165
166
	if #queue > 0 then
167
		term.setBackgroundColor(colors.black)
168
		for i=1,#queue do
169
			term.setTextColor(colors.white)
170
			term.setCursorPos(2,10 + (i-1)*2)
171
			term.write(queue[i].name)
172
			term.setTextColor(colors.lightGray)
173
			term.setCursorPos(2,11 + (i-1)*2)
174
			term.write(queue[i].artist)
175
		end
176
	end
177
end
178
179
function drawSearch()
180
	-- Search bar
181
	paintutils.drawFilledBox(2,3,width-1,5,colors.lightGray)
182
	term.setBackgroundColor(colors.lightGray)
183
	term.setCursorPos(3,4)
184
	term.setTextColor(colors.black)
185
	term.write(last_search or "Search...")
186
187
	--Search results
188
	if search_results ~= nil then
189
		term.setBackgroundColor(colors.black)
190
		for i=1,#search_results do
191
			term.setTextColor(colors.white)
192
			term.setCursorPos(2,7 + (i-1)*2)
193
			term.write(search_results[i].name)
194
			term.setTextColor(colors.lightGray)
195
			term.setCursorPos(2,8 + (i-1)*2)
196
			term.write(search_results[i].artist)
197
		end
198
	else
199
		term.setCursorPos(2,7)
200
		term.setBackgroundColor(colors.black)
201
		if search_error == true then
202
			term.setTextColor(colors.red)
203
			term.write("Network error")
204
		elseif last_search_url ~= nil then
205
			term.setTextColor(colors.lightGray)
206
			term.write("Searching...")
207
		else
208
			term.setCursorPos(1,7)
209
			term.setTextColor(colors.lightGray)
210
			print("Tip: You can paste YouTube video or playlist links.")
211
		end
212
	end
213
214
	--fullscreen song options
215
	if in_search_result == true then
216
		term.setBackgroundColor(colors.black)
217
		term.clear()
218
		term.setCursorPos(2,2)
219
		term.setTextColor(colors.white)
220
		term.write(search_results[clicked_result].name)
221
		term.setCursorPos(2,3)
222
		term.setTextColor(colors.lightGray)
223
		term.write(search_results[clicked_result].artist)
224
225
		term.setBackgroundColor(colors.gray)
226
		term.setTextColor(colors.white)
227
228
		term.setCursorPos(2,6)
229
		term.clearLine()
230
		term.write("Play now")
231
232
		term.setCursorPos(2,8)
233
		term.clearLine()
234
		term.write("Play next")
235
236
		term.setCursorPos(2,10)
237
		term.clearLine()
238
		term.write("Add to queue")
239
240
		term.setCursorPos(2,13)
241
		term.clearLine()
242
		term.write("Cancel")
243
	end
244
end
245
246
function uiLoop()
247
	redrawScreen()
248
249
	while true do
250
		if waiting_for_input then
251
			parallel.waitForAny(
252
				function()
253
					term.setCursorPos(3,4)
254
					term.setBackgroundColor(colors.white)
255
					term.setTextColor(colors.black)
256
					local input = read()
257
258
					if string.len(input) > 0 then
259
						last_search = input
260
						last_search_url = api_base_url .. "?v=" .. version .. "&search=" .. textutils.urlEncode(input)
261
						http.request(last_search_url)
262
						search_results = nil
263
						search_error = false
264
					else
265
						last_search = nil
266
						last_search_url = nil
267
						search_results = nil
268
						search_error = false
269
					end
270
271
					waiting_for_input = false
272
					os.queueEvent("redraw_screen")
273
				end,
274
				function()
275
					while waiting_for_input do
276
						local event, button, x, y = os.pullEvent("mouse_click")
277
						if y < 3 or y > 5 or x < 2 or x > width-1 then
278
							waiting_for_input = false
279
							os.queueEvent("redraw_screen")
280
							break
281
						end
282
					end
283
				end
284
			)
285
		else
286
			parallel.waitForAny(
287
				function()
288
					local event, button, x, y = os.pullEvent("mouse_click")
289
290
					if button == 1 then
291
						-- Tabs
292
						if in_search_result == false then
293
							if y == 1 then
294
								if x < width/2 then
295
									tab = 1
296
								else
297
									tab = 2
298
								end
299
								redrawScreen()
300
							end
301
						end
302
						
303
						if tab == 2 and in_search_result == false then
304
							-- Search box click
305
							if y >= 3 and y <= 5 and x >= 1 and x <= width-1 then
306
								paintutils.drawFilledBox(2,3,width-1,5,colors.white)
307
								term.setBackgroundColor(colors.white)
308
								waiting_for_input = true
309
							end
310
		
311
							-- Search result click
312
							if search_results then
313
								for i=1,#search_results do
314
									if y == 7 + (i-1)*2 or y == 8 + (i-1)*2 then
315
										term.setBackgroundColor(colors.white)
316
										term.setTextColor(colors.black)
317
										term.setCursorPos(2,7 + (i-1)*2)
318
										term.clearLine()
319
										term.write(search_results[i].name)
320
										term.setTextColor(colors.gray)
321
										term.setCursorPos(2,8 + (i-1)*2)
322
										term.clearLine()
323
										term.write(search_results[i].artist)
324
										sleep(0.2)
325
										in_search_result = true
326
										clicked_result = i
327
										redrawScreen()
328
									end
329
								end
330
							end
331
						elseif tab == 2 and in_search_result == true then
332
							-- Search result menu clicks
333
		
334
							term.setBackgroundColor(colors.white)
335
							term.setTextColor(colors.black)
336
		
337
							if y == 6 then
338
								term.setCursorPos(2,6)
339
								term.clearLine()
340
								term.write("Play now")
341
								sleep(0.2)
342
								in_search_result = false
343
								for _, speaker in ipairs(speakers) do
344
									speaker.stop()
345
									os.queueEvent("playback_stopped")
346
								end
347
								playing = true
348
								is_error = false
349
								playing_id = nil
350
								if search_results[clicked_result].type == "playlist" then
351
									now_playing = search_results[clicked_result].playlist_items[1]
352
									queue = {}
353
									if #search_results[clicked_result].playlist_items > 1 then
354
										for i=2, #search_results[clicked_result].playlist_items do
355
											table.insert(queue, search_results[clicked_result].playlist_items[i])
356
										end
357
									end
358
								else
359
									now_playing = search_results[clicked_result]
360
								end
361
								os.queueEvent("audio_update")
362
							end
363
		
364
							if y == 8 then
365
								term.setCursorPos(2,8)
366
								term.clearLine()
367
								term.write("Play next")
368
								sleep(0.2)
369
								in_search_result = false
370
								if search_results[clicked_result].type == "playlist" then
371
									for i = #search_results[clicked_result].playlist_items, 1, -1 do
372
										table.insert(queue, 1, search_results[clicked_result].playlist_items[i])
373
									end
374
								else
375
									table.insert(queue, 1, search_results[clicked_result])
376
								end
377
								os.queueEvent("audio_update")
378
							end
379
		
380
							if y == 10 then
381
								term.setCursorPos(2,10)
382
								term.clearLine()
383
								term.write("Add to queue")
384
								sleep(0.2)
385
								in_search_result = false
386
								if search_results[clicked_result].type == "playlist" then
387
									for i = 1, #search_results[clicked_result].playlist_items do
388
										table.insert(queue, search_results[clicked_result].playlist_items[i])
389
									end
390
								else
391
									table.insert(queue, search_results[clicked_result])
392
								end
393
								os.queueEvent("audio_update")
394
							end
395
		
396
							if y == 13 then
397
								term.setCursorPos(2,13)
398
								term.clearLine()
399
								term.write("Cancel")
400
								sleep(0.2)
401
								in_search_result = false
402
							end
403
		
404
							redrawScreen()
405
						elseif tab == 1 and in_search_result == false then
406
							-- Now playing tab clicks
407
		
408
							if y == 6 then
409
								-- Play/stop button
410
								if x >= 2 and x < 2 + 6 then
411
									if playing or now_playing ~= nil or #queue > 0 then
412
										term.setBackgroundColor(colors.white)
413
										term.setTextColor(colors.black)
414
										term.setCursorPos(2, 6)
415
										if playing then
416
											term.write(" Stop ")
417
										else 
418
											term.write(" Play ")
419
										end
420
										sleep(0.2)
421
									end
422
									if playing then
423
										playing = false
424
										for _, speaker in ipairs(speakers) do
425
											speaker.stop()
426
											os.queueEvent("playback_stopped")
427
										end
428
										playing_id = nil
429
										is_loading = false
430
										is_error = false
431
										os.queueEvent("audio_update")
432
									elseif now_playing ~= nil then
433
										playing_id = nil
434
										playing = true
435
										is_error = false
436
										os.queueEvent("audio_update")
437
									elseif #queue > 0 then
438
										now_playing = queue[1]
439
										table.remove(queue, 1)
440
										playing_id = nil
441
										playing = true
442
										is_error = false
443
										os.queueEvent("audio_update")
444
									end
445
								end
446
		
447
								-- Skip button
448
								if x >= 2 + 7 and x < 2 + 7 + 6 then
449
									if now_playing ~= nil or #queue > 0 then
450
										term.setBackgroundColor(colors.white)
451
										term.setTextColor(colors.black)
452
										term.setCursorPos(2 + 7, 6)
453
										term.write(" Skip ")
454
										sleep(0.2)
455
		
456
										is_error = false
457
										if playing then
458
											for _, speaker in ipairs(speakers) do
459
												speaker.stop()
460
												os.queueEvent("playback_stopped")
461
											end
462
										end
463
										if #queue > 0 then
464
											if looping == 1 then
465
												table.insert(queue, now_playing)
466
											end
467
											now_playing = queue[1]
468
											table.remove(queue, 1)
469
											playing_id = nil
470
										else
471
											now_playing = nil
472
											playing = false
473
											is_loading = false
474
											is_error = false
475
											playing_id = nil
476
										end
477
										os.queueEvent("audio_update")
478
									end
479
								end
480
		
481
								-- Loop button
482
								if x >= 2 + 7 + 7 and x < 2 + 7 + 7 + 12 then
483
									if looping == 0 then
484
										looping = 1
485
									elseif looping == 1 then
486
										looping = 2
487
									else
488
										looping = 0
489
									end
490
								end
491
							end
492
493
							if y == 8 then
494
								-- Volume slider
495
								if x >= 1 and x < 2 + 24 then
496
									volume = (x - 1) / 24 * 3
497
498
									-- for _, speaker in ipairs(speakers) do
499
									-- 	speaker.stop()
500
									-- 	os.queueEvent("playback_stopped")
501
									-- end
502
									-- playing_id = nil
503
									-- os.queueEvent("audio_update")
504
								end
505
							end
506
507
							redrawScreen()
508
						end
509
					end
510
				end,
511
				function()
512
					local event, button, x, y = os.pullEvent("mouse_drag")
513
514
					if button == 1 then
515
516
						if tab == 1 and in_search_result == false then
517
518
							if y >= 7 and y <= 9 then
519
								-- Volume slider
520
								if x >= 1 and x < 2 + 24 then
521
									volume = (x - 1) / 24 * 3
522
523
									-- for _, speaker in ipairs(speakers) do
524
									-- 	speaker.stop()
525
									-- 	os.queueEvent("playback_stopped")
526
									-- end
527
									-- playing_id = nil
528
									-- os.queueEvent("audio_update")
529
								end
530
							end
531
532
							redrawScreen()
533
						end
534
					end
535
				end,
536
				function()
537
					local event = os.pullEvent("redraw_screen")
538
539
					redrawScreen()
540
				end
541
			)
542
		end
543
	end
544
end
545
546
function audioLoop()
547
	while true do
548
549
		-- AUDIO
550
		if playing and now_playing then
551
			local thisnowplayingid = now_playing.id
552
			if playing_id ~= thisnowplayingid then
553
				playing_id = thisnowplayingid
554
				last_download_url = api_base_url .. "?v=" .. version .. "&id=" .. textutils.urlEncode(playing_id)
555
				playing_status = 0
556
				needs_next_chunk = 1
557
558
				http.request({url = last_download_url, binary = true})
559
				is_loading = true
560
561
				os.queueEvent("redraw_screen")
562
				os.queueEvent("audio_update")
563
			elseif playing_status == 1 and needs_next_chunk == 1 then
564
565
				while true do
566
					local chunk = player_handle.read(size)
567
					if not chunk then
568
						if looping == 2 or (looping == 1 and #queue == 0) then
569
							playing_id = nil
570
						elseif looping == 1 and #queue > 0 then
571
							table.insert(queue, now_playing)
572
							now_playing = queue[1]
573
							table.remove(queue, 1)
574
							playing_id = nil
575
						else
576
							if #queue > 0 then
577
								now_playing = queue[1]
578
								table.remove(queue, 1)
579
								playing_id = nil
580
							else
581
								now_playing = nil
582
								playing = false
583
								playing_id = nil
584
								is_loading = false
585
								is_error = false
586
							end
587
						end
588
589
						os.queueEvent("redraw_screen")
590
591
						player_handle.close()
592
						needs_next_chunk = 0
593
						break
594
					else
595
						if start then
596
							chunk, start = start .. chunk, nil
597
							size = size + 4
598
						end
599
				
600
						buffer = decoder(chunk)
601
						
602
						local fn = {}
603
						for i, speaker in ipairs(speakers) do 
604
							fn[i] = function()
605
								local name = peripheral.getName(speaker)
606
								if #speakers > 1 then
607
									if speaker.playAudio(buffer, volume) then
608
										parallel.waitForAny(
609
											function()
610
												repeat until select(2, os.pullEvent("speaker_audio_empty")) == name
611
											end,
612
											function()
613
												local event = os.pullEvent("playback_stopped")
614
												return
615
											end
616
										)
617
										if not playing or playing_id ~= thisnowplayingid then
618
											return
619
										end
620
									end
621
								else
622
									while not speaker.playAudio(buffer, volume) do
623
										parallel.waitForAny(
624
											function()
625
												repeat until select(2, os.pullEvent("speaker_audio_empty")) == name
626
											end,
627
											function()
628
												local event = os.pullEvent("playback_stopped")
629
												return
630
											end
631
										)
632
										if not playing or playing_id ~= thisnowplayingid then
633
											return
634
										end
635
									end
636
								end
637
								if not playing or playing_id ~= thisnowplayingid then
638
									return
639
								end
640
							end
641
						end
642
						
643
						local ok, err = pcall(parallel.waitForAll, table.unpack(fn))
644
						if not ok then
645
							needs_next_chunk = 2
646
							is_error = true
647
							break
648
						end
649
						
650
						-- If we're not playing anymore, exit the chunk processing loop
651
						if not playing or playing_id ~= thisnowplayingid then
652
							break
653
						end
654
					end
655
				end
656
				os.queueEvent("audio_update")
657
			end
658
		end
659
660
		os.pullEvent("audio_update")
661
	end
662
end
663
664
function httpLoop()
665
	while true do
666
		parallel.waitForAny(
667
			function()
668
				local event, url, handle = os.pullEvent("http_success")
669
670
				if url == last_search_url then
671
					search_results = textutils.unserialiseJSON(handle.readAll())
672
					os.queueEvent("redraw_screen")
673
				end
674
				if url == last_download_url then
675
					is_loading = false
676
					player_handle = handle
677
					start = handle.read(4)
678
					size = 16 * 1024 - 4
679
					playing_status = 1
680
					os.queueEvent("redraw_screen")
681
					os.queueEvent("audio_update")
682
				end
683
			end,
684
			function()
685
				local event, url = os.pullEvent("http_failure")	
686
687
				if url == last_search_url then
688
					search_error = true
689
					os.queueEvent("redraw_screen")
690
				end
691
				if url == last_download_url then
692
					is_loading = false
693
					is_error = true
694
					playing = false
695
					playing_id = nil
696
					os.queueEvent("redraw_screen")
697
					os.queueEvent("audio_update")
698
				end
699
			end
700
		)
701
	end
702
end
703
704
parallel.waitForAny(uiLoop, audioLoop, httpLoop)