robathome

MC_jukebox - Player

Oct 27th, 2025 (edited)
177
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 26.10 KB | None | 0 0
  1. local MODE = "ui-mirror-v1"
  2.  
  3. -- Open Rednet on any wireless modem
  4. local function _openRednetServer()
  5. local sideFound = nil
  6. for _, s in ipairs(rs.getSides()) do
  7. if peripheral.getType(s) == "modem" then sideFound = s break end
  8. end
  9. if not sideFound then
  10. local m = peripheral.find("modem", function(_, m) return m.isWireless and m.isWireless() end)
  11. if m then sideFound = peripheral.getName(m) end
  12. end
  13. assert(sideFound, "Wireless modem required on server")
  14. if not rednet.isOpen(sideFound) then rednet.open(sideFound) end
  15. return sideFound
  16. end
  17. _openRednetServer()
  18.  
  19. -- Track UI clients and speaker-node clients
  20. local clients = {} -- [id] = {w=..., h=...}
  21. local spk_clients = {} -- [id] = true
  22.  
  23. -- Broadcast helpers
  24. local function cast(pkt) -- to UI mirror/clients
  25. for id in pairs(clients) do rednet.send(id, pkt, MODE) end
  26. end
  27. local function castSpk(pkt) -- to remote speaker nodes
  28. for id in pairs(spk_clients) do rednet.send(id, pkt, MODE) end
  29. end
  30.  
  31. -- term/paintutils mirror
  32. local serverW, serverH = term.getSize()
  33. local realTerm = term.native()
  34. local MirrorTerm = {}
  35. for k,v in pairs(realTerm) do MirrorTerm[k] = v end
  36. function MirrorTerm.write(t) cast({op="write", s=t}); return realTerm.write(t) end
  37. function MirrorTerm.blit(t,f,b) cast({op="blit", t=t,f=f,b=b}); return realTerm.blit(t,f,b) end
  38. function MirrorTerm.clear() cast({op="clear"}); return realTerm.clear() end
  39. function MirrorTerm.clearLine() cast({op="clearLine"}); return realTerm.clearLine() end
  40. function MirrorTerm.setCursorPos(x,y) cast({op="setCursorPos",x=x,y=y}); return realTerm.setCursorPos(x,y) end
  41. function MirrorTerm.setCursorBlink(b) cast({op="setCursorBlink",b=b}); return realTerm.setCursorBlink(b) end
  42. function MirrorTerm.setTextColor(c) cast({op="setTextColor",c=c}); return realTerm.setTextColor(c) end
  43. function MirrorTerm.setBackgroundColor(c) cast({op="setBackgroundColor",c=c}); return realTerm.setBackgroundColor(c) end
  44. function MirrorTerm.getSize() return serverW, serverH end
  45. term.redirect(MirrorTerm)
  46.  
  47. local realPaint = paintutils
  48. paintutils = {}
  49. for k,v in pairs(realPaint) do paintutils[k] = v end
  50. function paintutils.drawBox(x1,y1,x2,y2,col)
  51. cast({op="drawBox",x1=x1,y1=y1,x2=x2,y2=y2,col=col})
  52. return realPaint.drawBox(x1,y1,x2,y2,col)
  53. end
  54. function paintutils.drawFilledBox(x1,y1,x2,y2,col)
  55. cast({op="drawFilledBox",x1=x1,y1=y1,x2=x2,y2=y2,col=col})
  56. return realPaint.drawFilledBox(x1,y1,x2,y2,col)
  57. end
  58.  
  59. -- Build compact status for controllers
  60. local function _copyQueue()
  61. local q, out = rawget(_G, "queue"), {}
  62. if type(q) == "table" then
  63. for i,item in ipairs(q) do
  64. out[i] = { name = item.name, artist = item.artist }
  65. end
  66. end
  67. return out
  68. end
  69. local function _copyResults()
  70. local r, out = rawget(_G, "search_results"), nil
  71. if type(r) == "table" then
  72. out = {}
  73. for i,item in ipairs(r) do
  74. out[i] = { name = item.name, artist = item.artist, type = item.type or "track" }
  75. end
  76. end
  77. return out
  78. end
  79. local function buildStatus()
  80. local now = rawget(_G, "now_playing")
  81. return {
  82. op = "STATUS",
  83. now = now and { name = now.name, artist = now.artist } or nil,
  84. playing = not not rawget(_G, "playing"),
  85. looping = tonumber(rawget(_G, "looping")) or 0,
  86. volume = tonumber(rawget(_G, "volume")) or 1.0,
  87. is_loading = not not rawget(_G, "is_loading"),
  88. is_error = not not rawget(_G, "is_error"),
  89. queue = _copyQueue(),
  90. last_search = rawget(_G, "last_search"),
  91. search_results = _copyResults(),
  92. }
  93. end
  94.  
  95. -- Remote audio streamer (send DFPWM chunks to speaker nodes)
  96. local function netAudioSend(dfpwm_str, vol)
  97. if not dfpwm_str or #dfpwm_str == 0 then return end
  98. if next(spk_clients) == nil then return end
  99. local max = 4096
  100. local seq = os.epoch("utc")
  101. castSpk({op="AUDIO_BEGIN", seq=seq, vol=vol})
  102. local i = 1
  103. while i <= #dfpwm_str do
  104. castSpk({op="AUDIO_PART", seq=seq, data=dfpwm_str:sub(i, i+max-1)})
  105. i = i + max
  106. end
  107. castSpk({op="AUDIO_END", seq=seq})
  108. end
  109.  
  110. -- Safe speaker stop
  111. local function _stopSpeakers()
  112. local sp = rawget(_G, "speakers")
  113. if type(sp) == "table" then
  114. for _, s in ipairs(sp) do pcall(function() s.stop() end) end
  115. os.queueEvent("playback_stopped")
  116. end
  117. end
  118.  
  119. -- Execute controller commands
  120. local function execCommand(msg)
  121. local cmd = msg.cmd
  122. if cmd == "PLAY_TOGGLE" then
  123. if _G.playing then
  124. _G.playing = false
  125. _stopSpeakers()
  126. _G.playing_id = nil
  127. _G.is_loading = false
  128. _G.is_error = false
  129. elseif _G.now_playing ~= nil then
  130. _G.playing_id = nil
  131. _G.playing = true
  132. _G.is_error = false
  133. elseif type(_G.queue) == "table" and #_G.queue > 0 then
  134. _G.now_playing = _G.queue[1]
  135. table.remove(_G.queue, 1)
  136. _G.playing_id = nil
  137. _G.playing = true
  138. _G.is_error = false
  139. end
  140. os.queueEvent("audio_update")
  141. elseif cmd == "STOP" then
  142. _G.playing = false
  143. _stopSpeakers()
  144. _G.playing_id = nil
  145. _G.is_loading = false
  146. _G.is_error = false
  147. os.queueEvent("audio_update")
  148. elseif cmd == "SKIP" then
  149. if _G.playing then _stopSpeakers() end
  150. if type(_G.queue) == "table" and #_G.queue > 0 then
  151. if _G.looping == 1 and _G.now_playing then
  152. table.insert(_G.queue, _G.now_playing)
  153. end
  154. _G.now_playing = _G.queue[1]
  155. table.remove(_G.queue, 1)
  156. _G.playing_id = nil
  157. else
  158. _G.now_playing = nil
  159. _G.playing = false
  160. _G.is_loading = false
  161. _G.is_error = false
  162. _G.playing_id = nil
  163. end
  164. os.queueEvent("audio_update")
  165. elseif cmd == "LOOP_NEXT" then
  166. _G.looping = ((_G.looping or 0) + 1) % 3
  167. elseif cmd == "VOL_SET" then
  168. local v = tonumber(msg.value)
  169. if v then _G.volume = math.max(0, math.min(3, v)) end
  170. elseif cmd == "SEARCH" and type(msg.q) == "string" then
  171. local api = rawget(_G, "api_base_url")
  172. local ver = rawget(_G, "version")
  173. if api and ver then
  174. _G.last_search = msg.q
  175. _G.last_search_url = api .. "?v=" .. ver .. "&search=" .. textutils.urlEncode(_G.last_search)
  176. http.request(_G.last_search_url)
  177. _G.search_results = nil
  178. _G.search_error = false
  179. end
  180. elseif cmd == "PLAY_NOW" and tonumber(msg.idx) and type(_G.search_results) == "table" then
  181. local i = tonumber(msg.idx)
  182. local item = _G.search_results[i]
  183. if item then
  184. _stopSpeakers()
  185. _G.playing = true
  186. _G.is_error = false
  187. _G.playing_id = nil
  188. if item.type == "playlist" and item.playlist_items and #item.playlist_items > 0 then
  189. _G.now_playing = item.playlist_items[1]
  190. _G.queue = {}
  191. for j = 2, #item.playlist_items do table.insert(_G.queue, item.playlist_items[j]) end
  192. else
  193. _G.now_playing = item
  194. end
  195. os.queueEvent("audio_update")
  196. end
  197. elseif cmd == "PLAY_NEXT" and tonumber(msg.idx) and type(_G.search_results) == "table" then
  198. local i = tonumber(msg.idx)
  199. local item = _G.search_results[i]
  200. if item then
  201. _G.queue = _G.queue or {}
  202. if item.type == "playlist" and item.playlist_items then
  203. for j = #item.playlist_items, 1, -1 do table.insert(_G.queue, 1, item.playlist_items[j]) end
  204. else
  205. table.insert(_G.queue, 1, item)
  206. end
  207. os.queueEvent("audio_update")
  208. end
  209. elseif cmd == "ADD_QUEUE" and tonumber(msg.idx) and type(_G.search_results) == "table" then
  210. local i = tonumber(msg.idx)
  211. local item = _G.search_results[i]
  212. if item then
  213. _G.queue = _G.queue or {}
  214. if item.type == "playlist" and item.playlist_items then
  215. for j = 1, #item.playlist_items do table.insert(_G.queue, item.playlist_items[j]) end
  216. else
  217. table.insert(_G.queue, item)
  218. end
  219. os.queueEvent("audio_update")
  220. end
  221. elseif cmd == "PLAY_FROM_QUEUE_IDX" and tonumber(msg.idx) and type(_G.queue) == "table" then
  222. local i = tonumber(msg.idx)
  223. if i >= 1 and i <= #_G.queue then
  224. _stopSpeakers()
  225. _G.now_playing = _G.queue[i]
  226. table.remove(_G.queue, i)
  227. _G.playing_id = nil
  228. _G.playing = true
  229. _G.is_error = false
  230. os.queueEvent("audio_update")
  231. end
  232. elseif cmd == "REMOVE_QUEUE_IDX" and tonumber(msg.idx) and type(_G.queue) == "table" then
  233. local i = tonumber(msg.idx)
  234. if i >= 1 and i <= #_G.queue then
  235. table.remove(_G.queue, i)
  236. os.queueEvent("audio_update")
  237. end
  238. end
  239. os.queueEvent("redraw_screen")
  240. end
  241.  
  242. -- Listeners
  243. function mirrorHelloLoop()
  244. cast({op="screen", w=serverW, h=serverH})
  245. while true do
  246. local id, msg, proto = rednet.receive(MODE)
  247. if proto == MODE and type(msg) == "table" then
  248. if msg.op == "HELLO" and msg.role == "speaker" then
  249. spk_clients[id] = true
  250. rednet.send(id, { op="HELLO_ACK_SPK" }, MODE)
  251. elseif msg.op == "HELLO" and msg.w and msg.h then
  252. clients[id] = { w = msg.w, h = msg.h }
  253. rednet.send(id, { op="HELLO_ACK", w=serverW, h=serverH }, MODE)
  254. os.queueEvent("redraw_screen")
  255. elseif msg.op == "CLICK" then
  256. os.queueEvent("mouse_click", msg.button or 1, msg.x, msg.y)
  257. elseif msg.op == "DRAG" then
  258. os.queueEvent("mouse_drag", msg.button or 1, msg.x, msg.y)
  259. elseif msg.op == "SCROLL" then
  260. os.queueEvent("mouse_scroll", msg.dir or 0, msg.x, msg.y)
  261. elseif msg.op == "CMD" then
  262. execCommand(msg)
  263. elseif msg.op == "STATUS_REQ" then
  264. rednet.send(id, buildStatus(), MODE)
  265. end
  266. end
  267. end
  268. end
  269.  
  270. function mirrorStatusLoop()
  271. while true do
  272. cast(buildStatus())
  273. sleep(0.5)
  274. end
  275. end
  276. -- =====================
  277. -- END PATCH
  278. -- =====================
  279.  
  280.  
  281. -- =====================
  282. -- ORIGINAL SERVER PROGRAM (with minor edits to integrate streaming + threads)
  283. -- =====================
  284. local api_base_url = "https://ipod-2to6_MAGYNA-uc.a.run.app/"
  285. local version = "2.1"
  286.  
  287. local width, height = term.getSize()
  288. local tab = 1
  289.  
  290. local waiting_for_input = false
  291. local last_search = nil
  292. local last_search_url = nil
  293. local search_results = nil
  294. local search_error = false
  295. local in_search_result = false
  296. local clicked_result = nil
  297.  
  298. local playing = false
  299. local queue = {}
  300. local now_playing = nil
  301. local looping = 0
  302. local volume = 1.5
  303.  
  304. local playing_id = nil
  305. local last_download_url = nil
  306. local playing_status = 0
  307. local is_loading = false
  308. local is_error = false;
  309.  
  310. local player_handle = nil
  311. local start = nil
  312. local pcm = nil
  313. local size = nil
  314. local decoder = require "cc.audio.dfpwm".make_decoder()
  315. local needs_next_chunk = 0
  316. local buffer
  317.  
  318. local speakers = { peripheral.find("speaker") }
  319. if #speakers == 0 then
  320. print("No local speakers attached; streaming only.")
  321. end
  322.  
  323. function redrawScreen()
  324. if waiting_for_input then return end
  325. term.setCursorBlink(false); term.setBackgroundColor(colors.black); term.clear()
  326. term.setCursorPos(1,1); term.setBackgroundColor(colors.gray); term.clearLine()
  327. tabs = {" Now Playing ", " Search "}
  328. for i=1,#tabs,1 do
  329. if tab == i then term.setTextColor(colors.black) term.setBackgroundColor(colors.white)
  330. else term.setTextColor(colors.white) term.setBackgroundColor(colors.gray) end
  331. term.setCursorPos((math.floor((width/#tabs)*(i-0.5)))-math.ceil(#tabs[i]/2)+1, 1); term.write(tabs[i])
  332. end
  333. if tab == 1 then drawNowPlaying() elseif tab == 2 then drawSearch() end
  334. end
  335.  
  336. function drawNowPlaying()
  337. if now_playing ~= nil then
  338. term.setBackgroundColor(colors.black); term.setTextColor(colors.white); term.setCursorPos(2,3); term.write(now_playing.name)
  339. term.setTextColor(colors.lightGray); term.setCursorPos(2,4); term.write(now_playing.artist)
  340. else
  341. term.setBackgroundColor(colors.black); term.setTextColor(colors.lightGray); term.setCursorPos(2,3); term.write("Not playing")
  342. end
  343. if is_loading == true then term.setTextColor(colors.gray); term.setBackgroundColor(colors.black); term.setCursorPos(2,5); term.write("Loading...")
  344. elseif is_error == true then term.setTextColor(colors.red); term.setBackgroundColor(colors.black); term.setCursorPos(2,5); term.write("Network error") end
  345. term.setTextColor(colors.white); term.setBackgroundColor(colors.gray)
  346. if playing then term.setCursorPos(2, 6); term.write(" Stop ") else
  347. if now_playing ~= nil or #queue > 0 then term.setTextColor(colors.white) term.setBackgroundColor(colors.gray) else term.setTextColor(colors.lightGray) term.setBackgroundColor(colors.gray) end
  348. term.setCursorPos(2, 6); term.write(" Play ")
  349. end
  350. if now_playing ~= nil or #queue > 0 then term.setTextColor(colors.white) term.setBackgroundColor(colors.gray) else term.setTextColor(colors.lightGray) term.setBackgroundColor(colors.gray) end
  351. term.setCursorPos(2 + 7, 6); term.write(" Skip ")
  352. if looping ~= 0 then term.setTextColor(colors.black) term.setBackgroundColor(colors.white) else term.setTextColor(colors.white) term.setBackgroundColor(colors.gray) end
  353. term.setCursorPos(2 + 7 + 7, 6)
  354. if looping == 0 then term.write(" Loop Off ") elseif looping == 1 then term.write(" Loop Queue ") else term.write(" Loop Song ") end
  355. term.setCursorPos(2,8); paintutils.drawBox(2,8,25,8,colors.gray)
  356. local wv = math.floor(24 * (volume / 3) + 0.5)-1
  357. if not (wv == -1) then paintutils.drawBox(2,8,2+wv,8,colors.white) end
  358. if volume < 0.6 then term.setCursorPos(2+wv+2,8); term.setBackgroundColor(colors.gray); term.setTextColor(colors.white)
  359. else term.setCursorPos(2+wv-3-(volume == 3 and 1 or 0),8); term.setBackgroundColor(colors.white); term.setTextColor(colors.black) end
  360. term.write(math.floor(100 * (volume / 3) + 0.5) .. "%")
  361. if #queue > 0 then
  362. term.setBackgroundColor(colors.black)
  363. for i=1,#queue do term.setTextColor(colors.white); term.setCursorPos(2,10 + (i-1)*2); term.write(queue[i].name); term.setTextColor(colors.lightGray); term.setCursorPos(2,11 + (i-1)*2); term.write(queue[i].artist) end
  364. end
  365. end
  366.  
  367. function drawSearch()
  368. paintutils.drawFilledBox(2,3,width-1,5,colors.lightGray); term.setBackgroundColor(colors.lightGray); term.setCursorPos(3,4); term.setTextColor(colors.black); term.write(last_search or "Search...")
  369. if search_results ~= nil then
  370. term.setBackgroundColor(colors.black)
  371. for i=1,#search_results do term.setTextColor(colors.white); term.setCursorPos(2,7 + (i-1)*2); term.write(search_results[i].name); term.setTextColor(colors.lightGray); term.setCursorPos(2,8 + (i-1)*2); term.write(search_results[i].artist) end
  372. else
  373. term.setCursorPos(2,7); term.setBackgroundColor(colors.black)
  374. if search_error == true then term.setTextColor(colors.red); term.write("Network error")
  375. elseif last_search_url ~= nil then term.setTextColor(colors.lightGray); term.write("Searching...")
  376. else term.setCursorPos(1,7); term.setTextColor(colors.lightGray); print("Tip: You can paste YouTube video or playlist links.") end
  377. end
  378. if in_search_result == true then
  379. term.setBackgroundColor(colors.black); term.clear(); term.setCursorPos(2,2); term.setTextColor(colors.white); term.write(search_results[clicked_result].name)
  380. term.setCursorPos(2,3); term.setTextColor(colors.lightGray); term.write(search_results[clicked_result].artist)
  381. term.setBackgroundColor(colors.gray); term.setTextColor(colors.white)
  382. term.setCursorPos(2,6); term.clearLine(); term.write("Play now")
  383. term.setCursorPos(2,8); term.clearLine(); term.write("Play next")
  384. term.setCursorPos(2,10); term.clearLine(); term.write("Add to queue")
  385. term.setCursorPos(2,13); term.clearLine(); term.write("Cancel")
  386. end
  387. end
  388.  
  389. function uiLoop()
  390. redrawScreen()
  391. while true do
  392. if waiting_for_input then
  393. parallel.waitForAny(
  394. function()
  395. term.setCursorPos(3,4); term.setBackgroundColor(colors.white); term.setTextColor(colors.black)
  396. local input = read()
  397. if string.len(input) > 0 then
  398. last_search = input; last_search_url = api_base_url .. "?v=" .. version .. "&search=" .. textutils.urlEncode(input)
  399. http.request(last_search_url); search_results = nil; search_error = false
  400. else last_search = nil; last_search_url = nil; search_results = nil; search_error = false end
  401. waiting_for_input = false; os.queueEvent("redraw_screen")
  402. end,
  403. function()
  404. while waiting_for_input do
  405. local event, button, x, y = os.pullEvent("mouse_click")
  406. if y < 3 or y > 5 or x < 2 or x > width-1 then waiting_for_input = false; os.queueEvent("redraw_screen"); break end
  407. end
  408. end
  409. )
  410. else
  411. parallel.waitForAny(
  412. function()
  413. local event, button, x, y = os.pullEvent("mouse_click")
  414. if button == 1 then
  415. if in_search_result == false and y == 1 then if x < width/2 then tab = 1 else tab = 2 end; redrawScreen() end
  416. if tab == 2 and in_search_result == false then
  417. if y >= 3 and y <= 5 and x >= 1 and x <= width-1 then paintutils.drawFilledBox(2,3,width-1,5,colors.white); term.setBackgroundColor(colors.white); waiting_for_input = true end
  418. if search_results then
  419. for i=1,#search_results do
  420. if y == 7 + (i-1)*2 or y == 8 + (i-1)*2 then
  421. term.setBackgroundColor(colors.white); term.setTextColor(colors.black); term.setCursorPos(2,7 + (i-1)*2); term.clearLine(); term.write(search_results[i].name)
  422. term.setTextColor(colors.gray); term.setCursorPos(2,8 + (i-1)*2); term.clearLine(); term.write(search_results[i].artist)
  423. sleep(0.2); in_search_result = true; clicked_result = i; redrawScreen()
  424. end
  425. end
  426. end
  427. elseif tab == 2 and in_search_result == true then
  428. term.setBackgroundColor(colors.white); term.setTextColor(colors.black)
  429. if y == 6 then
  430. term.setCursorPos(2,6); term.clearLine(); term.write("Play now"); sleep(0.2)
  431. in_search_result = false; for _, speaker in ipairs(speakers) do speaker.stop() os.queueEvent("playback_stopped") end
  432. playing = true; is_error = false; playing_id = nil
  433. if search_results[clicked_result].type == "playlist" then
  434. now_playing = search_results[clicked_result].playlist_items[1]; queue = {}
  435. if #search_results[clicked_result].playlist_items > 1 then for i=2, #search_results[clicked_result].playlist_items do table.insert(queue, search_results[clicked_result].playlist_items[i]) end end
  436. else now_playing = search_results[clicked_result] end
  437. os.queueEvent("audio_update")
  438. end
  439. if y == 8 then
  440. term.setCursorPos(2,8); term.clearLine(); term.write("Play next"); sleep(0.2)
  441. in_search_result = false
  442. if search_results[clicked_result].type == "playlist" then for i = #search_results[clicked_result].playlist_items, 1, -1 do table.insert(queue, 1, search_results[clicked_result].playlist_items[i]) end
  443. else table.insert(queue, 1, search_results[clicked_result]) end
  444. os.queueEvent("audio_update")
  445. end
  446. if y == 10 then
  447. term.setCursorPos(2,10); term.clearLine(); term.write("Add to queue"); sleep(0.2)
  448. in_search_result = false
  449. if search_results[clicked_result].type == "playlist" then for i = 1, #search_results[clicked_result].playlist_items do table.insert(queue, search_results[clicked_result].playlist_items[i]) end
  450. else table.insert(queue, search_results[clicked_result]) end
  451. os.queueEvent("audio_update")
  452. end
  453. if y == 13 then term.setCursorPos(2,13); term.clearLine(); term.write("Cancel"); sleep(0.2); in_search_result = false end
  454. redrawScreen()
  455. elseif tab == 1 and in_search_result == false then
  456. if y == 6 then
  457. if x >= 2 and x < 2 + 6 then
  458. if playing or now_playing ~= nil or #queue > 0 then term.setBackgroundColor(colors.white); term.setTextColor(colors.black); term.setCursorPos(2, 6); if playing then term.write(" Stop ") else term.write(" Play ") end; sleep(0.2) end
  459. if playing then
  460. playing = false; for _, speaker in ipairs(speakers) do speaker.stop() os.queueEvent("playback_stopped") end; playing_id = nil; is_loading = false; is_error = false; os.queueEvent("audio_update")
  461. elseif now_playing ~= nil then playing_id = nil; playing = true; is_error = false; os.queueEvent("audio_update")
  462. elseif #queue > 0 then now_playing = queue[1]; table.remove(queue, 1); playing_id = nil; playing = true; is_error = false; os.queueEvent("audio_update") end
  463. end
  464. if x >= 2 + 7 and x < 2 + 7 + 6 then
  465. if now_playing ~= nil or #queue > 0 then
  466. term.setBackgroundColor(colors.white); term.setTextColor(colors.black); term.setCursorPos(2 + 7, 6); term.write(" Skip "); sleep(0.2)
  467. is_error = false
  468. if playing then for _, speaker in ipairs(speakers) do speaker.stop() os.queueEvent("playback_stopped") end end
  469. if #queue > 0 then if looping == 1 then table.insert(queue, now_playing) end; now_playing = queue[1]; table.remove(queue, 1); playing_id = nil
  470. else now_playing = nil; playing = false; is_loading = false; is_error = false; playing_id = nil end
  471. os.queueEvent("audio_update")
  472. end
  473. end
  474. if x >= 2 + 7 + 7 and x < 2 + 7 + 7 + 12 then if looping == 0 then looping = 1 elseif looping == 1 then looping = 2 else looping = 0 end end
  475. end
  476. if y == 8 then if x >= 1 and x < 2 + 24 then volume = (x - 1) / 24 * 3 end end
  477. redrawScreen()
  478. end
  479. end
  480. end,
  481. function() local event, button, x, y = os.pullEvent("mouse_drag"); if button == 1 and tab == 1 and in_search_result == false then if y >= 7 and y <= 9 and x >= 1 and x < 2 + 24 then volume = (x - 1) / 24 * 3 end; redrawScreen() end end,
  482. function() local event = os.pullEvent("redraw_screen"); redrawScreen() end
  483. )
  484. end
  485. end
  486. end
  487.  
  488. function audioLoop()
  489. while true do
  490. if playing and now_playing then
  491. local thisnowplayingid = now_playing.id
  492. if playing_id ~= thisnowplayingid then
  493. playing_id = thisnowplayingid
  494. last_download_url = api_base_url .. "?v=" .. version .. "&id=" .. textutils.urlEncode(playing_id)
  495. playing_status = 0; needs_next_chunk = 1
  496. http.request({url = last_download_url, binary = true}); is_loading = true
  497. os.queueEvent("redraw_screen"); os.queueEvent("audio_update")
  498. elseif playing_status == 1 and needs_next_chunk == 1 then
  499. while true do
  500. local chunk = player_handle.read(size)
  501. if not chunk then
  502. if looping == 2 or (looping == 1 and #queue == 0) then playing_id = nil
  503. elseif looping == 1 and #queue > 0 then table.insert(queue, now_playing); now_playing = queue[1]; table.remove(queue, 1); playing_id = nil
  504. else if #queue > 0 then now_playing = queue[1]; table.remove(queue, 1); playing_id = nil else now_playing = nil; playing = false; playing_id = nil; is_loading = false; is_error = false end end
  505. os.queueEvent("redraw_screen"); if player_handle and player_handle.close then player_handle.close() end; needs_next_chunk = 0; break
  506. else
  507. if start then chunk, start = start .. chunk, nil; size = size + 4 end
  508. buffer = decoder(chunk) -- local playback
  509. netAudioSend(chunk, volume) -- NEW: remote speaker broadcast
  510. local fn = {}
  511. for i, speaker in ipairs(speakers) do
  512. fn[i] = function()
  513. local name = peripheral.getName(speaker)
  514. if #speakers > 1 then
  515. if speaker.playAudio(buffer, volume) then
  516. parallel.waitForAny(
  517. function() repeat until select(2, os.pullEvent("speaker_audio_empty")) == name end,
  518. function() local event = os.pullEvent("playback_stopped") return end
  519. )
  520. if not playing or playing_id ~= thisnowplayingid then return end
  521. end
  522. else
  523. while not speaker.playAudio(buffer, volume) do
  524. parallel.waitForAny(
  525. function() repeat until select(2, os.pullEvent("speaker_audio_empty")) == name end,
  526. function() local event = os.pullEvent("playback_stopped") return end
  527. )
  528. if not playing or playing_id ~= thisnowplayingid then return end
  529. end
  530. end
  531. if not playing or playing_id ~= thisnowplayingid then return end
  532. end
  533. end
  534. local ok, err = pcall(parallel.waitForAll, table.unpack(fn))
  535. if not ok then needs_next_chunk = 2; is_error = true; break end
  536. if not playing or playing_id ~= thisnowplayingid then break end
  537. end
  538. end
  539. os.queueEvent("audio_update")
  540. end
  541. end
  542. os.pullEvent("audio_update")
  543. end
  544. end
  545.  
  546. function httpLoop()
  547. while true do
  548. parallel.waitForAny(
  549. function()
  550. local event, url, handle = os.pullEvent("http_success")
  551. if url == last_search_url then search_results = textutils.unserialiseJSON(handle.readAll()); os.queueEvent("redraw_screen") end
  552. if url == last_download_url then is_loading = false; player_handle = handle; start = handle.read(4); size = 16 * 1024 - 4; playing_status = 1; os.queueEvent("redraw_screen"); os.queueEvent("audio_update") end
  553. end,
  554. function()
  555. local event, url = os.pullEvent("http_failure")
  556. if url == last_search_url then search_error = true; os.queueEvent("redraw_screen") end
  557. if url == last_download_url then is_loading = false; is_error = true; playing = false; playing_id = nil; os.queueEvent("redraw_screen"); os.queueEvent("audio_update") end
  558. end
  559. )
  560. end
  561. end
  562.  
  563. parallel.waitForAny(uiLoop, audioLoop, httpLoop, mirrorHelloLoop, mirrorStatusLoop)
Advertisement
Add Comment
Please, Sign In to add comment