robathome icon

MC_jukebox - Remote

robathome | PRO | 10/27/25 06:00:55 PM UTC (Edited) | 0 ⭐ | 582 👁️ | Never ⏰ | []
text |

7.64 KB

|

None

|

0 👍

/

0 👎

-- pocket_compact_controller.txt
-- Compact controller UI for Advanced Wireless Pocket Computer.
-- Requires server to include server_cmd_patch.txt and to run
-- mirrorHelloLoop + mirrorStatusLoop.
 local MODE = "ui-mirror-v1"
 -- Open rednet
local function _openRednetClient()
  local side = nil
  for _, s in ipairs(rs.getSides()) do
    if peripheral.getType(s) == "modem" then side = s break end
  end
  assert(side, "Wireless modem required on client")
  if not rednet.isOpen(side) then rednet.open(side) end
  return side
end
_openRednetClient()
 -- State mirrored from server
local S = {
  now = nil, playing = false, looping = 0, volume = 1.0,
  is_loading = false, is_error = false, queue = {}, last_search = nil,
  search_results = nil,
}
 local cw, ch = term.getSize()
local TAB = 1       -- 1=Now, 2=Queue, 3=Find
local selIdx = 1    -- selection index for lists
local resIdx = 1    -- selection for search results
local serverId = nil
 -- Helpers
local function send(pkt)
  if serverId then rednet.send(serverId, pkt, MODE) else rednet.broadcast(pkt, MODE) end
end
 local function cmd(name, extra)
  local m = { op="CMD", cmd=name }
  if extra then for k,v in pairs(extra) do m[k]=v end end
  send(m)
end
 -- UI draw
local function bar(t) term.setBackgroundColor(colors.gray) term.setTextColor(colors.white) term.setCursorPos(1,1) term.clearLine() term.write(t) end
local function footer(t) term.setCursorPos(1,ch) term.setBackgroundColor(colors.gray) term.setTextColor(colors.white) term.clearLine() term.write(t) end
local function clearBody() term.setBackgroundColor(colors.black) for y=2,ch-1 do term.setCursorPos(1,y) term.clearLine() end end
 local function drawNow()
  bar(" Now  | Queue | Find ")
  clearBody()
  term.setTextColor(colors.white)
  term.setCursorPos(2,3)
  term.write(S.now and (S.now.name or "Unknown") or "Not playing")
  term.setCursorPos(2,4)
  term.setTextColor(colors.lightGray)
  term.write(S.now and (S.now.artist or "") or "")
   term.setTextColor(colors.white)
  term.setCursorPos(2,6)
  local status = (S.is_error and "Error") or (S.is_loading and "Loading") or (S.playing and "Playing") or "Stopped"
  term.write(("Status: %s"):format(status))
   -- Volume line
  local pct = math.floor((S.volume/3)*100 + 0.5)
  term.setCursorPos(2,8); term.write(("Vol: %3d%%"):format(pct))
   -- Loop state
  local loopTxt = (S.looping==0 and "Off") or (S.looping==1 and "Queue") or "Song"
  term.setCursorPos(2,9); term.write("Loop: "..loopTxt)
   footer("⏯ Play/Pause  ⏭ Skip  L Loop  - Vol+  + Vol-  2 Queue 3 Find")
end
 local function drawQueue()
  bar(" Now  |*Queue*| Find ")
  clearBody()
  term.setTextColor(colors.white)
  term.setCursorPos(2,2) term.write("Up Next:")
  for i=1, math.min(#S.queue, ch-5) do
    local y = 2 + i
    term.setCursorPos(2,y)
    if i==selIdx then term.setBackgroundColor(colors.white) term.setTextColor(colors.black) else term.setBackgroundColor(colors.black) term.setTextColor(colors.white) end
    local name = S.queue[i].name or "Unknown"
    term.write(name:sub(1, cw-2))
    term.setBackgroundColor(colors.black) term.setTextColor(colors.lightGray)
    term.setCursorPos(2,y+1)
    if S.queue[i].artist then term.write(("by %s"):format(S.queue[i].artist):sub(1, cw-2)) end
    y = y + 1
  end
  footer("↑/↓ Select  Enter Play  D Delete  1 Now 3 Find")
end
 local function drawFind()
  bar(" Now  | Queue |*Find*")
  clearBody()
  term.setCursorPos(2,2) term.setTextColor(colors.white) term.write("Query:")
  term.setCursorPos(2,3) term.setTextColor(colors.yellow)
  term.write(S.last_search or "(press S to set)")
   term.setTextColor(colors.white)
  term.setCursorPos(2,5) term.write("Results:")
  local list = S.search_results or {}
  for i=1, math.min(#list, ch-8) do
    local y = 5 + i
    term.setCursorPos(2,y)
    if i==resIdx then term.setBackgroundColor(colors.white) term.setTextColor(colors.black) else term.setBackgroundColor(colors.black) term.setTextColor(colors.white) end
    local name = list[i].name or "Unknown"
    term.write(name:sub(1, cw-2))
    term.setBackgroundColor(colors.black) term.setTextColor(colors.lightGray)
    term.setCursorPos(2,y+1)
    if list[i].artist then term.write(("by %s"):format(list[i].artist):sub(1, cw-2)) end
  end
  footer("S Search  Enter Play  N PlayNext  A Add  1 Now 2 Queue")
end
 local function redraw()
  term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
  if TAB==1 then drawNow()
  elseif TAB==2 then drawQueue()
  else drawFind() end
end
 -- Net receive
local function netLoop()
  while true do
    local id, msg, proto = rednet.receive(MODE)
    if proto == MODE and type(msg) == "table" then
      if msg.op == "HELLO_ACK" then
        serverId = id
      elseif msg.op == "STATUS" then
        serverId = serverId or id
        S.now = msg.now; S.playing = msg.playing; S.looping = msg.looping
        S.volume = msg.volume; S.is_loading = msg.is_loading; S.is_error = msg.is_error
        S.queue = msg.queue or {}; S.last_search = msg.last_search; S.search_results = msg.search_results
        redraw()
      elseif msg.op == "screen" then
        serverId = serverId or id
      end
    end
  end
end
 -- Periodic hello + status requests
local function helloLoop()
  while true do
    if not serverId then rednet.broadcast({ op="HELLO", w=cw, h=ch }, MODE) end
    send({ op="STATUS_REQ" })
    sleep(1.0)
  end
end
 -- Input
local function inputLoop()
  redraw()
  while true do
    local e, p1, p2, p3 = os.pullEvent()
    if e == "key" then
      local k = p1
      if k == keys.one then TAB=1; redraw()
      elseif k == keys.two then TAB=2; redraw()
      elseif k == keys.three then TAB=3; redraw()
      elseif TAB == 1 then
        if k == keys.space then cmd("PLAY_TOGGLE")
        elseif k == keys.l then cmd("LOOP_NEXT")
        elseif k == keys.minus then cmd("VOL_SET", {value = math.max(0, (S.volume or 1)-0.15)})
        elseif k == keys.equals or k == keys.plus then cmd("VOL_SET", {value = math.min(3, (S.volume or 1)+0.15)})
        elseif k == keys.right then cmd("SKIP")
        end
      elseif TAB == 2 then
        if k == keys.up then selIdx = math.max(1, selIdx-1); redraw()
        elseif k == keys.down then selIdx = math.min(math.max(#S.queue,1), selIdx+1); redraw()
        elseif k == keys.enter then cmd("PLAY_FROM_QUEUE_IDX", {idx = selIdx})
        elseif k == keys.d then cmd("REMOVE_QUEUE_IDX", {idx = selIdx})
        end
      elseif TAB == 3 then
        if k == keys.up then resIdx = math.max(1, resIdx-1); redraw()
        elseif k == keys.down then resIdx = math.min(math.max(#(S.search_results or {}),1), resIdx+1); redraw()
        elseif k == keys.s then
          term.setCursorBlink(true)
          term.setCursorPos(2,3)
          term.setTextColor(colors.white) term.setBackgroundColor(colors.black)
          term.clearLine() term.setCursorPos(2,3)
          local q = read()
          term.setCursorBlink(false)
          if q and #q>0 then cmd("SEARCH", {q=q}) end
        elseif k == keys.enter then cmd("PLAY_NOW", {idx = resIdx})
        elseif k == keys.n then cmd("PLAY_NEXT", {idx = resIdx})
        elseif k == keys.a then cmd("ADD_QUEUE", {idx = resIdx})
        end
      end
    elseif e == "mouse_click" then
      local x,y = p2,p3
      -- Top bar tabs by rough regions
      if y == 1 then
        if x <= 8 then TAB=1 elseif x <= 16 then TAB=2 else TAB=3 end
        redraw()
      end
    end
  end
end
 -- Start
term.setBackgroundColor(colors.black) term.clear() term.setCursorPos(1,1)
print("Pocket controller")
parallel.waitForAny(helloLoop, netLoop, inputLoop)
 

Comments