This commit is contained in:
Crispy 2025-05-26 02:36:15 +02:00
parent fe086e3a0c
commit 1ff3cea4d6
29 changed files with 3206 additions and 79 deletions

704
computer/17/music Normal file
View file

@ -0,0 +1,704 @@
local api_base_url = "https://ipod-2to6magyna-uc.a.run.app/"
local version = "2.1"
local width, height = term.getSize()
local tab = 1
local waiting_for_input = false
local last_search = nil
local last_search_url = nil
local search_results = nil
local search_error = false
local in_search_result = false
local clicked_result = nil
local playing = false
local queue = {}
local now_playing = nil
local looping = 0
local volume = 1.5
local playing_id = nil
local last_download_url = nil
local playing_status = 0
local is_loading = false
local is_error = false;
local player_handle = nil
local start = nil
local pcm = nil
local size = nil
local decoder = require "cc.audio.dfpwm".make_decoder()
local needs_next_chunk = 0
local buffer
local speakers = { peripheral.find("speaker") }
if #speakers == 0 then
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)
end
function redrawScreen()
if waiting_for_input then
return
end
term.setCursorBlink(false) -- Make sure cursor is off when redrawing
-- Clear the screen
term.setBackgroundColor(colors.black)
term.clear()
--Draw the three top tabs
term.setCursorPos(1,1)
term.setBackgroundColor(colors.gray)
term.clearLine()
tabs = {" Now Playing ", " Search "}
for i=1,#tabs,1 do
if tab == i then
term.setTextColor(colors.black)
term.setBackgroundColor(colors.white)
else
term.setTextColor(colors.white)
term.setBackgroundColor(colors.gray)
end
term.setCursorPos((math.floor((width/#tabs)*(i-0.5)))-math.ceil(#tabs[i]/2)+1, 1)
term.write(tabs[i])
end
if tab == 1 then
drawNowPlaying()
elseif tab == 2 then
drawSearch()
end
end
function drawNowPlaying()
if now_playing ~= nil then
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.setCursorPos(2,3)
term.write(now_playing.name)
term.setTextColor(colors.lightGray)
term.setCursorPos(2,4)
term.write(now_playing.artist)
else
term.setBackgroundColor(colors.black)
term.setTextColor(colors.lightGray)
term.setCursorPos(2,3)
term.write("Not playing")
end
if is_loading == true then
term.setTextColor(colors.gray)
term.setBackgroundColor(colors.black)
term.setCursorPos(2,5)
term.write("Loading...")
elseif is_error == true then
term.setTextColor(colors.red)
term.setBackgroundColor(colors.black)
term.setCursorPos(2,5)
term.write("Network error")
end
term.setTextColor(colors.white)
term.setBackgroundColor(colors.gray)
if playing then
term.setCursorPos(2, 6)
term.write(" Stop ")
else
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
term.setCursorPos(2, 6)
term.write(" Play ")
end
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
term.setCursorPos(2 + 7, 6)
term.write(" Skip ")
if looping ~= 0 then
term.setTextColor(colors.black)
term.setBackgroundColor(colors.white)
else
term.setTextColor(colors.white)
term.setBackgroundColor(colors.gray)
end
term.setCursorPos(2 + 7 + 7, 6)
if looping == 0 then
term.write(" Loop Off ")
elseif looping == 1 then
term.write(" Loop Queue ")
else
term.write(" Loop Song ")
end
term.setCursorPos(2,8)
paintutils.drawBox(2,8,25,8,colors.gray)
local width = math.floor(24 * (volume / 3) + 0.5)-1
if not (width == -1) then
paintutils.drawBox(2,8,2+width,8,colors.white)
end
if volume < 0.6 then
term.setCursorPos(2+width+2,8)
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
else
term.setCursorPos(2+width-3-(volume == 3 and 1 or 0),8)
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
end
term.write(math.floor(100 * (volume / 3) + 0.5) .. "%")
if #queue > 0 then
term.setBackgroundColor(colors.black)
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
end
end
function drawSearch()
-- Search bar
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...")
--Search results
if search_results ~= nil then
term.setBackgroundColor(colors.black)
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
else
term.setCursorPos(2,7)
term.setBackgroundColor(colors.black)
if search_error == true then
term.setTextColor(colors.red)
term.write("Network error")
elseif last_search_url ~= nil then
term.setTextColor(colors.lightGray)
term.write("Searching...")
else
term.setCursorPos(1,7)
term.setTextColor(colors.lightGray)
print("Tip: You can paste YouTube video or playlist links.")
end
end
--fullscreen song options
if in_search_result == true then
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(2,2)
term.setTextColor(colors.white)
term.write(search_results[clicked_result].name)
term.setCursorPos(2,3)
term.setTextColor(colors.lightGray)
term.write(search_results[clicked_result].artist)
term.setBackgroundColor(colors.gray)
term.setTextColor(colors.white)
term.setCursorPos(2,6)
term.clearLine()
term.write("Play now")
term.setCursorPos(2,8)
term.clearLine()
term.write("Play next")
term.setCursorPos(2,10)
term.clearLine()
term.write("Add to queue")
term.setCursorPos(2,13)
term.clearLine()
term.write("Cancel")
end
end
function uiLoop()
redrawScreen()
while true do
if waiting_for_input then
parallel.waitForAny(
function()
term.setCursorPos(3,4)
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
local input = read()
if string.len(input) > 0 then
last_search = input
last_search_url = api_base_url .. "?v=" .. version .. "&search=" .. textutils.urlEncode(input)
http.request(last_search_url)
search_results = nil
search_error = false
else
last_search = nil
last_search_url = nil
search_results = nil
search_error = false
end
waiting_for_input = false
os.queueEvent("redraw_screen")
end,
function()
while waiting_for_input do
local event, button, x, y = os.pullEvent("mouse_click")
if y < 3 or y > 5 or x < 2 or x > width-1 then
waiting_for_input = false
os.queueEvent("redraw_screen")
break
end
end
end
)
else
parallel.waitForAny(
function()
local event, button, x, y = os.pullEvent("mouse_click")
if button == 1 then
-- Tabs
if in_search_result == false then
if y == 1 then
if x < width/2 then
tab = 1
else
tab = 2
end
redrawScreen()
end
end
if tab == 2 and in_search_result == false then
-- Search box click
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
-- Search result click
if search_results then
for i=1,#search_results do
if y == 7 + (i-1)*2 or y == 8 + (i-1)*2 then
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
term.setCursorPos(2,7 + (i-1)*2)
term.clearLine()
term.write(search_results[i].name)
term.setTextColor(colors.gray)
term.setCursorPos(2,8 + (i-1)*2)
term.clearLine()
term.write(search_results[i].artist)
sleep(0.2)
in_search_result = true
clicked_result = i
redrawScreen()
end
end
end
elseif tab == 2 and in_search_result == true then
-- Search result menu clicks
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
if y == 6 then
term.setCursorPos(2,6)
term.clearLine()
term.write("Play now")
sleep(0.2)
in_search_result = false
for _, speaker in ipairs(speakers) do
speaker.stop()
os.queueEvent("playback_stopped")
end
playing = true
is_error = false
playing_id = nil
if search_results[clicked_result].type == "playlist" then
now_playing = search_results[clicked_result].playlist_items[1]
queue = {}
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
else
now_playing = search_results[clicked_result]
end
os.queueEvent("audio_update")
end
if y == 8 then
term.setCursorPos(2,8)
term.clearLine()
term.write("Play next")
sleep(0.2)
in_search_result = false
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
else
table.insert(queue, 1, search_results[clicked_result])
end
os.queueEvent("audio_update")
end
if y == 10 then
term.setCursorPos(2,10)
term.clearLine()
term.write("Add to queue")
sleep(0.2)
in_search_result = false
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
else
table.insert(queue, search_results[clicked_result])
end
os.queueEvent("audio_update")
end
if y == 13 then
term.setCursorPos(2,13)
term.clearLine()
term.write("Cancel")
sleep(0.2)
in_search_result = false
end
redrawScreen()
elseif tab == 1 and in_search_result == false then
-- Now playing tab clicks
if y == 6 then
-- Play/stop button
if x >= 2 and x < 2 + 6 then
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
if playing then
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")
elseif now_playing ~= nil then
playing_id = nil
playing = true
is_error = false
os.queueEvent("audio_update")
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
end
-- Skip button
if x >= 2 + 7 and x < 2 + 7 + 6 then
if now_playing ~= nil or #queue > 0 then
term.setBackgroundColor(colors.white)
term.setTextColor(colors.black)
term.setCursorPos(2 + 7, 6)
term.write(" Skip ")
sleep(0.2)
is_error = false
if playing then
for _, speaker in ipairs(speakers) do
speaker.stop()
os.queueEvent("playback_stopped")
end
end
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
else
now_playing = nil
playing = false
is_loading = false
is_error = false
playing_id = nil
end
os.queueEvent("audio_update")
end
end
-- Loop button
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
end
if y == 8 then
-- Volume slider
if x >= 1 and x < 2 + 24 then
volume = (x - 1) / 24 * 3
-- for _, speaker in ipairs(speakers) do
-- speaker.stop()
-- os.queueEvent("playback_stopped")
-- end
-- playing_id = nil
-- os.queueEvent("audio_update")
end
end
redrawScreen()
end
end
end,
function()
local event, button, x, y = os.pullEvent("mouse_drag")
if button == 1 then
if tab == 1 and in_search_result == false then
if y >= 7 and y <= 9 then
-- Volume slider
if x >= 1 and x < 2 + 24 then
volume = (x - 1) / 24 * 3
-- for _, speaker in ipairs(speakers) do
-- speaker.stop()
-- os.queueEvent("playback_stopped")
-- end
-- playing_id = nil
-- os.queueEvent("audio_update")
end
end
redrawScreen()
end
end
end,
function()
local event = os.pullEvent("redraw_screen")
redrawScreen()
end
)
end
end
end
function audioLoop()
while true do
-- AUDIO
if playing and now_playing then
local thisnowplayingid = now_playing.id
if playing_id ~= thisnowplayingid then
playing_id = thisnowplayingid
last_download_url = api_base_url .. "?v=" .. version .. "&id=" .. textutils.urlEncode(playing_id)
playing_status = 0
needs_next_chunk = 1
http.request({url = last_download_url, binary = true})
is_loading = true
os.queueEvent("redraw_screen")
os.queueEvent("audio_update")
elseif playing_status == 1 and needs_next_chunk == 1 then
while true do
local chunk = player_handle.read(size)
if not chunk then
if looping == 2 or (looping == 1 and #queue == 0) then
playing_id = nil
elseif looping == 1 and #queue > 0 then
table.insert(queue, now_playing)
now_playing = queue[1]
table.remove(queue, 1)
playing_id = nil
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
os.queueEvent("redraw_screen")
player_handle.close()
needs_next_chunk = 0
break
else
if start then
chunk, start = start .. chunk, nil
size = size + 4
end
buffer = decoder(chunk)
local fn = {}
for i, speaker in ipairs(speakers) do
fn[i] = function()
local name = peripheral.getName(speaker)
if #speakers > 1 then
if speaker.playAudio(buffer, volume) then
parallel.waitForAny(
function()
repeat until select(2, os.pullEvent("speaker_audio_empty")) == name
end,
function()
local event = os.pullEvent("playback_stopped")
return
end
)
if not playing or playing_id ~= thisnowplayingid then
return
end
end
else
while not speaker.playAudio(buffer, volume) do
parallel.waitForAny(
function()
repeat until select(2, os.pullEvent("speaker_audio_empty")) == name
end,
function()
local event = os.pullEvent("playback_stopped")
return
end
)
if not playing or playing_id ~= thisnowplayingid then
return
end
end
end
if not playing or playing_id ~= thisnowplayingid then
return
end
end
end
local ok, err = pcall(parallel.waitForAll, table.unpack(fn))
if not ok then
needs_next_chunk = 2
is_error = true
break
end
-- If we're not playing anymore, exit the chunk processing loop
if not playing or playing_id ~= thisnowplayingid then
break
end
end
end
os.queueEvent("audio_update")
end
end
os.pullEvent("audio_update")
end
end
function httpLoop()
while true do
parallel.waitForAny(
function()
local event, url, handle = os.pullEvent("http_success")
if url == last_search_url then
search_results = textutils.unserialiseJSON(handle.readAll())
os.queueEvent("redraw_screen")
end
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
end,
function()
local event, url = os.pullEvent("http_failure")
if url == last_search_url then
search_error = true
os.queueEvent("redraw_screen")
end
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
end
)
end
end
parallel.waitForAny(uiLoop, audioLoop, httpLoop)