-----------------------------------------------------------------系统启动阶段------------------------------------------------------------------------------------------------- --*获取程序所在目录 local mypath = "/"..fs.getDir(shell.getRunningProgram()) if not fs.exists(mypath.."/lib/basalt.lua") then shell.run("wget https://git.liulikeji.cn/GitHub/Basalt/releases/download/v1.6.6/basalt.lua "..mypath.."/lib/basalt.lua") end if not fs.exists(mypath.."/speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/cc_speakerlib/raw/branch/main/speakerlib.lua "..mypath.."/speakerlib.lua") end if not fs.exists(mypath.."/MusicLyrics.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/ComputerCraft-Music168-Player/raw/branch/main/MusicLyrics.lua "..mypath.."/MusicLyrics.lua") end if not fs.exists(mypath.."/ime_lib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/cc_rime/raw/branch/main/ime_lib.lua "..mypath.."/ime_lib.lua") end if not fs.exists(mypath.."/utf8display.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/ComputerCraft-Utf8/raw/branch/main/utf8display/utf8display.lua "..mypath.."/utf8display.lua") end if not fs.exists(mypath.."/json.lua") then shell.run("wget https://git.liulikeji.cn/GitHub/json.lua/raw/branch/master/json.lua "..mypath.."/json.lua") end --*GUI库导入 local basalt = require(mypath.."/lib/basalt") local utf8display = require(mypath.."/utf8display") local json = require(mypath.."/json") local ime = require(mypath.."/ime_lib") --*初始化GUI框架 local mainf = basalt.createFrame() local main = { mainf:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h"):setBackground(colors.red), } local Playprint = false -- 未使用,保留但可移除 local Playopen = false local Playstop = false local play_GUI_state = false --*GUI框架配置表 local sub = { ["UI"] = { main[1]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h -2"):setBackground(colors.red), main[1]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h -2"):setBackground(colors.white):hide(), main[1]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h -2"):setBackground(colors.white):hide(), main[1]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h -2"):setBackground(colors.red):hide(), main[1]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h -2"):setBackground(colors.white):hide(), }, ["menu"] ={ main[1]:addFrame():setPosition(1, "parent.h"):setSize("parent.w", 1):setBackground(colors.lightGray), }, ["BF"] = { mainf:addFrame():setPosition(1, "parent.h + 1"):setSize("parent.w", "parent.h"):setBackground(colors.red), main[1]:addFrame():setPosition(1, "parent.h - 1"):setSize("parent.w", 1):setBackground(colors.lightGray):hide(), }, ["play_table"] = { mainf:addFrame():setPosition(2, "parent.h + 1"):setSize("parent.w-2", 13):setBackground(colors.orange), } } --创建动画 local play_Gui_UP = mainf:addAnimation():setObject(sub["BF"][1]):move(1,1,0.3) local play_Gui_DO = mainf:addAnimation():setObject(sub["BF"][1]):move(1,mainf:getHeight()+1,1) local play_table_Gui_UP = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()-12,0.3) local play_table_Gui_DO = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()+1,1) -- 输入法窗口参数 local imeWindowWidth = 33 local imeWindowHeight = 13 local imeWindowX = math.max(1, math.floor((mainf:getWidth() - imeWindowWidth) / 2) + 1) local imeWindowY = math.max(1, math.floor((mainf:getHeight() - imeWindowHeight) / 2) + 1) local imeWindowOpen = false local searchText = "" sub["IME"] = { mainf:addFrame():setPosition(imeWindowX, mainf:getHeight() + 1):setSize(imeWindowWidth, imeWindowHeight):setBackground(colors.blue):hide(), } local ime_Gui_UP = mainf:addAnimation():setObject(sub["IME"][1]):move(imeWindowX, imeWindowY, 0.3) local ime_Gui_DO = mainf:addAnimation():setObject(sub["IME"][1]):move(imeWindowX, mainf:getHeight() + 1, 0.3) --创建播放界面 local play_name = "NO Music" local play_id = "NO Music" local play_Gui = { -- 1: 关闭按钮 sub["BF"][1]:addButton():setPosition(1,1):setSize(3, 1):setText("V") :onClick(function() play_Gui_DO:play(); play_GUI_state=false; main[1]:enable() end) :setBackground(colors.red):setForeground(colors.white), -- 2: 歌曲名标签 sub["BF"][1]:addLabel():setText(play_name):setPosition(sub["BF"][1]:getWidth()/2 - #play_name/2,1) :setBackground(colors.red):setForeground(colors.white), -- 3: 歌曲ID标签 sub["BF"][1]:addLabel():setText(play_id):setPosition(sub["BF"][1]:getWidth()/2 - #play_id/2,2) :setBackground(colors.red):setForeground(colors.white), -- 4: 歌词 Program sub["BF"][1]:addProgram():setPosition(2,2):setSize("parent.w-2", "parent.h-4"), -- 5: 进度条 (原10) sub["BF"][1]:addProgressbar():setPosition(3, "parent.h - 2"):setSize("parent.w - 4", 1) :setProgressBar(colors.red, "=", colors.white):setBackground(colors.red):setBackgroundSymbol("-"):setForeground(colors.white), -- 6: 当前时间 (原11) sub["BF"][1]:addLabel():setText("00:00"):setPosition("3", "parent.h - 1"):setSize(5, 1):setForeground(colors.white), -- 7: 总时间 (原12) sub["BF"][1]:addLabel():setText("00:00"):setPosition("parent.w - 6", "parent.h - 1"):setSize(5, 1):setForeground(colors.white), -- 8: 播放模式按钮 (原13) sub["BF"][1]:addButton():setPosition(3, "parent.h - 0"):setSize(3, 1):setText("=O=") :onClick(function() -- 预留功能 end):setForeground(colors.white):setBackground(colors.red), -- 9: 上一首 (原14) sub["BF"][1]:addButton():setPosition("parent.w /2 - 4","parent.h - 0"):setSize(2, 1):setText("|\17") :onClick(function() play_set_1() end):setForeground(colors.white):setBackground(colors.red), -- 10: 播放/暂停 (原15) sub["BF"][1]:addButton():setPosition("parent.w / 2 ", "parent.h - 0"):setSize(2, 1):setText("I>") :setForeground(colors.white):setBackground(colors.red), -- 11: 下一首 (原16) sub["BF"][1]:addButton():setPosition("parent.w / 2 +4", "parent.h - 0"):setSize(2, 1):setText("\16|") :onClick(function() play_set_0() end):setForeground(colors.white):setBackground(colors.red), -- 12: 播放列表 (原17) sub["BF"][1]:addButton():setPosition("parent.w - 4", "parent.h - 0"):setSize(3, 1):setText("=T=") :onClick(function() play_table_Gui_UP:play(); main[1]:disable(); sub["BF"][1]:disable() end) :setForeground(colors.white):setBackground(colors.red), -- 13: Slider (原18) sub["BF"][1]:addSlider():setPosition(3, "parent.h - 2"):setSize("parent.w - 4", 1) :setMaxValue(100):setBackground(colors.red):setForeground(colors.white), } -- 为播放/暂停按钮绑定事件 play_Gui[10]:onClick(function() togglePaused() end) --创建播放UI local play_column_Gui = { sub["BF"][2]:addLabel():setText(""):setPosition(1,1):setSize("parent.w-7",1):setBackground(colors.lightGray):setForeground(colors.white), sub["BF"][2]:addButton():setPosition("parent.w -4 ", 1):setSize(2, 1):setText("I>"):setForeground(colors.white):setBackground(colors.lightGray), sub["BF"][2]:addButton():setPosition("parent.w-1", 1):setSize(1, 1):setText("T") :onClick(function() play_table_Gui_UP:play(); main[1]:disable() end) :setForeground(colors.white):setBackground(colors.lightGray), sub["BF"][2]:addButton():setPosition(1, 1):setSize("parent.w -5", 1):setText("") :onClick(function() play_Gui_UP:play(); play_GUI_state=true; main[1]:disable() end) :setBackground(colors.lights), } -- 为播放列的播放/暂停按钮绑定事件 play_column_Gui[2]:onClick(function() togglePaused() end) local play_table_Gui = { sub["play_table"][1]:addButton():setPosition("parent.w-3",1):setSize(3, 1):setText("V") :onClick(function() if not play_GUI_state then main[1]:enable() end sub["BF"][1]:enable() play_table_Gui_DO:play() end) :setBackground(colors.no):setForeground(colors.white), sub["play_table"][1]:addLabel():setText("PlyaTable"):setPosition(1,1):setForeground(colors.white), sub["play_table"][1]:addList():setPosition(2,3):setSize("parent.w-2", "parent.h-2"):setScrollable(true), } -- 关闭输入法窗口 local function closeImeWindow() imeWindowOpen = false main[1]:enable() ime_Gui_DO:play() end local ime_Gui = { 1, 1, sub["IME"][1]:addProgram():setPosition(2, 1):setSize(31, 12), } -- 打开输入法窗口 local function openImeWindow() if imeWindowOpen then return end imeWindowOpen = true main[1]:disable() sub["IME"][1]:show() ime_Gui_UP:play() ime_Gui[3]:execute(function() sleep(0.31) local x, y = imeWindowX, imeWindowY if type(sub["IME"][1].getPosition) == "function" then x, y = sub["IME"][1]:getPosition() elseif type(sub["IME"][1].getX) == "function" and type(sub["IME"][1].getY) == "function" then x = sub["IME"][1]:getX() y = sub["IME"][1]:getY() end os.queueEvent("mouse_click", 1, x + 3, y + 3) local root = term.current() local text = window.create(root, 1, 1, 31, 3) local main = window.create(root, 1, 4, 31, 4) local keyboardWindow = window.create(root, 1, 8, 31, 5) local function runIme() return ime.run({ main = {term = main, BackgroundColor = colors.lightBlue}, text = {term = text}, keyboard = {term = keyboardWindow}, InputText = searchText or "", API_URL = "http://rime.liulikeji.cn/query", utf8api = utf8display, cacheFont = false, }) end os.queueEvent("music168_ime_done", runIme()) end) end local function setSearchText(text) searchText = text or "" if GUI and GUI[1] and GUI[1][1] then GUI[1][1]:setText(searchText) end if GUI and GUI[2] and GUI[2][1] then GUI[2][1]:setValue(searchText) end end --创建菜单栏 local menuBut = { sub["menu"][1]:addButton():setPosition(3,1):setSize(3, 1):setText("{Q}") :onClick(function() for index, value in ipairs(menuBut) do value:setBackground(colors.lightGray) end menuBut[1]:setBackground(colors.red) for index, value in ipairs(sub["UI"]) do value:hide() end sub["UI"][1]:show() end) :setForeground(colors.white):setBackground(colors.red), sub["menu"][1]:addButton():setPosition(8,1):setSize(3, 1):setText("{T}") :onClick(function() for index, value in ipairs(menuBut) do value:setBackground(colors.lightGray) end menuBut[2]:setBackground(colors.red) for index, value in ipairs(sub["UI"]) do value:hide() end sub["UI"][2]:show() end) :setForeground(colors.white):setBackground(colors.lightGray), sub["menu"][1]:addButton():setPosition(12,1):setSize(4, 1):setText("{PH}") :onClick(function() for index, value in ipairs(menuBut) do value:setBackground(colors.lightGray) end menuBut[3]:setBackground(colors.red) for index, value in ipairs(sub["UI"]) do value:hide() end sub["UI"][3]:show() end) :setForeground(colors.white):setBackground(colors.lightGray), sub["menu"][1]:addButton():setPosition(17,1):setSize(3, 1):setText("{G}") :onClick(function() for index, value in ipairs(menuBut) do value:setBackground(colors.lightGray) end menuBut[4]:setBackground(colors.red) for index, value in ipairs(sub["UI"]) do value:hide() end sub["UI"][4]:show() end) :setForeground(colors.white):setBackground(colors.lightGray), sub["menu"][1]:addButton():setPosition(22,1):setSize(3, 1):setText("{Z}") :onClick(function() for index, value in ipairs(menuBut) do value:setBackground(colors.lightGray) end menuBut[5]:setBackground(colors.red) for index, value in ipairs(sub["UI"]) do value:hide() end sub["UI"][5]:show() end) :setForeground(colors.white):setBackground(colors.lightGray), } -----------------------------------------------------------------DATA--------------------------------------------------------------------------------------------------------- local play_data_table = { ["music"] = {}, ["play"] = false, ["play_table"] = {}, ["play_table_index"] = 0, ["mode"] = "" } local getPlay = 0 local getPlaymax = 0 local Playstop = false local music168PlayOpen = false local currentMusicName = nil local loadingDots = 0 local loadingTimer = nil local isLoading = false local stopRequested = false local SPEAKERLIB_ID = "music168_player" local speakerState = { progress = 0, total = 0, paused = false, open = false, } local server_url = "http://music168.liulikeji.cn/" local play_lib_F = nil -- 用于搜索结果的 Frame -- 错误提示窗口 local errorWindowOpen = false local errorWindowWidth = 40 local errorWindowHeight = 10 local function showErrorWindow(title, message, callback) if errorWindowOpen then return end errorWindowOpen = true local w, h = mainf:getWidth(), mainf:getHeight() local x = math.max(1, math.floor((w - errorWindowWidth) / 2) + 1) local y = math.max(1, math.floor((h - errorWindowHeight) / 2) + 1) local errorFrame = mainf:addFrame() :setPosition(x, y) :setSize(errorWindowWidth, errorWindowHeight) :setBackground(colors.red) errorFrame:addLabel() :setText(title) :setPosition(2, 2) :setForeground(colors.white) local messageFrame = errorFrame:addFrame() :setPosition(2, 4) :setSize(errorWindowWidth - 2, errorWindowHeight - 6) :setBackground(colors.red) messageFrame:addProgram() :setPosition(1, 1) :setSize(errorWindowWidth - 2, errorWindowHeight - 6) :execute(function() term.setBackgroundColor(colors.red) term.clear() utf8display.write(message, colors.white, colors.red) end) :disable() errorFrame:addButton() :setPosition(math.floor(errorWindowWidth / 2) - 3, errorWindowHeight - 1) :setSize(7, 1) :setText("确定") :setBackground(colors.gray) :setForeground(colors.white) :onClick(function() errorFrame:remove() errorWindowOpen = false main[1]:enable() if callback then callback() end end) main[1]:disable() return errorFrame end local function queueSpeakerEvent(name, ...) os.queueEvent(name, SPEAKERLIB_ID, ...) end local function syncSpeakerState(data) speakerState.progress = tonumber(data.progress) or 0 speakerState.total = tonumber(data.total) or 0 speakerState.paused = data.paused == true speakerState.open = data.open == true getPlay = speakerState.progress getPlaymax = speakerState.total Playstop = speakerState.paused Playopen = speakerState.open return true end local function stopCurrentPlayback() if music168PlayOpen then stopRequested = true queueSpeakerEvent("speakerlib_stop") music168PlayOpen = false -- 立即重置状态 getPlay = 0 speakerState.progress = 0 speakerState.open = false Playopen = false play_data_table["play"] = false -- 停止歌词程序 play_Gui[4]:stop() os.queueEvent("music168_play_stop") end end local function setPaused(paused) if paused then queueSpeakerEvent("speakerlib_pause") speakerState.paused = true Playstop = true play_data_table["play"] = false else queueSpeakerEvent("speakerlib_resume") speakerState.paused = false Playstop = false play_data_table["play"] = true end end local function togglePaused() if play_data_table["play"] then setPaused(true) else setPaused(false) end end -----------------------------------------------------------------模块--------------------------------------------------------------------------------------------------------- -- 音乐+ function play_set_1() stopCurrentPlayback() local table_index = play_table_Gui[3]:getItemIndex() if table_index <= 1 then play_table_Gui[3]:selectItem(play_table_Gui[3]:getItemCount()) else play_table_Gui[3]:selectItem(table_index - 1) end -- 触发播放 local idx = play_table_Gui[3]:getItemIndex() if play_data_table["play_table"][idx] then playmusic(play_data_table["play_table"][idx]["name"], play_data_table["play_table"][idx]["id"], play_data_table["play_table"], idx) end end -- 音乐- function play_set_0() stopCurrentPlayback() local table_index = play_table_Gui[3]:getItemIndex() if table_index >= play_table_Gui[3]:getItemCount() then play_table_Gui[3]:selectItem(1) else play_table_Gui[3]:selectItem(table_index + 1) end local idx = play_table_Gui[3]:getItemIndex() if play_data_table["play_table"][idx] then playmusic(play_data_table["play_table"][idx]["name"], play_data_table["play_table"][idx]["id"], play_data_table["play_table"], idx) end end -- 获取音乐URL function GetmusicUrl(music_id) local retries = 3 for attempt = 1, retries do local ok, httpGet_Url = pcall(function() return http.post( server_url.."api/song/url", json.encode({["id"]=music_id}), nil, nil, 5000 ) end) if ok and httpGet_Url then local readOk, json_str = pcall(function() return httpGet_Url.readAll() end) httpGet_Url.close() if readOk and json_str then local decodeOk, data = pcall(function() return json.decode(json_str) end) if decodeOk and data and data["data"] and data["data"][1] and data["data"][1]["url"] then return data["data"][1]["url"] end end end end showErrorWindow("网络错误", "无法获取音乐播放地址,请检查网络连接后重试。", function() stopCurrentPlayback() end) return nil end -- 播放音乐 function playmusic(music_name, music_id, play_table, index) stopCurrentPlayback() stopRequested = false music168PlayOpen = true -- 重置状态 getPlay = 0 getPlaymax = 0 Playopen = false speakerState.progress = 0 speakerState.total = 0 speakerState.paused = false speakerState.open = false play_data_table["play"] = true -- 更新进度条显示 play_Gui[5]:setProgress(0) play_Gui[6]:setText("00:00") play_Gui[7]:setText("00:00") play_Gui[13]:setValue(0) -- 更新界面显示 play_Gui[2]:setText(music_name):setPosition(sub["BF"][1]:getWidth()/2 +1 - #music_name/2, 1) play_Gui[3]:setText(tostring(music_id)):setPosition(sub["BF"][1]:getWidth()/2 +1 - #tostring(music_id)/2, 2) play_column_Gui[1]:setText(music_name.." | "..tostring(music_id)) -- 更新数据 play_data_table["music"] = { ["music_id"] = music_id, ["music_name"] = music_name } play_data_table["play_table"] = play_table play_data_table["play_table_index"] = index currentMusicName = music_name -- 更新播放列表 play_table_Gui[3]:clear() for i, value in ipairs(play_table) do play_table_Gui[3]:addItem(value["name"].." | "..tostring(value["id"])) end play_table_Gui[3]:selectItem(index) -- 启动播放线程(由 speakerp 循环处理) end -- 扬声器状态监听 function speakerStateListener() while true do local event, id, payload = os.pullEvent() if id == SPEAKERLIB_ID then if event == "speakerlib_state" then local data = json.decode(payload or "null") if type(data) == "table" then syncSpeakerState(data) end elseif event == "speakerlib_downloading" then local baseName = currentMusicName or "Music" baseName = baseName:gsub(" %[Loading%.+%]$", "") currentMusicName = baseName isLoading = true loadingDots = 0 if loadingTimer then os.cancelTimer(loadingTimer) end loadingTimer = os.startTimer(0.5) if play_Gui[2] then local displayName = currentMusicName .. " [Loading.]" play_Gui[2]:setText(displayName):setPosition(sub["BF"][1]:getWidth()/2 +1 - #displayName/2, 1) end elseif event == "speakerlib_ready" then getPlaymax = tonumber(payload) or getPlaymax isLoading = false if loadingTimer then os.cancelTimer(loadingTimer) loadingTimer = nil end if currentMusicName then local baseName = currentMusicName:gsub(" %[Loading%.+%]$", "") currentMusicName = baseName if play_Gui[2] then play_Gui[2]:setText(currentMusicName):setPosition(sub["BF"][1]:getWidth()/2 +1 - #currentMusicName/2, 1) end end elseif event == "speakerlib_play_start" then play_data_table["play"] = true speakerState.open = true Playopen = true elseif event == "speakerlib_play_pause" then play_data_table["play"] = false speakerState.paused = true Playstop = true elseif event == "speakerlib_play_resume" then play_data_table["play"] = true speakerState.paused = false Playstop = false elseif event == "speakerlib_play_stop" then speakerState.open = false Playopen = false getPlay = 0 getPlaymax = 0 play_data_table["play"] = false isLoading = false if loadingTimer then os.cancelTimer(loadingTimer) loadingTimer = nil end elseif event == "speakerlib_play_end" then speakerState.open = false Playopen = false isLoading = false if loadingTimer then os.cancelTimer(loadingTimer) loadingTimer = nil end -- 自然结束,自动切歌 if music168PlayOpen and not stopRequested then play_set_0() end end elseif event == "timer" and id == loadingTimer and isLoading then loadingDots = (loadingDots % 3) + 1 local dots = string.rep(".", loadingDots) local displayName = currentMusicName .. " [Loading" .. dots .. "]" if play_Gui[2] then play_Gui[2]:setText(displayName):setPosition(sub["BF"][1]:getWidth()/2 +1 - #displayName/2, 1) end loadingTimer = os.startTimer(0.5) end end end -- 搜索 function Search(input_str, GUI_in, api) local Search_table = {} local retries = 3 for attempt = 1, retries do local ok, http1 = pcall(function() if api == "search" then return http.post(server_url.."api/search", json.encode({["value"]=input_str}), nil, nil, 5000) elseif api == "playlist" then return http.post(server_url.."api/playlist/detail", json.encode({["id"]=input_str}), nil, nil, 5000) end end) if ok and http1 then local readOk, json_str = pcall(function() return http1.readAll() end) http1.close() if readOk and json_str then local decodeOk, table_get = pcall(function() return json.decode(json_str) end) if decodeOk and table_get then local kg_a = false if api == "search" then if table_get["result"] and table_get["result"]["songCount"] and table_get["result"]["songCount"] ~= 0 then kg_a = true end elseif api == "playlist" then if table_get["code"] and table_get["code"] ~= 404 then kg_a = true end end if kg_a then if api == "search" then for index, value in ipairs(table_get["result"]["songs"]) do Search_table[index] = { ["id"] = value["id"], ["name"] = value["name"], ["artists_id"] = value["artists"][1]["id"], ["artists_name"] = value["artists"][1]["name"] } end elseif api == "playlist" then for index, value in ipairs(table_get["playlist"]["tracks"]) do Search_table[index] = { ["id"] = value["id"], ["name"] = value["name"], ["artists_id"] = value["ar"][1]["id"], ["artists_name"] = value["ar"][1]["name"] } end end if play_lib_F then play_lib_F:remove() end play_lib_F = GUI_in[3]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h"):setBackground(colors.white):setScrollable() local yPos = 2 for index, value in ipairs(Search_table) do local frame = play_lib_F:addFrame():setPosition(2, yPos):setSize("parent.w-2", 4):setBackground(colors.lightBlue) :onClick(function() stopCurrentPlayback() play_Gui_UP:play() play_GUI_state = true main[1]:disable() playmusic(value["name"], value["id"], Search_table, index) end) local textf = frame:addFrame():setPosition(1, 1):setSize("parent.w", 3) textf:addProgram():setPosition(1, 1):setSize("parent.w + 200", 4):execute(function() term.setBackgroundColor(colors.lightGray) term.clear() utf8display.write(value["name"], colors.white, colors.lightGray) end):injectEvent("char", false, "w"):disable() frame:addLabel():setText("name:"..value["name"].." id:"..value["id"].." artists:"..value["artists_name"]):setPosition(1, 4) yPos = yPos + 5 end return true else if play_lib_F then play_lib_F:remove() end play_lib_F = GUI_in[3]:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h"):setBackground(colors.white) local frame = play_lib_F:addFrame():setPosition(2, 2):setSize("parent.w-2", 3):setBackground(colors.lightBlue) frame:addLabel():setText("没有找到相关结果"):setPosition(1, 1) return true end end end end end showErrorWindow("网络错误", "无法连接到音乐服务器,请检查网络连接后重试。") return false end -----------------------------------------------------------------渲染界面阶段------------------------------------------------------------------------------------------------- local GUI = { { -- 搜索界面 sub["UI"][1]:addLabel():setPosition(2,1):setSize("parent.w-3", 1):setText(""):setForeground(colors.gray):setBackground(colors.lightGray), sub["UI"][1]:addButton():setPosition("parent.w-1",1):setSize(1, 1):setText("Q") :onClick(function() Search(searchText, GUI[1], "search") end) :setForeground(colors.white):setBackground(colors.lightGray), sub["UI"][1]:addFrame():setPosition(1, 3):setSize("parent.w", "parent.h -3"):setBackground(colors.white) }, { -- 歌单界面 sub["UI"][4]:addInput():setPosition(2,1):setSize("parent.w-3", 1):setForeground(colors.gray):setBackground(colors.lightGray), sub["UI"][4]:addButton():setPosition("parent.w-1",1):setSize(1, 1):setText("Q") :onClick(function() Search(searchText, GUI[2], "playlist") end) :setForeground(colors.white):setBackground(colors.lightGray), sub["UI"][4]:addFrame():setPosition(1, 3):setSize("parent.w", "parent.h -3"):setBackground(colors.white) }, } -- 搜索框点击打开输入法 sub["UI"][1]:addButton():setPosition(2,1):setSize("parent.w-3", 1):setText(""):setBackground(colors.lightGray):setForeground(colors.gray) :onClick(function() openImeWindow() end) setSearchText(searchText) -- 更新 UI 线程 function thread2() local lastSliderValue = 0 while true do local screenWidth, _ = term.getSize() local w, h = term.getSize() local px = (w >= 100 and h >= 30) and "--12px" or "--8px" -- 处理进度条拖动 local currentSliderValue = play_Gui[13]:getValue() or 0 if currentSliderValue ~= lastSliderValue then lastSliderValue = currentSliderValue if getPlaymax > 0 then queueSpeakerEvent("speakerlib_seek", getPlaymax * (currentSliderValue / 100)) end end sleep(0.1) -- 更新播放进度条 if getPlay ~= nil and getPlaymax ~= nil and getPlaymax > 0 then play_Gui[5]:setProgress((getPlay / getPlaymax) * 100) local current = getPlay or 0 local total = getPlaymax or 0 local currentTimeStr = string.format("%02d:%02d", math.floor(current / 60), current % 60) local totalTimeStr = string.format("%02d:%02d", math.floor(total / 60), total % 60) play_Gui[6]:setText(currentTimeStr) play_Gui[7]:setText(totalTimeStr) end -- 更新播放/暂停按钮状态 if play_data_table["play"] then play_Gui[10]:setText("II") play_column_Gui[2]:setText("II") sub["BF"][2]:show() else play_Gui[10]:setText("I>") play_column_Gui[2]:setText("I>") end -- 检测播放列表切换 if play_data_table["play_table_index"] ~= 0 then local currentIndex = play_table_Gui[3]:getItemIndex() if play_data_table["play_table_index"] ~= currentIndex then -- 用户手动切换列表项 if play_data_table["play"] then stopCurrentPlayback() end local selected = play_data_table["play_table"][currentIndex] if selected then playmusic(selected["name"], selected["id"], play_data_table["play_table"], currentIndex) end end end end end -- 粘贴事件 function paste() while true do local event, text = os.pullEvent("paste") setSearchText(text) if GUI[2] and GUI[2][1] then GUI[2][1]:setValue(text) end end end -- 输入法结果处理 function imeResultListener() while true do local _, text = os.pullEvent("music168_ime_done") if type(text) == "string" and text ~= "" then setSearchText(text) Search(searchText, GUI[1], "search") end closeImeWindow() end end -- 播放线程(负责启动外部播放器和歌词) function speakerp() while true do if music168PlayOpen and not stopRequested then local playMusicId = play_data_table["music"]["music_id"] if playMusicId then Playopen = true play_Gui[4]:stop() -- 启动歌词程序 play_Gui[4]:execute(function() shell.run(mypath.."/MusicLyrics.lua", "http://music168.liulikeji.cn/api/song/lyric?id="..playMusicId, "--id="..SPEAKERLIB_ID) end) play_Gui[4]:injectEvent("char", "w") sleep(0.1) -- 启动播放器 local url = GetmusicUrl(playMusicId) if not url then stopCurrentPlayback() else shell.run(mypath.."/speakerlib.lua", url, "-id", SPEAKERLIB_ID, "-noui") -- 播放结束或停止 if music168PlayOpen and not stopRequested then -- 自然结束,自动下一首 play_set_0() end end end end sleep(0.01) end end -- 歌词 Program 的事件注入线程 function gc() while true do play_Gui[4]:injectEvent(os.pullEvent()) end end -- 初始化停止事件 music168PlayOpen = false stopRequested = false os.queueEvent("music168_play_stop") -----------------------------------------------------------------启动循环渲染器----------------------------------------------------------------------------------------------- parallel.waitForAll(basalt.autoUpdate, thread2, paste, speakerp, gc, speakerStateListener, imeResultListener) -----------------------------------------------------------------以下结束-----------------------------------------------------------------------------------------------------