Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0f275cc9d | |||
|
|
ca4e086a66 | ||
|
|
91f9048755 | ||
| d69a4b6def | |||
|
|
62b95e38b0 |
520
MusicLyrics.lua
Normal file
520
MusicLyrics.lua
Normal file
@@ -0,0 +1,520 @@
|
||||
-- 歌词显示软件
|
||||
local function loadRemoteFont(url)
|
||||
local response = http.get(url)
|
||||
if not response then
|
||||
error("无法连接到字体服务器")
|
||||
end
|
||||
|
||||
if response.getResponseCode() ~= 200 then
|
||||
error("字体服务器返回错误: " .. response.getResponseCode())
|
||||
end
|
||||
|
||||
local content = response.readAll()
|
||||
response.close()
|
||||
|
||||
local sandbox = {}
|
||||
local chunk, err = load(content, "=remoteFont", "t", sandbox)
|
||||
if not chunk then
|
||||
error("加载字体失败: " .. err)
|
||||
end
|
||||
|
||||
local success, result = pcall(chunk)
|
||||
if not success then
|
||||
error("执行字体脚本失败: " .. result)
|
||||
end
|
||||
|
||||
return sandbox.font or sandbox[1] or result
|
||||
end
|
||||
|
||||
-- 显示单个字符的函数
|
||||
local function displayChar(charMap, x, y, textColor, backgroundColor)
|
||||
local origTextColor = term.getTextColor()
|
||||
local origBackgroundColor = term.getBackgroundColor()
|
||||
|
||||
term.setTextColor(textColor)
|
||||
term.setBackgroundColor(backgroundColor)
|
||||
|
||||
for row = 1, #charMap do
|
||||
term.setCursorPos(x, y + row - 1)
|
||||
local line = charMap[row]
|
||||
|
||||
for col = 1, #line do
|
||||
local byte = string.byte(line, col)
|
||||
|
||||
if byte < 128 then
|
||||
term.setTextColor(backgroundColor)
|
||||
term.setBackgroundColor(textColor)
|
||||
term.write(string.char(byte + 128))
|
||||
else
|
||||
term.setTextColor(textColor)
|
||||
term.setBackgroundColor(backgroundColor)
|
||||
term.write(string.char(byte))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
term.setTextColor(origTextColor)
|
||||
term.setBackgroundColor(origBackgroundColor)
|
||||
end
|
||||
|
||||
-- 显示UTF-8字符串的函数
|
||||
local function displayUtf8String(str, font, x, y, textColor, backgroundColor)
|
||||
local function utf8codes(str)
|
||||
local i = 1
|
||||
return function()
|
||||
if i > #str then return end
|
||||
|
||||
local b1 = string.byte(str, i)
|
||||
i = i + 1
|
||||
|
||||
if b1 < 0x80 then
|
||||
return b1
|
||||
elseif b1 >= 0xC0 and b1 < 0xE0 then
|
||||
local b2 = string.byte(str, i) or 0
|
||||
i = i + 1
|
||||
return (b1 - 0xC0) * 64 + (b2 - 0x80)
|
||||
elseif b1 >= 0xE0 and b1 < 0xF0 then
|
||||
local b2 = string.byte(str, i) or 0
|
||||
i = i + 1
|
||||
local b3 = string.byte(str, i) or 0
|
||||
i = i + 1
|
||||
return (b1 - 0xE0) * 4096 + (b2 - 0x80) * 64 + (b3 - 0x80)
|
||||
else
|
||||
return 32
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cursorX = x
|
||||
|
||||
for code in utf8codes(str) do
|
||||
local charMap = font[code]
|
||||
if not charMap then
|
||||
charMap = font[32] or {{"\x80"}}
|
||||
end
|
||||
|
||||
displayChar(charMap, cursorX, y, textColor, backgroundColor)
|
||||
cursorX = cursorX + #charMap[1]
|
||||
end
|
||||
end
|
||||
|
||||
-- 解析歌词时间
|
||||
local function parseTime(timeStr)
|
||||
local minutes, seconds, milliseconds = timeStr:match("(%d+):(%d+)%.(%d+)")
|
||||
if minutes and seconds and milliseconds then
|
||||
return tonumber(minutes) * 60 + tonumber(seconds) + tonumber(milliseconds) / 1000
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- 解析歌词内容
|
||||
local function parseLyrics(lyricStr)
|
||||
local lyrics = {}
|
||||
if not lyricStr or lyricStr == "" then return lyrics end
|
||||
|
||||
for line in lyricStr:gmatch("[^\r\n]+") do
|
||||
if line:match("%[.+%]") then
|
||||
local timeStr = line:match("%[(.+)%]")
|
||||
local text = line:match("%].*$")
|
||||
if text then text = text:sub(2) else text = "" end
|
||||
|
||||
-- 过滤掉作词、作曲等元信息
|
||||
if timeStr and not timeStr:match("by:") and not timeStr:match("offset:") and not text:match("作词") and not text:match("作曲") then
|
||||
local time = parseTime(timeStr)
|
||||
table.insert(lyrics, {time = time, text = text})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 按时间排序
|
||||
table.sort(lyrics, function(a, b) return a.time < b.time end)
|
||||
return lyrics
|
||||
end
|
||||
|
||||
-- 计算字符串宽度
|
||||
local function getStringWidth(str, font)
|
||||
local width = 0
|
||||
local function utf8codes(s)
|
||||
local i = 1
|
||||
return function()
|
||||
if i > #s then return end
|
||||
local b1 = string.byte(s, i)
|
||||
i = i + 1
|
||||
if b1 < 0x80 then
|
||||
return b1
|
||||
elseif b1 >= 0xC0 and b1 < 0xE0 then
|
||||
local b2 = string.byte(s, i) or 0
|
||||
i = i + 1
|
||||
return (b1 - 0xC0) * 64 + (b2 - 0x80)
|
||||
elseif b1 >= 0xE0 and b1 < 0xF0 then
|
||||
local b2 = string.byte(s, i) or 0
|
||||
i = i + 1
|
||||
local b3 = string.byte(s, i) or 0
|
||||
i = i + 1
|
||||
return (b1 - 0xE0) * 4096 + (b2 - 0x80) * 64 + (b3 - 0x80)
|
||||
else
|
||||
return 32
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for code in utf8codes(str) do
|
||||
local charMap = font[code] or font[32]
|
||||
width = width + #charMap[1]
|
||||
end
|
||||
return width
|
||||
end
|
||||
|
||||
-- 获取当前应该显示的歌词索引
|
||||
local function getCurrentLyricIndex(lyricPairs, currentTime)
|
||||
local currentIndex = 1
|
||||
for i = 1, #lyricPairs do
|
||||
if lyricPairs[i].time <= currentTime then
|
||||
currentIndex = i
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return currentIndex
|
||||
end
|
||||
|
||||
-- 解析颜色参数
|
||||
local function parseColor(colorStr)
|
||||
local colorsMap = {
|
||||
white = colors.white,
|
||||
orange = colors.orange,
|
||||
magenta = colors.magenta,
|
||||
lightBlue = colors.lightBlue,
|
||||
yellow = colors.yellow,
|
||||
lime = colors.lime,
|
||||
pink = colors.pink,
|
||||
gray = colors.gray,
|
||||
lightGray = colors.lightGray,
|
||||
cyan = colors.cyan,
|
||||
purple = colors.purple,
|
||||
blue = colors.blue,
|
||||
brown = colors.brown,
|
||||
green = colors.green,
|
||||
red = colors.red,
|
||||
black = colors.black
|
||||
}
|
||||
|
||||
return colorsMap[colorStr] or colors.white
|
||||
end
|
||||
|
||||
-- 主显示函数
|
||||
local function displayLyrics(url, notzh, fontType, colorsConfig)
|
||||
-- 选择字体
|
||||
local fontUrl
|
||||
if fontType == "12px" then
|
||||
fontUrl = "https://alist.liulikeji.cn/d/HFS/fusion-pixel-12px-proportional-zh_hans.lua"
|
||||
else
|
||||
fontUrl = "https://alist.liulikeji.cn/d/HFS/fusion-pixel-8px-proportional-zh_hans.lua"
|
||||
end
|
||||
|
||||
local font = loadRemoteFont(fontUrl)
|
||||
|
||||
-- 获取歌词数据
|
||||
local response = http.get(url)
|
||||
if not response then
|
||||
print("无法连接到服务器")
|
||||
return
|
||||
end
|
||||
|
||||
if response.getResponseCode() ~= 200 then
|
||||
print("服务器返回错误: " .. response.getResponseCode())
|
||||
response.close()
|
||||
return
|
||||
end
|
||||
|
||||
local content = response.readAll()
|
||||
response.close()
|
||||
|
||||
-- 解析JSON数据
|
||||
local data = textutils.unserialiseJSON(content)
|
||||
if not data or not data.lrc or not data.lrc.lyric then
|
||||
print("无法解析歌词数据")
|
||||
return
|
||||
end
|
||||
|
||||
-- 解析歌词
|
||||
local lyrics = parseLyrics(data.lrc.lyric)
|
||||
local tlyrics = {}
|
||||
if not notzh and data.tlyric and data.tlyric.lyric then
|
||||
tlyrics = parseLyrics(data.tlyric.lyric)
|
||||
end
|
||||
|
||||
if #lyrics == 0 and #tlyrics == 0 then
|
||||
print("没有找到有效歌词")
|
||||
return
|
||||
end
|
||||
|
||||
-- 构建时间索引映射
|
||||
local timeIndexMap = {}
|
||||
for i, lyric in ipairs(lyrics) do
|
||||
local timeKey = string.format("%.3f", lyric.time)
|
||||
timeIndexMap[timeKey] = {original = i}
|
||||
end
|
||||
|
||||
for i, lyric in ipairs(tlyrics) do
|
||||
local timeKey = string.format("%.3f", lyric.time)
|
||||
if timeIndexMap[timeKey] then
|
||||
timeIndexMap[timeKey].translation = i
|
||||
else
|
||||
timeIndexMap[timeKey] = {translation = i}
|
||||
end
|
||||
end
|
||||
|
||||
-- 构建歌词对数组
|
||||
local lyricPairs = {}
|
||||
local allTimes = {}
|
||||
for timeKey, _ in pairs(timeIndexMap) do
|
||||
table.insert(allTimes, tonumber(timeKey))
|
||||
end
|
||||
table.sort(allTimes)
|
||||
|
||||
for _, time in ipairs(allTimes) do
|
||||
local timeKey = string.format("%.3f", time)
|
||||
local indices = timeIndexMap[timeKey]
|
||||
|
||||
local originalText = ""
|
||||
local translationText = ""
|
||||
|
||||
if indices.original then
|
||||
originalText = lyrics[indices.original].text or ""
|
||||
end
|
||||
|
||||
if indices.translation then
|
||||
translationText = tlyrics[indices.translation].text or ""
|
||||
end
|
||||
|
||||
table.insert(lyricPairs, {
|
||||
time = time,
|
||||
original = originalText,
|
||||
translation = translationText
|
||||
})
|
||||
end
|
||||
|
||||
if #lyricPairs == 0 then
|
||||
print("没有找到匹配的歌词对")
|
||||
return
|
||||
end
|
||||
|
||||
-- 计算显示参数
|
||||
local screenWidth, screenHeight = term.getSize()
|
||||
local fontHeight = #font[32] -- 使用空格字符获取字体高度
|
||||
|
||||
-- 检查是否有翻译歌词
|
||||
local hasTranslation = false
|
||||
if not notzh then
|
||||
for _, pair in ipairs(lyricPairs) do
|
||||
if pair.translation and pair.translation ~= "" then
|
||||
hasTranslation = true
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 根据是否显示翻译来计算行高
|
||||
local lyricPairHeight
|
||||
if notzh or not hasTranslation then
|
||||
lyricPairHeight = fontHeight + 1 -- 只有原文的高度
|
||||
else
|
||||
lyricPairHeight = fontHeight * 2 + 1 -- 原文+翻译的高度
|
||||
end
|
||||
|
||||
local maxLyricPairs = math.floor(screenHeight / lyricPairHeight)
|
||||
local visibleLyricPairs = math.max(1, maxLyricPairs) -- 确保至少显示1对
|
||||
|
||||
-- 显示循环
|
||||
while true do
|
||||
-- 获取当前播放时间
|
||||
local currentTime = _G.getPlay - 1 or 0 -- 直接获取_G.getPlay的值
|
||||
|
||||
-- 获取当前歌词索引
|
||||
local currentIndex = getCurrentLyricIndex(lyricPairs, currentTime)
|
||||
term.setBackgroundColor(colorsConfig.background)
|
||||
-- 清屏
|
||||
term.clear()
|
||||
|
||||
-- 计算显示范围 - 真正的居中显示策略
|
||||
local startPairIndex, endPairIndex
|
||||
|
||||
if visibleLyricPairs < 3 then
|
||||
-- 当可显示歌词少于3个时,优先显示当前和下一个歌词
|
||||
startPairIndex = currentIndex
|
||||
endPairIndex = math.min(#lyricPairs, startPairIndex + visibleLyricPairs - 1)
|
||||
|
||||
-- 如果显示空间还有剩余,尝试向前补充显示前面的歌词
|
||||
if endPairIndex - startPairIndex + 1 < visibleLyricPairs then
|
||||
local remainingSpace = visibleLyricPairs - (endPairIndex - startPairIndex + 1)
|
||||
startPairIndex = math.max(1, startPairIndex - remainingSpace)
|
||||
endPairIndex = math.min(#lyricPairs, startPairIndex + visibleLyricPairs - 1)
|
||||
end
|
||||
else
|
||||
-- 当可显示歌词3个或更多时,采用真正的居中显示策略
|
||||
-- 计算当前歌词上方和下方应该显示的歌词数量
|
||||
local aboveCount, belowCount
|
||||
if visibleLyricPairs % 2 == 1 then
|
||||
-- 奇数个显示位置:上下数量相等
|
||||
local halfCount = math.floor(visibleLyricPairs / 2)
|
||||
aboveCount = halfCount
|
||||
belowCount = halfCount
|
||||
else
|
||||
-- 偶数个显示位置:上面少一个,下面多一个(如你要求的4个和5个)
|
||||
aboveCount = math.floor(visibleLyricPairs / 2) - 1
|
||||
belowCount = math.floor(visibleLyricPairs / 2)
|
||||
end
|
||||
|
||||
-- 计算起始和结束索引
|
||||
startPairIndex = math.max(1, currentIndex - aboveCount)
|
||||
endPairIndex = math.min(#lyricPairs, currentIndex + belowCount)
|
||||
|
||||
-- 边界调整:如果前面不够显示,向后补充
|
||||
if currentIndex - startPairIndex < aboveCount then
|
||||
local needMore = aboveCount - (currentIndex - startPairIndex)
|
||||
endPairIndex = math.min(#lyricPairs, endPairIndex + needMore)
|
||||
end
|
||||
|
||||
-- 边界调整:如果后面不够显示,向前补充
|
||||
if endPairIndex - currentIndex < belowCount then
|
||||
local needMore = belowCount - (endPairIndex - currentIndex)
|
||||
startPairIndex = math.max(1, startPairIndex - needMore)
|
||||
end
|
||||
|
||||
-- 最终调整确保显示足够的行数
|
||||
if endPairIndex - startPairIndex + 1 < visibleLyricPairs then
|
||||
if startPairIndex > 1 then
|
||||
startPairIndex = math.max(1, endPairIndex - visibleLyricPairs + 1)
|
||||
else
|
||||
endPairIndex = math.min(#lyricPairs, startPairIndex + visibleLyricPairs - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local startY = 2
|
||||
|
||||
-- 显示歌词对
|
||||
for i = startPairIndex, endPairIndex do
|
||||
local lyricPair = lyricPairs[i]
|
||||
|
||||
-- 计算Y坐标 - 根据是否显示翻译动态计算
|
||||
local pairY
|
||||
if notzh or not hasTranslation then
|
||||
-- 不显示翻译时,每行只占一个字体高度
|
||||
pairY = startY + (i - startPairIndex) * (fontHeight + 1)
|
||||
else
|
||||
-- 显示翻译时,每对歌词占两个字体高度
|
||||
pairY = startY + (i - startPairIndex) * (fontHeight * 2 + 1)
|
||||
end
|
||||
|
||||
-- 设置颜色
|
||||
local textColor = colorsConfig.normalMain
|
||||
local translationColor = colorsConfig.normalSub
|
||||
local bgColor = colorsConfig.background
|
||||
|
||||
if i == currentIndex then
|
||||
textColor = colorsConfig.selectedMain
|
||||
translationColor = colorsConfig.selectedSub
|
||||
bgColor = colorsConfig.selectedBackground
|
||||
end
|
||||
|
||||
-- 显示原文(第一行)
|
||||
if lyricPair.original and lyricPair.original ~= "" then
|
||||
local x = math.floor((screenWidth - getStringWidth(lyricPair.original, font)) / 2)
|
||||
displayUtf8String(lyricPair.original, font, x, pairY, textColor, bgColor)
|
||||
end
|
||||
|
||||
-- 显示翻译(第二行)- 只有当notzh为false且有翻译时才显示
|
||||
if not notzh and hasTranslation and lyricPair.translation and lyricPair.translation ~= "" then
|
||||
local x = math.floor((screenWidth - getStringWidth(lyricPair.translation, font)) / 2)
|
||||
displayUtf8String(lyricPair.translation, font, x, pairY + fontHeight, translationColor, bgColor)
|
||||
end
|
||||
end
|
||||
|
||||
sleep(0.5)
|
||||
end
|
||||
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print("歌词显示已退出")
|
||||
end
|
||||
|
||||
|
||||
-- 解析命令行参数
|
||||
local function parseArgs(args)
|
||||
local url = nil
|
||||
local notzh = false
|
||||
local fontType = "8px" -- 默认8px
|
||||
|
||||
-- 默认颜色配置
|
||||
local colorsConfig = {
|
||||
background = colors.black, -- 背景颜色
|
||||
normalMain = colors.gray, -- 歌词主颜色
|
||||
normalSub = colors.gray, -- 歌词辅颜色
|
||||
selectedMain = colors.white, -- 歌词选中主颜色
|
||||
selectedSub = colors.lightGray, -- 选中辅颜色
|
||||
selectedBackground = colors.black -- 选中背景色
|
||||
}
|
||||
|
||||
for i = 1, #args do
|
||||
local arg = args[i]
|
||||
|
||||
-- 处理颜色参数
|
||||
if arg:match("--normabg=") then
|
||||
local colorName = arg:sub(6)
|
||||
colorsConfig.background = parseColor(colorName)
|
||||
elseif arg:match("--normalmain=") then
|
||||
local colorName = arg:sub(14)
|
||||
colorsConfig.normalMain = parseColor(colorName)
|
||||
elseif arg:match("--normalsub=") then
|
||||
local colorName = arg:sub(13)
|
||||
colorsConfig.normalSub = parseColor(colorName)
|
||||
elseif arg:match("--selectedmain=") then
|
||||
local colorName = arg:sub(16)
|
||||
colorsConfig.selectedMain = parseColor(colorName)
|
||||
elseif arg:match("--selectedsub=") then
|
||||
local colorName = arg:sub(15)
|
||||
colorsConfig.selectedSub = parseColor(colorName)
|
||||
elseif arg:match("--selectedbg=") then
|
||||
local colorName = arg:sub(14)
|
||||
colorsConfig.selectedBackground = parseColor(colorName)
|
||||
elseif arg == "--notzh" then
|
||||
notzh = true
|
||||
elseif arg == "--8px" then
|
||||
fontType = "8px"
|
||||
elseif arg == "--12px" then
|
||||
fontType = "12px"
|
||||
elseif not url and arg:sub(1, 4) == "http" then
|
||||
url = arg
|
||||
end
|
||||
end
|
||||
|
||||
return url, notzh, fontType, colorsConfig
|
||||
end
|
||||
|
||||
-- 主程序
|
||||
local function main(...)
|
||||
local args = {...}
|
||||
local url, notzh, fontType, colorsConfig = parseArgs(args)
|
||||
|
||||
-- 如果没有通过参数提供URL,则要求用户输入
|
||||
if not url then
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
write("请输入歌词URL: ")
|
||||
url = read()
|
||||
|
||||
if not url or url == "" then
|
||||
print("URL不能为空")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
displayLyrics(url, notzh, fontType, colorsConfig)
|
||||
end
|
||||
|
||||
-- 运行主程序
|
||||
main(...)
|
||||
|
||||
127
README.md
Normal file
127
README.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# ComputerCraft 网易云音乐播放器
|
||||
|
||||
|
||||
## 📌 简介
|
||||
|
||||
本程序是一个基于 **ComputerCraft**(Minecraft 模组)的网易云音乐播放器,使用 **Lua** 编写,调用了第三方网易云 API、DFPWM 音频转码 API,结合 [Basalt GUI库](https://github.com/PyPyl/Basalt) 构建图形界面,支持搜索/播放网易云音乐,并支持歌单播放、歌词同步等功能。
|
||||
|
||||
此版本支持中文音乐名称显示(搜索出来的列表),但搜索只能使用英文或中文拼音
|
||||
|
||||
支持歌词显示,且会根据显示大小变更文字分辨率,但在默认的终端使用会显示不全,所以此功能建议使用屏幕显示(monitor 方向 music168.lua)
|
||||
|
||||
此软件使用触控交互,所以不支持石头电脑
|
||||
---
|
||||
|
||||
## 🚀 安装方法
|
||||
|
||||
在 Minecraft 中安装好 [ComputerCraft](https://www.mcmod.cn/class/1681.html) 模组后在电脑终端:
|
||||
|
||||
```bash
|
||||
wget https://git.liulikeji.cn/xingluo/ComputerCraft-Music168-Player/releases/download/v1.1.0/music168.lua
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
music168
|
||||
```
|
||||
|
||||
即可启动播放器。
|
||||
并自动补全运行库
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 使用方法
|
||||
|
||||
### 🔍 搜索音乐
|
||||
|
||||
1. 在顶部输入框中输入歌曲名(拼音或英文均可,空格隔开)或网易云音乐 ID(需要多试几次)。
|
||||
2. 点击输入框右侧的 `Q` 按钮开始搜索。
|
||||
3. 下方列表中点击歌曲即可播放。
|
||||
|
||||
### 📂 歌单播放
|
||||
|
||||
1. 底部菜单栏点击 `{G}` 按钮,进入歌单搜索界面。
|
||||
2. 输入歌单 ID(可通过复制粘贴)并点击 `Q` 搜索。
|
||||
3. 点击歌单中的音乐可播放。
|
||||
|
||||
### 🎵 播放控制
|
||||
|
||||
- 点击播放界面中的按钮可控制播放暂停。
|
||||
- 点击 `T` 可打开播放列表,点击列表内歌曲切换歌曲。
|
||||
- 点击播放界面左上角的 `V` 退出播放界面到搜索。
|
||||
- 点击进度条可以切换播放进度
|
||||
|
||||
### 多通道音频
|
||||
|
||||
- 默认为全部单通道播放,如使用双声道音频请按以下操作
|
||||
|
||||
编辑文件 speaker_groups.cfg
|
||||
```bash
|
||||
edit speaker_groups.cfg
|
||||
```
|
||||
在内部写入例如:
|
||||
```json
|
||||
{
|
||||
main = {}, --混合通道扬声器列表
|
||||
left = {"speaker_0","speaker_1"}, --左声道扬声器列表
|
||||
right = {"speaker_5","speaker_6"} --右声道扬声器列表
|
||||
}
|
||||
```
|
||||
这里的"speaker_0","speaker_1"等数量不限制,但多了会导致音频不同步或超出最大上限
|
||||
"speaker_0","speaker_1"等为扬声器名称,如果使用网线链接则是打开时聊天栏的名称,如果直接放在旁边则为方向名例如(top,left,right)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 📦 使用技术栈
|
||||
|
||||
- **语言**:Lua
|
||||
- **GUI 库**:[Basalt GUI](https://git.liulikeji.cn/GitHub/Basalt)
|
||||
- **网易云 API**:[liulikeji.cn](http://music168.liulikeji.cn:15843/)
|
||||
- **DFPWM 音频转码接口**:[GMapiServer-ffmpeg](https://git.liulikeji.cn/xingluo/GMapiServer/)
|
||||
- **歌词支持**:通过 `MusicLyrics.lua` 加载歌词
|
||||
- **播放和格式转换**:通过`speakerlib.lua` 转换格式和处理播放
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
- 本程序为半成品,部分按钮仅作装饰。
|
||||
- 若播放无声音,请检查播放界面左下角时间是否为 `00:00`,如果长时间无变化,请稍等或重试。
|
||||
- 因为网络延迟或 API 崩溃,可能导致播放失败。
|
||||
- 播放效果依赖于 ComputerCraft 中的 **speaker** 外设(扬声器)。
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 更新历史
|
||||
|
||||
- **新增歌单播放功能**
|
||||
- 修复部分播放逻辑与 UI 显示错误
|
||||
|
||||
- **新增中文音乐名,歌词显示,多通道音频**
|
||||
- 使用了新版GMApi接口
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🛑 声明
|
||||
|
||||
> 本项目仅供学习交流使用,API 来源于第三方网易云api,音乐内容的版权属于原权利人。请勿用于商业用途。
|
||||
|
||||
---
|
||||
|
||||
## 🧑💻 项目作者
|
||||
|
||||
项目为个人开发,目前处于“咕咕咕”状态,更新不确定。
|
||||
|
||||
---
|
||||
|
||||
## 📬 反馈与支持
|
||||
|
||||
如有建议或问题,请联系项目原作者。
|
||||
|
||||
---
|
||||
|
||||
> 祝你享受愉快的音乐时光 🎶
|
||||
在 ComputerCraft 世界中聆听你的网易云音乐❤
|
||||
702
music168.lua
702
music168.lua
@@ -1,329 +1,373 @@
|
||||
-----------------------------------------------------------------系统启动阶段-------------------------------------------------------------------------------------------------
|
||||
|
||||
--*获取程序所在目录
|
||||
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.3/basalt.lua lib/basalt.lua") end
|
||||
if not fs.exists(mypath.."/speaker.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/ComputerCraft-Music168-Player/releases/download/v1.0.0/speaker.lua") end
|
||||
|
||||
--*GUI库导入
|
||||
basalt = require(mypath.."/lib/basalt")
|
||||
--*初始化GUI框架
|
||||
local mainf = basalt.createFrame()
|
||||
main = {
|
||||
mainf:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h"):setBackground(colors.red),
|
||||
}
|
||||
_G.Playprint = false
|
||||
_G.Playopen =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),
|
||||
}
|
||||
}
|
||||
|
||||
--创建动画
|
||||
play_Gui_UP = mainf:addAnimation():setObject(sub["BF"][1]):move(1,1,0.3)
|
||||
play_Gui_DO = mainf:addAnimation():setObject(sub["BF"][1]):move(1,mainf:getHeight()+1,1)
|
||||
play_table_Gui_UP = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()-12,0.3)
|
||||
play_table_Gui_DO = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()+1,1)
|
||||
--play_Gui_UP:play()
|
||||
--main[1]:hide()
|
||||
--main[1]:addAnimation():setObject(sub["BF"][1]):move(1,"parent.h+1",1.5):play()
|
||||
--创建播放界面
|
||||
play_name = "NO Music"
|
||||
play_id = "NO Music"
|
||||
play_Gui = {
|
||||
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),
|
||||
sub["BF"][1]:addLabel():setText("NO Music"):setPosition(sub["BF"][1]:getWidth()/2 - #play_name/2,1):setBackground(colors.red):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText("NO Music"):setPosition(sub["BF"][1]:getWidth()/2 - #play_id/2,2):setBackground(colors.red):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText(" "):setPosition(3,4):setSize("parent.w-4", "parent.h-10"):setBackground(colors.white):setForeground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition(3,"parent.h-5"):setSize(1, 1):setText("\3"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition(8,"parent.h-5"):setSize(1, 1):setText("\25"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w/2","parent.h-5"):setSize(2, 1):setText("+-"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w-3","parent.h-5"):setSize(1, 1):setText("@"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w-8","parent.h-5"):setSize(1, 1):setText("E"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addProgressbar():setPosition(3, "parent.h - 4"):setSize("parent.w - 4", 1):setProgressBar(colors.red, "=", colors.white):setBackground(colors.red):setBackgroundSymbol("-"):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText("00:00"):setPosition("3", "parent.h - 3"):setSize(5, 1):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText("00:00"):setPosition("parent.w - 6", "parent.h - 3"):setSize(5, 1):setForeground(colors.white),
|
||||
sub["BF"][1]:addButton():setPosition(3, "parent.h - 2"):setSize(3, 1):setText("=O="):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w /2 - 4","parent.h - 2"):setSize(2, 1):setText("|\17"):onClick(function() play_set_1() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w / 2 ", "parent.h - 2"):setSize(2, 1):setText("I>"):onClick(function() if play_data_table["play"] then _G.Playstop = true play_data_table["play"]=false else play_data_table["play"]=true end end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w / 2 +4", "parent.h - 2"):setSize(2, 1):setText("\16|"):onClick(function() play_set_0() end):setForeground(colors.white):setBackground(colors.red),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w - 4", "parent.h - 2"):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),
|
||||
sub["BF"][1]:addSlider():setPosition(3, "parent.h - 4"):setSize("parent.w - 4", 1):setMaxValue(100):setBackground(colors.red):setForeground(colors.white),--:setBackgroundSymbol("\x8c"):setSymbol(" "),
|
||||
}
|
||||
--创建播放UI
|
||||
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>"):onClick(function() if play_data_table["play"] then _G.Playstop = true play_data_table["play"]=false else play_data_table["play"]=true end end):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_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),
|
||||
}
|
||||
|
||||
--创建菜单栏
|
||||
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---------------------------------------------------------------------------------------------------------
|
||||
play_data_table = { ["music"] = {} , ["play"] = false ,["play_table"] = {}, ["play_table_index"] = 0, ["mode"] = "" , }
|
||||
_G.Playopen = false
|
||||
-----------------------------------------------------------------模块---------------------------------------------------------------------------------------------------------
|
||||
--多线程
|
||||
thread_table = {}
|
||||
function AddThread(funct)
|
||||
thread1 = mainf:addThread()
|
||||
thread1:start(funct)
|
||||
table.insert(thread_table,thread1)
|
||||
return #thread_table
|
||||
end
|
||||
|
||||
--音乐+
|
||||
function play_set_1()
|
||||
_G.music168_playopen = false
|
||||
_G.getPlay = 0
|
||||
_G.Playopen = false
|
||||
_G.Playstop = false
|
||||
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
|
||||
end
|
||||
--音乐-
|
||||
function play_set_0()
|
||||
_G.music168_playopen = false
|
||||
_G.getPlay = 0
|
||||
_G.Playopen = false
|
||||
_G.Playstop = false
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
--获取URL
|
||||
function GetmusicUrl(music_id)
|
||||
while true do
|
||||
local http = http.post(server_url.."api/song/url",textutils.serialiseJSON({["id"]=music_id}))
|
||||
if http then
|
||||
json_str = http.readAll()
|
||||
local table = textutils.unserialiseJSON(json_str)
|
||||
if table["data"][1]["url"] then
|
||||
return(table["data"][1]["url"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--dfpwm转码
|
||||
|
||||
--播放
|
||||
function playmusic(music_name,music_id,play_table,index)
|
||||
_G.getPlay = 0
|
||||
_G.Playopen = false
|
||||
_G.Playstop = false
|
||||
play_Gui[2]:setText(music_name):setPosition(sub["BF"][1]:getWidth()/2 +1 - #music_name/2,1)
|
||||
play_Gui[3]:setText(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
|
||||
play_data_table["play"] = true
|
||||
|
||||
play_table_Gui[3]:clear()
|
||||
for index, value in ipairs(play_table) do
|
||||
play_table_Gui[3]:addItem(value["name"].." | "..tostring(value["id"]))
|
||||
end
|
||||
play_table_Gui[3]:selectItem(index)
|
||||
_G.music168_music_id = music_id
|
||||
|
||||
_G.music168_playopen = true
|
||||
--basalt.debug("true")
|
||||
--play_thread_id = AddThread(function ()
|
||||
--
|
||||
|
||||
|
||||
--end)
|
||||
|
||||
end
|
||||
|
||||
--搜索
|
||||
server_url = "http://music168.liulikeji.cn:15843/"
|
||||
function Search(input_str,GUI_in,api)
|
||||
Search_table = {}
|
||||
while true do
|
||||
kg_a=false
|
||||
if api=="search" then
|
||||
http1 = http.post(server_url.."api/search",textutils.serialiseJSON({["value"]=input_str}))
|
||||
json_str = http1.readAll()
|
||||
table_get = textutils.unserialiseJSON(json_str)
|
||||
if table_get["result"]["songCount"] ~= 0 then kg_a=true end
|
||||
elseif api=="playlist" then
|
||||
http1 = http.post(server_url.."api/playlist/detail",textutils.serialiseJSON({["id"]=input_str}))
|
||||
json_str = http1.readAll()
|
||||
table_get = textutils.unserialiseJSON(json_str)
|
||||
if table_get["code"] ~= 404 then kg_a=true end
|
||||
end
|
||||
|
||||
if http1 then
|
||||
if kg_a then
|
||||
if api=="search" then
|
||||
for index, value in ipairs(table_get["result"]["songs"]) do
|
||||
out_table = {["id"] = value["id"],["name"]=value["name"],["artists_id"]=value["artists"][1]["id"],["artists_name"]=value["artists"][1]["name"]}
|
||||
Search_table[index]=out_table
|
||||
end
|
||||
elseif api=="playlist" then
|
||||
for index, value in ipairs(table_get["playlist"]["tracks"]) do
|
||||
out_table = {["id"] = value["id"],["name"]=value["name"],["artists_id"]=value["ar"][1]["id"],["artists_name"]=value["ar"][1]["name"]}
|
||||
Search_table[index]=out_table
|
||||
end
|
||||
end
|
||||
|
||||
a=2
|
||||
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()
|
||||
|
||||
for index, value in ipairs(Search_table) do
|
||||
frame = play_lib_F:addFrame():setPosition(2, a):setSize("parent.w-2", 3):setBackground(colors.lightBlue):onClick(function() if play_data_table["play"] then shell.run(mypath.."/speaker.lua stop") if _G.Playopen then end _G.music168_playopen = false play_data_table["play"]=false end play_Gui_UP:play() play_GUI_state = true main[1]:disable() playmusic(value["name"],value["id"],Search_table,index) end)
|
||||
frame:addLabel():setText(value["name"]):setPosition(1, 1)
|
||||
frame:addLabel():setText("id: "..value["id"]):setPosition(1, 2)
|
||||
frame:addLabel():setText("artists: "..value["artists_name"]):setPosition(1, 3)
|
||||
a=a+4
|
||||
end
|
||||
break;
|
||||
else
|
||||
frame = GUI_in[3]:addFrame():setPosition(2, 2):setSize("parent.w-2", 3):setBackground(colors.lightBlue)
|
||||
frame:addLabel():setText("[songCount] == 0"):setPosition(1, 1)
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-----------------------------------------------------------------渲染界面阶段-------------------------------------------------------------------------------------------------
|
||||
GUI = {
|
||||
{
|
||||
sub["UI"][1]:addInput():setPosition(2,1):setSize("parent.w-3", 1):setForeground(colors.gray):setBackground(colors.lightGray),
|
||||
sub["UI"][1]:addButton():setPosition("parent.w-1",1):setSize(1, 1):setText("Q"):onClick(function() Search(GUI[1][1]:getValue(),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(GUI[1][1]:getValue(),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)
|
||||
},
|
||||
}
|
||||
|
||||
_G.getPlay = nil
|
||||
|
||||
function thread2()
|
||||
while true do
|
||||
--basalt.debug(_G.music168_playopen)
|
||||
local screenWidth, _ = term.getSize()
|
||||
if play_Gui[18]:getIndex() ~=1 then _G.setPlay = (play_Gui[18]:getIndex() / (screenWidth-2)) * 100 play_Gui[18]:setIndex(1) end
|
||||
sleep(0.1)
|
||||
--
|
||||
if _G.getPlay ~= nil then
|
||||
play_Gui[10]:setProgress(_G.getPlay*100)
|
||||
end
|
||||
if play_data_table["play"]== true then
|
||||
_G.Playstop = false
|
||||
play_Gui[15]:setText("II")
|
||||
play_column_Gui[2]:setText("II")
|
||||
sub["BF"][2]:show()
|
||||
else
|
||||
play_Gui[15]:setText("I>")
|
||||
play_column_Gui[2]:setText("I>")
|
||||
--play_Gui[11]:setText("00:00")
|
||||
_G.Playstop = true
|
||||
end
|
||||
if play_data_table["play_table_index"] ~= 0 then
|
||||
if play_data_table["play_table_index"] ~= play_table_Gui[3]:getItemIndex() then
|
||||
index = play_table_Gui[3]:getItemIndex()
|
||||
if play_data_table["play"] then
|
||||
|
||||
shell.run(mypath.."/speaker.lua stop")
|
||||
if _G.Playopen then
|
||||
|
||||
end
|
||||
play_data_table["play"]=false
|
||||
end
|
||||
_G.music168_playopen = false
|
||||
sleep(0.1)
|
||||
playmusic(play_data_table["play_table"][index]["name"],play_data_table["play_table"][index]["id"],play_data_table["play_table"],index)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function paste()
|
||||
while true do
|
||||
local event, text = os.pullEvent("paste")
|
||||
GUI[1][1]:setValue(text)
|
||||
GUI[2][1]:setValue(text)
|
||||
end
|
||||
end
|
||||
|
||||
function speakerp()
|
||||
function speaker_thread()
|
||||
|
||||
|
||||
--play_time_thread_id = AddThread(function() local time_f = os.date("%M%S") while true do while _G.Playopen do time_f = os.date("%M%S") sleep(0.1) end time_F = os.date("%M%S") play_Gui[11]:setText(string.format("%02d", os.date("*t",time_F-time_f).min or 00)..":"..string.format("%02d", os.date("*t",time_F-time_f).sec or 00) or "00:00") sleep(1) end end)
|
||||
--basalt.debug(mypath)
|
||||
--shell.run(mypath.."/speaker play "..dfpwmURL.readAll())
|
||||
if _G.music168_music_id then
|
||||
--basalt.debug(music168_music_id)
|
||||
_G.Playopen = true
|
||||
dfpwmURL = http.post("http://gmapi.liulikeji.cn:15842/dfpwm",textutils.serialiseJSON({ ["url"] = GetmusicUrl(_G.music168_music_id) } ))
|
||||
shell.run(mypath.."/speaker play "..dfpwmURL.readAll())
|
||||
play_set_0()
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
function while_thread() while _G.music168_playopen do sleep(0.01) end end
|
||||
|
||||
while true do
|
||||
|
||||
if _G.music168_playopen then parallel.waitForAny(speaker_thread, while_thread) end
|
||||
sleep(0.01)
|
||||
end
|
||||
end
|
||||
|
||||
_G.music168_playopen = false
|
||||
-----------------------------------------------------------------启动循环渲染器-----------------------------------------------------------------------------------------------
|
||||
parallel.waitForAll(basalt.autoUpdate, thread2, paste, speakerp)
|
||||
-----------------------------------------------------------------以下结束-----------------------------------------------------------------------------------------------------
|
||||
-----------------------------------------------------------------系统启动阶段-------------------------------------------------------------------------------------------------
|
||||
--*获取程序所在目录
|
||||
|
||||
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.3/basalt.lua lib/basalt.lua") end
|
||||
if not fs.exists(mypath.."/speakerlib.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/ComputerCraft-Music168-Player/raw/branch/1.1.0/speakerlib.lua") end
|
||||
if not fs.exists(mypath.."/MusicLyrics.lua") then shell.run("wget https://git.liulikeji.cn/xingluo/ComputerCraft-Music168-Player/raw/branch/1.1.0/MusicLyrics.lua") end
|
||||
|
||||
--*GUI库导入
|
||||
basalt = require(mypath.."/lib/basalt")
|
||||
--*初始化GUI框架
|
||||
local mainf = basalt.createFrame()
|
||||
main = {
|
||||
mainf:addFrame():setPosition(1, 1):setSize("parent.w", "parent.h"):setBackground(colors.red),
|
||||
}
|
||||
_G.Playprint = false
|
||||
_G.Playopen =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),
|
||||
}
|
||||
}
|
||||
--创建动画
|
||||
play_Gui_UP = mainf:addAnimation():setObject(sub["BF"][1]):move(1,1,0.3)
|
||||
play_Gui_DO = mainf:addAnimation():setObject(sub["BF"][1]):move(1,mainf:getHeight()+1,1)
|
||||
play_table_Gui_UP = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()-12,0.3)
|
||||
play_table_Gui_DO = mainf:addAnimation():setObject(sub["play_table"][1]):move(2,mainf:getHeight()+1,1)
|
||||
--play_Gui_UP:play()
|
||||
--main[1]:hide()
|
||||
--main[1]:addAnimation():setObject(sub["BF"][1]):move(1,"parent.h+1",1.5):play()
|
||||
--创建播放界面
|
||||
play_name = "NO Music"
|
||||
play_id = "NO Music"
|
||||
play_Gui = {
|
||||
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),
|
||||
sub["BF"][1]:addLabel():setText("NO Music"):setPosition(sub["BF"][1]:getWidth()/2 - #play_name/2,1):setBackground(colors.red):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText("NO Music"):setPosition(sub["BF"][1]:getWidth()/2 - #play_id/2,2):setBackground(colors.red):setForeground(colors.white),
|
||||
sub["BF"][1]:addProgram():setPosition(2,2):setSize("parent.w-2", "parent.h-4"),
|
||||
1,--sub["BF"][1]:addButton():setPosition(3,"parent.h-5"):setSize(1, 1):setText("\3"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
1,--sub["BF"][1]:addButton():setPosition(8,"parent.h-5"):setSize(1, 1):setText("\25"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
1,--sub["BF"][1]:addButton():setPosition("parent.w/2","parent.h-5"):setSize(2, 1):setText("+-"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
1,--sub["BF"][1]:addButton():setPosition("parent.w-3","parent.h-5"):setSize(1, 1):setText("@"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
1,--sub["BF"][1]:addButton():setPosition("parent.w-8","parent.h-5"):setSize(1, 1):setText("E"):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
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),
|
||||
sub["BF"][1]:addLabel():setText("00:00"):setPosition("3", "parent.h - 1"):setSize(5, 1):setForeground(colors.white),
|
||||
sub["BF"][1]:addLabel():setText("00:00"):setPosition("parent.w - 6", "parent.h - 1"):setSize(5, 1):setForeground(colors.white),
|
||||
sub["BF"][1]:addButton():setPosition(3, "parent.h - 0"):setSize(3, 1):setText("=O="):onClick(function() end):setForeground(colors.white):setBackground(colors.red),
|
||||
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),
|
||||
sub["BF"][1]:addButton():setPosition("parent.w / 2 ", "parent.h - 0"):setSize(2, 1):setText("I>"):onClick(function() if play_data_table["play"] then _G.Playstop = true play_data_table["play"]=false else play_data_table["play"]=true end end):setForeground(colors.white):setBackground(colors.red),
|
||||
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),
|
||||
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),
|
||||
sub["BF"][1]:addSlider():setPosition(3, "parent.h - 2"):setSize("parent.w - 4", 1):setMaxValue(100):setBackground(colors.red):setForeground(colors.white),--:setBackgroundSymbol("\x8c"):setSymbol(" "),
|
||||
}
|
||||
--创建播放UI
|
||||
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>"):onClick(function() if play_data_table["play"] then _G.Playstop = true play_data_table["play"]=false else play_data_table["play"]=true end end):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_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),
|
||||
}
|
||||
--创建菜单栏
|
||||
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---------------------------------------------------------------------------------------------------------
|
||||
play_data_table = { ["music"] = {} , ["play"] = false ,["play_table"] = {}, ["play_table_index"] = 0, ["mode"] = "" , }
|
||||
_G.Playopen = false
|
||||
_G.getPlay = 0
|
||||
_G.getPlaymax = 0
|
||||
_G.setPlay = nil
|
||||
-----------------------------------------------------------------模块---------------------------------------------------------------------------------------------------------
|
||||
|
||||
--音乐+
|
||||
function play_set_1()
|
||||
_G.music168_playopen = false os.queueEvent("music168_play_stop")
|
||||
_G.getPlay = 0
|
||||
_G.Playopen = false
|
||||
_G.Playstop = false
|
||||
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
|
||||
end
|
||||
--音乐-
|
||||
function play_set_0()
|
||||
_G.music168_playopen = false os.queueEvent("music168_play_stop")
|
||||
_G.getPlay = 0
|
||||
_G.Playopen = false
|
||||
_G.Playstop = false
|
||||
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
|
||||
end
|
||||
--获取URL
|
||||
function GetmusicUrl(music_id)
|
||||
while true do
|
||||
local http = http.post(server_url.."api/song/url",textutils.serialiseJSON({["id"]=music_id}))
|
||||
if http then
|
||||
json_str = http.readAll()
|
||||
local table = textutils.unserialiseJSON(json_str)
|
||||
if table["data"][1]["url"] then
|
||||
return(table["data"][1]["url"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
--dfpwm转码
|
||||
--播放
|
||||
function playmusic(music_name,music_id,play_table,index)
|
||||
_G.getPlay = 0
|
||||
_G.getPlaymax = 0
|
||||
_G.Playopen = false
|
||||
_G.music168_playopen = false os.queueEvent("music168_play_stop")
|
||||
|
||||
play_Gui[2]:setText(music_name):setPosition(sub["BF"][1]:getWidth()/2 +1 - #music_name/2,1)
|
||||
play_Gui[3]:setText(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
|
||||
play_data_table["play"] = true
|
||||
play_table_Gui[3]:clear()
|
||||
for index, value in ipairs(play_table) do
|
||||
play_table_Gui[3]:addItem(value["name"].." | "..tostring(value["id"]))
|
||||
end
|
||||
|
||||
|
||||
play_table_Gui[3]:selectItem(index)
|
||||
_G.music168_music_id = music_id
|
||||
|
||||
_G.music168_playopen = true
|
||||
--basalt.debug("true")
|
||||
--play_thread_id = AddThread(function ()
|
||||
--
|
||||
--end)
|
||||
end
|
||||
|
||||
printUtf8 = load(http.get("https://alist.liulikeji.cn/d/HFS/utf8ptrint.lua").readAll())()
|
||||
--搜索
|
||||
server_url = "http://music168.liulikeji.cn:15843/"
|
||||
function Search(input_str,GUI_in,api)
|
||||
Search_table = {}
|
||||
while true do
|
||||
kg_a=false
|
||||
if api=="search" then
|
||||
http1 = http.post(server_url.."api/search",textutils.serialiseJSON({["value"]=input_str}))
|
||||
json_str = http1.readAll()
|
||||
table_get = textutils.unserialiseJSON(json_str)
|
||||
if table_get["result"]["songCount"] ~= 0 then kg_a=true end
|
||||
elseif api=="playlist" then
|
||||
http1 = http.post(server_url.."api/playlist/detail",textutils.serialiseJSON({["id"]=input_str}))
|
||||
json_str = http1.readAll()
|
||||
table_get = textutils.unserialiseJSON(json_str)
|
||||
if table_get["code"] ~= 404 then kg_a=true end
|
||||
end
|
||||
if http1 then
|
||||
if kg_a then
|
||||
if api=="search" then
|
||||
for index, value in ipairs(table_get["result"]["songs"]) do
|
||||
out_table = {["id"] = value["id"],["name"]=value["name"],["artists_id"]=value["artists"][1]["id"],["artists_name"]=value["artists"][1]["name"]}
|
||||
Search_table[index]=out_table
|
||||
end
|
||||
elseif api=="playlist" then
|
||||
for index, value in ipairs(table_get["playlist"]["tracks"]) do
|
||||
out_table = {["id"] = value["id"],["name"]=value["name"],["artists_id"]=value["ar"][1]["id"],["artists_name"]=value["ar"][1]["name"]}
|
||||
Search_table[index]=out_table
|
||||
end
|
||||
end
|
||||
a=2
|
||||
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()
|
||||
for index, value in ipairs(Search_table) do
|
||||
id = value["id"]
|
||||
local frame = play_lib_F:addFrame():setPosition(2, a):setSize("parent.w-2", 4):setBackground(colors.lightBlue):onClick(function() if play_data_table["play"] then shell.run(mypath.."/speakerlib.lua stop") if _G.Playopen then end _G.music168_playopen = false os.queueEvent("music168_play_stop") play_data_table["play"]=false end play_Gui_UP:play() play_GUI_state = true main[1]:disable() _G.music168_playopen = false os.queueEvent("music168_play_stop") 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", 4):execute(function ()
|
||||
term.setBackgroundColor(colors.lightGray)
|
||||
term.clear()
|
||||
printUtf8(value["name"],colors.white,colors.lightGray)
|
||||
end):injectEvent("char", false, "w"):disable()
|
||||
--frame:addLabel():setText(value["name"]):setPosition(1, 1)
|
||||
frame:addLabel():setText("name:"..value["name"].." id:"..value["id"].." artists:"..value["artists_name"]):setPosition(1, 4)
|
||||
--frame:addLabel():setText("artists: "..value["artists_name"]):setPosition(1, 3)
|
||||
a=a+5
|
||||
end
|
||||
|
||||
break;
|
||||
else
|
||||
frame = GUI_in[3]:addFrame():setPosition(2, 2):setSize("parent.w-2", 3):setBackground(colors.lightBlue)
|
||||
frame:addLabel():setText("[songCount] == 0"):setPosition(1, 1)
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
play_Gui[4]:onError(function(self, event, err)
|
||||
|
||||
end)
|
||||
|
||||
play_Gui[4]:onDone(function()
|
||||
|
||||
end)
|
||||
|
||||
-----------------------------------------------------------------渲染界面阶段-------------------------------------------------------------------------------------------------
|
||||
GUI = {
|
||||
{
|
||||
sub["UI"][1]:addInput():setPosition(2,1):setSize("parent.w-3", 1):setForeground(colors.gray):setBackground(colors.lightGray),
|
||||
sub["UI"][1]:addButton():setPosition("parent.w-1",1):setSize(1, 1):setText("Q"):onClick(function() Search(GUI[1][1]:getValue(),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(GUI[1][1]:getValue(),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)
|
||||
},
|
||||
}
|
||||
|
||||
function thread2()
|
||||
while true do
|
||||
--basalt.debug(_G.music168_playopen = false os.queueEvent("music168_play_stop"))
|
||||
local screenWidth, _ = term.getSize()
|
||||
|
||||
-- 处理用户拖动进度条设置播放位置
|
||||
|
||||
w,h = term.getSize()
|
||||
if w >= 100 and h >= 30 then px = "--12px" else px = "--8px" end
|
||||
|
||||
if play_Gui[18]:getIndex() ~=1 then
|
||||
local sliderValue = play_Gui[18]:getValue() or 0
|
||||
_G.setPlay = _G.getPlaymax * (sliderValue / 100)
|
||||
play_Gui[18]:setIndex(1)
|
||||
end
|
||||
|
||||
sleep(0.1)
|
||||
|
||||
-- 更新播放进度条
|
||||
if _G.getPlay ~= nil and _G.getPlaymax ~= nil and _G.getPlaymax > 0 then
|
||||
play_Gui[10]:setProgress((_G.getPlay / _G.getPlaymax) * 100)
|
||||
|
||||
-- 更新当前播放时间显示
|
||||
local current = _G.getPlay or 0
|
||||
local total = _G.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[11]:setText(currentTimeStr) -- 当前播放时间
|
||||
play_Gui[12]:setText(totalTimeStr) -- 总时间
|
||||
end
|
||||
|
||||
if play_data_table["play"]== true then
|
||||
_G.Playstop = false
|
||||
play_Gui[15]:setText("II")
|
||||
play_column_Gui[2]:setText("II")
|
||||
sub["BF"][2]:show()
|
||||
else
|
||||
play_Gui[15]:setText("I>")
|
||||
play_column_Gui[2]:setText("I>")
|
||||
--play_Gui[11]:setText("00:00")
|
||||
_G.Playstop = true
|
||||
end
|
||||
if play_data_table["play_table_index"] ~= 0 then
|
||||
if play_data_table["play_table_index"] ~= play_table_Gui[3]:getItemIndex() then
|
||||
index = play_table_Gui[3]:getItemIndex()
|
||||
if play_data_table["play"] then
|
||||
|
||||
shell.run(mypath.."/speakerlib.lua stop")
|
||||
if _G.Playopen then
|
||||
|
||||
end
|
||||
play_data_table["play"]=false
|
||||
end
|
||||
_G.music168_playopen = false os.queueEvent("music168_play_stop")
|
||||
sleep(0.1)
|
||||
playmusic(play_data_table["play_table"][index]["name"],play_data_table["play_table"][index]["id"],play_data_table["play_table"],index)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function paste()
|
||||
while true do
|
||||
local event, text = os.pullEvent("paste")
|
||||
GUI[1][1]:setValue(text)
|
||||
GUI[2][1]:setValue(text)
|
||||
end
|
||||
end
|
||||
|
||||
function speakerp()
|
||||
function speaker_thread()
|
||||
local startTime = os.clock()
|
||||
|
||||
if _G.music168_music_id then
|
||||
--basalt.debug(music168_music_id)
|
||||
|
||||
|
||||
_G.Playopen = true
|
||||
play_Gui[4]:stop()
|
||||
|
||||
play_Gui[4]:execute(function ()
|
||||
shell.run("MusicLyrics.lua http://music168.liulikeji.cn:15843/api/song/lyric?id=".._G.music168_music_id.." "..px)
|
||||
end)
|
||||
play_Gui[4]:injectEvent("char","w")
|
||||
sleep(0.1)
|
||||
|
||||
--dfpwmURL = http.post("http://gmapi.liulikeji.cn:15842/dfpwm",textutils.serialiseJSON({ ["url"] = GetmusicUrl(_G.music168_music_id) } ))
|
||||
shell.run(mypath.."/speakerlib play "..GetmusicUrl(_G.music168_music_id))
|
||||
-- 检查是否播放完成自动跳转下一首
|
||||
if _G.music168_playopen then
|
||||
play_set_0()
|
||||
play_Gui[4]:stop()
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function while_thread()
|
||||
os.pullEvent("music168_play_stop")
|
||||
_G.getPlay = 0
|
||||
_G.getPlaymax = 0
|
||||
play_Gui[4]:stop()
|
||||
end
|
||||
|
||||
while true do
|
||||
if _G.music168_playopen then
|
||||
parallel.waitForAny(speaker_thread, while_thread)
|
||||
sleep(0.1)
|
||||
end
|
||||
sleep(0.01)
|
||||
end
|
||||
end
|
||||
|
||||
function gc()
|
||||
while true do
|
||||
play_Gui[4]:injectEvent(os.pullEvent())
|
||||
end
|
||||
|
||||
end
|
||||
_G.music168_playopen = false os.queueEvent("music168_play_stop")
|
||||
-----------------------------------------------------------------启动循环渲染器-----------------------------------------------------------------------------------------------
|
||||
parallel.waitForAll(basalt.autoUpdate, thread2, paste, speakerp,gc)
|
||||
-----------------------------------------------------------------以下结束-----------------------------------------------------------------------------------------------------
|
||||
|
||||
254
speaker.lua
254
speaker.lua
@@ -1,254 +0,0 @@
|
||||
if _G.Playprint == nil then _G.Playprint = true end
|
||||
local function get_speakers(name)
|
||||
if name then
|
||||
local speaker = peripheral.wrap(name)
|
||||
if speaker == nil then
|
||||
error(("Speaker %q does not exist"):format(name), 0)
|
||||
return
|
||||
elseif not peripheral.hasType(name, "speaker") then
|
||||
error(("%q is not a speaker"):format(name), 0)
|
||||
end
|
||||
|
||||
return { speaker }
|
||||
else
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
if #speakers == 0 then
|
||||
error("No speakers attached", 0)
|
||||
end
|
||||
return speakers
|
||||
end
|
||||
end
|
||||
function spekerStop()
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for _, speaker in pairs(speakers) do
|
||||
speaker.stop()
|
||||
end
|
||||
end
|
||||
|
||||
function speakerPlay(buffer)
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for _, speaker in pairs(speakers) do
|
||||
a = speaker.playAudio(buffer)
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
|
||||
local function pcm_decoder(chunk)
|
||||
local buffer = {}
|
||||
for i = 1, #chunk do
|
||||
buffer[i] = chunk:byte(i) - 128
|
||||
end
|
||||
return buffer
|
||||
end
|
||||
|
||||
|
||||
function displayProgressBar(percent)
|
||||
term.setCursorPos(1, 1)
|
||||
local screenWidth, _ = term.getSize() -- 获取终端的宽度和高度
|
||||
local barLength = math.floor(screenWidth - 8) -- 进度条长度为屏幕宽度减去固定长度(用于百分比显示)
|
||||
|
||||
local numBars = math.floor(percent / (100 / barLength))
|
||||
|
||||
-- 构建进度条字符串
|
||||
local progressBar = "["
|
||||
for i = 1, barLength do
|
||||
if i <= numBars then
|
||||
progressBar = progressBar .. "="
|
||||
else
|
||||
progressBar = progressBar .. " "
|
||||
end
|
||||
end
|
||||
progressBar = progressBar .. "] " .. math.floor(percent) .. "%" -- 百分比不显示小数点
|
||||
|
||||
-- 清空屏幕并输出进度条到屏幕顶部
|
||||
|
||||
term.setCursorPos(1, 3)
|
||||
print(progressBar)
|
||||
end
|
||||
|
||||
-- 测试函数
|
||||
|
||||
|
||||
|
||||
|
||||
local cmd = ...
|
||||
if cmd == "stop" then
|
||||
_G.Playopen = false
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for _, speaker in pairs(speakers) do
|
||||
speaker.stop()
|
||||
end
|
||||
spekerStop()
|
||||
elseif cmd == "play" then
|
||||
local _, file, type = ...
|
||||
|
||||
local handle, err
|
||||
if http and file:match("^https?://") then
|
||||
if type == "mp3" then
|
||||
if _G.Playprint then print("mp3 > dfpwm.....") end
|
||||
local json = textutils.serialiseJSON({ ["url"] = file } )
|
||||
handle, err = http.get{ url = http.post("http://gmapi.liulikeji.cn:15842/dfpwm",json).readAll(), binary = true }
|
||||
else
|
||||
handle, err = http.get{ url = file, binary = true }
|
||||
end
|
||||
else
|
||||
handle, err = fs.open(file, "rb")
|
||||
end
|
||||
|
||||
if not handle then
|
||||
error(err, 0)
|
||||
end
|
||||
|
||||
local start = handle.read(4)
|
||||
local pcm = false
|
||||
local size = 16 * 1024 - 4
|
||||
if start == "RIFF" then
|
||||
handle.read(4)
|
||||
if handle.read(8) ~= "WAVEfmt " then
|
||||
handle.close()
|
||||
error("Could not play audio: Unsupported WAV file", 0)
|
||||
end
|
||||
|
||||
local fmtsize = ("<I4"):unpack(handle.read(4))
|
||||
local fmt = handle.read(fmtsize)
|
||||
local format, channels, rate, _, _, bits = ("<I2I2I4I4I2I2"):unpack(fmt)
|
||||
if not ((format == 1 and bits == 8) or (format == 0xFFFE and bits == 1)) then
|
||||
handle.close()
|
||||
error("Could not play audio: Unsupported WAV file", 0)
|
||||
end
|
||||
if channels ~= 1 or rate ~= 48000 then
|
||||
end
|
||||
if format == 0xFFFE then
|
||||
local guid = fmt:sub(25)
|
||||
if guid ~= "\x3A\xC1\xFA\x38\x81\x1D\x43\x61\xA4\x0D\xCE\x53\xCA\x60\x7C\xD1" then -- DFPWM format GUID
|
||||
handle.close()
|
||||
error("Could not play audio: Unsupported WAV file", 0)
|
||||
end
|
||||
size = size + 4
|
||||
else
|
||||
pcm = true
|
||||
size = 16 * 1024 * 8
|
||||
end
|
||||
|
||||
repeat
|
||||
local chunk = handle.read(4)
|
||||
if chunk == nil then
|
||||
handle.close()
|
||||
error("Could not play audio: Invalid WAV file", 0)
|
||||
elseif chunk ~= "data" then -- Ignore extra chunks
|
||||
local size = ("<I4"):unpack(handle.read(4))
|
||||
handle.read(size)
|
||||
end
|
||||
until chunk == "data"
|
||||
|
||||
handle.read(4)
|
||||
start = nil
|
||||
end
|
||||
|
||||
|
||||
local decoder = pcm and pcm_decoder or require "cc.audio.dfpwm".make_decoder()
|
||||
local b1=true
|
||||
achunk_1 = handle.readAll()
|
||||
local achunk_s = achunk_1
|
||||
local achunk_K = #achunk_1
|
||||
whilecs_1 = 0
|
||||
_G.setPlay = nil
|
||||
|
||||
function play1()
|
||||
_G.Playopen = true
|
||||
|
||||
_G.Playstop = false
|
||||
c1=true
|
||||
|
||||
while _G.Playopen do
|
||||
|
||||
if _G.Playstop then
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for _, speaker in pairs(speakers) do
|
||||
speaker.stop()
|
||||
end
|
||||
|
||||
if _G.Playprint then print("play stop....") end
|
||||
while _G.Playstop do sleep(0.1) end
|
||||
b1=true
|
||||
c1=false
|
||||
end
|
||||
|
||||
--local chunk = handle.read(size)
|
||||
if c1 then
|
||||
chunk = achunk_1:sub(0,size)
|
||||
achunk_1 = achunk_1:sub(size+1,-1)
|
||||
|
||||
local achunk_ZK = achunk_K / size
|
||||
|
||||
if _G.setPlay ~= nil then
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for _, speaker in pairs(speakers) do
|
||||
speaker.stop()
|
||||
end
|
||||
whilecs_1 = math.floor((setPlay / 100) * achunk_ZK)
|
||||
|
||||
achunk_1 = achunk_s:sub(size*whilecs_1,-1)
|
||||
chunk = achunk_1:sub(0,size)
|
||||
achunk_1 = achunk_1:sub(size+1,-1)
|
||||
_G.setPlay = nil
|
||||
b1 = true
|
||||
end
|
||||
|
||||
local f = whilecs_1 / achunk_ZK
|
||||
_G.getPlay = f
|
||||
if _G.Playprint then
|
||||
term.clear()
|
||||
displayProgressBar(f*100)
|
||||
print(whilecs_1)
|
||||
print(achunk_ZK)
|
||||
print(f)
|
||||
end
|
||||
|
||||
whilecs_1 = whilecs_1+1
|
||||
|
||||
if f >= 1 then break end
|
||||
|
||||
if chunk == nil then break end
|
||||
|
||||
if start then
|
||||
chunk, start = start .. chunk, nil
|
||||
size = size + 4
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local buffer = decoder(chunk)
|
||||
if b1 then
|
||||
speakerPlay(buffer)
|
||||
b1=false
|
||||
c1 = true
|
||||
|
||||
else
|
||||
|
||||
while setPlay==nil and _G.Playstop==false do
|
||||
local speakers = { peripheral.find("speaker") }
|
||||
for i,speaker in pairs(speakers) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
speakerPlay(buffer)
|
||||
break
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
_G.getPlay=nil
|
||||
end
|
||||
|
||||
|
||||
play1()
|
||||
|
||||
|
||||
spekerStop()
|
||||
|
||||
handle.close()
|
||||
else
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
end
|
||||
383
speakerlib.lua
Normal file
383
speakerlib.lua
Normal file
@@ -0,0 +1,383 @@
|
||||
-- SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
-- 全局控制变量
|
||||
_G.getPlaymax = 0 -- 总时间(秒)
|
||||
_G.getPlay = 0 -- 当前播放时间(秒)
|
||||
_G.setPlay = 0 -- 设置播放进度(秒)
|
||||
_G.Playopen = true -- 播放开关(false停止)
|
||||
_G.Playstop = false -- 暂停控制(true暂停,false恢复)
|
||||
_G.Playprint = false -- 信息输出开关(true开,false关)
|
||||
_G.setVolume = 1 -- 音量控制(0-3)
|
||||
|
||||
local API_URL = "http://newgmapi.liulikeji.cn/api/ffmpeg"
|
||||
local mypath = "/"..fs.getDir(shell.getRunningProgram())
|
||||
|
||||
-- 扬声器配置
|
||||
local speakerlist = {
|
||||
main = {},
|
||||
left = {},
|
||||
right = {}
|
||||
}
|
||||
|
||||
local function printlog(...)
|
||||
if _G.Playprint then
|
||||
print(...)
|
||||
end
|
||||
end
|
||||
|
||||
local function loadSpeakerConfig()
|
||||
local speaker_groups = fs.open(mypath.."/speaker_groups.cfg","r")
|
||||
if speaker_groups then
|
||||
local content = speaker_groups.readAll()
|
||||
speaker_groups.close()
|
||||
if content then
|
||||
local success, tableData = pcall(textutils.unserialise, content)
|
||||
if success and type(tableData) == "table" then
|
||||
speakerlist = { main = {}, left = {}, right = {} }
|
||||
for group_name, speakers in pairs(tableData) do
|
||||
if speakerlist[group_name] then
|
||||
for _, speaker_name in ipairs(speakers) do
|
||||
local speaker = peripheral.wrap(speaker_name)
|
||||
if speaker and peripheral.hasType(speaker_name, "speaker") then
|
||||
table.insert(speakerlist[group_name], speaker)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 默认配置:所有扬声器都在main组
|
||||
speakerlist = {
|
||||
main = { peripheral.find("speaker") },
|
||||
left = {},
|
||||
right = {}
|
||||
}
|
||||
end
|
||||
|
||||
local function Get_dfpwm_url(INPUT_URL, args)
|
||||
local requestData = {
|
||||
input_url = INPUT_URL,
|
||||
args = args,
|
||||
output_format = "dfpwm"
|
||||
}
|
||||
|
||||
local response, err = http.post(
|
||||
API_URL,
|
||||
textutils.serializeJSON(requestData),
|
||||
{ ["Content-Type"] = "application/json" }
|
||||
)
|
||||
|
||||
if not response then
|
||||
error("HTTP Request Failure: "..(err or "Unknown error"))
|
||||
end
|
||||
|
||||
local responseData = textutils.unserializeJSON(response.readAll())
|
||||
response.close()
|
||||
|
||||
if responseData.status ~= "success" then
|
||||
error("Conversion failed: "..(responseData.error or "Unknown error"))
|
||||
end
|
||||
|
||||
return responseData.download_url
|
||||
end
|
||||
|
||||
local function get_total_duration(url)
|
||||
if _G.Playprint then printlog("Calculating duration...") end
|
||||
local handle, err = http.get(url)
|
||||
if not handle then
|
||||
error("Could not get duration: " .. (err or "Unknown error"))
|
||||
end
|
||||
|
||||
local data = handle.readAll()
|
||||
handle.close()
|
||||
|
||||
-- DFPWM: 每字节8个样本,48000采样率
|
||||
local total_length = (#data * 8) / 48000
|
||||
return total_length, #data
|
||||
end
|
||||
|
||||
local function play_audio_chunk(speakers, buffer)
|
||||
if #speakers > 0 and buffer and #buffer > 0 then
|
||||
for _, speaker in pairs(speakers) do
|
||||
local success = false
|
||||
while not success and _G.Playopen do
|
||||
success = speaker.playAudio(buffer, _G.setVolume)
|
||||
if not success then
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local cmd = ...
|
||||
|
||||
if cmd == "stop" then
|
||||
local all_speakers = {}
|
||||
for _, group in pairs(speakerlist) do
|
||||
for _, speaker in pairs(group) do
|
||||
table.insert(all_speakers, speaker)
|
||||
end
|
||||
end
|
||||
|
||||
for _, speaker in pairs(all_speakers) do
|
||||
speaker.stop()
|
||||
end
|
||||
elseif cmd == "play" then
|
||||
local _, file = ...
|
||||
if not file then
|
||||
error("Usage: speaker play <url>", 0)
|
||||
end
|
||||
|
||||
if not http or not file:match("^https?://") then
|
||||
error("Only HTTP/HTTPS URLs are supported", 0)
|
||||
end
|
||||
|
||||
-- 加载扬声器配置
|
||||
loadSpeakerConfig()
|
||||
|
||||
-- 检查是否有扬声器
|
||||
local has_speakers = false
|
||||
for _, group in pairs(speakerlist) do
|
||||
if #group > 0 then
|
||||
has_speakers = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not has_speakers then
|
||||
error("No speakers attached", 0)
|
||||
end
|
||||
|
||||
-- 获取DFPWM转换URL
|
||||
local main_dfpwm_url, left_dfpwm_url, right_dfpwm_url
|
||||
local main_httpfile, left_httpfile, right_httpfile
|
||||
|
||||
if _G.Playprint then printlog("Converting audio...") end
|
||||
|
||||
if #speakerlist.main > 0 then
|
||||
main_dfpwm_url = Get_dfpwm_url(file, { "-vn", "-ar", "48000", "-ac", "1" })
|
||||
end
|
||||
|
||||
if #speakerlist.left > 0 then
|
||||
left_dfpwm_url = Get_dfpwm_url(file, { "-vn", "-ar", "48000", "-filter_complex", "pan=mono|c0=FL" })
|
||||
end
|
||||
|
||||
if #speakerlist.right > 0 then
|
||||
right_dfpwm_url = Get_dfpwm_url(file, { "-vn", "-ar", "48000", "-filter_complex", "pan=mono|c0=FR" })
|
||||
end
|
||||
|
||||
-- 计算总时长(使用任意一个通道)
|
||||
local total_length, total_size
|
||||
if main_dfpwm_url then
|
||||
total_length, total_size = get_total_duration(main_dfpwm_url)
|
||||
elseif left_dfpwm_url then
|
||||
total_length, total_size = get_total_duration(left_dfpwm_url)
|
||||
elseif right_dfpwm_url then
|
||||
total_length, total_size = get_total_duration(right_dfpwm_url)
|
||||
else
|
||||
error("No audio channels available", 0)
|
||||
end
|
||||
|
||||
-- 设置总时间
|
||||
_G.getPlaymax = total_length
|
||||
_G.getPlay = 0
|
||||
|
||||
if _G.Playprint then
|
||||
printlog("Playing " .. file .. " (" .. math.ceil(total_length) .. "s)")
|
||||
end
|
||||
|
||||
-- 创建HTTP连接
|
||||
if main_dfpwm_url then
|
||||
main_httpfile = http.get(main_dfpwm_url)
|
||||
if not main_httpfile then
|
||||
error("Could not open main audio stream")
|
||||
end
|
||||
end
|
||||
|
||||
if left_dfpwm_url then
|
||||
left_httpfile = http.get(left_dfpwm_url)
|
||||
if not left_httpfile then
|
||||
error("Could not open left audio stream")
|
||||
end
|
||||
end
|
||||
|
||||
if right_dfpwm_url then
|
||||
right_httpfile = http.get(right_dfpwm_url)
|
||||
if not right_httpfile then
|
||||
error("Could not open right audio stream")
|
||||
end
|
||||
end
|
||||
|
||||
-- 初始化DFPWM解码器
|
||||
local decoder = require "cc.audio.dfpwm".make_decoder()
|
||||
local left_decoder = require "cc.audio.dfpwm".make_decoder()
|
||||
local right_decoder = require "cc.audio.dfpwm".make_decoder()
|
||||
|
||||
-- 每次读取的字节数(DFPWM: 每秒6000字节)
|
||||
local chunk_size = 6000
|
||||
local bytes_read = 0
|
||||
|
||||
-- 初始化播放位置
|
||||
if _G.setPlay > 0 then
|
||||
local skip_bytes = math.floor(_G.setPlay * 6000)
|
||||
if skip_bytes < total_size then
|
||||
-- 跳过指定字节数
|
||||
local skipped = 0
|
||||
while skipped < skip_bytes and _G.Playopen do
|
||||
local to_skip = math.min(8192, skip_bytes - skipped)
|
||||
if main_httpfile then main_httpfile.read(to_skip) end
|
||||
if left_httpfile then left_httpfile.read(to_skip) end
|
||||
if right_httpfile then right_httpfile.read(to_skip) end
|
||||
skipped = skipped + to_skip
|
||||
bytes_read = bytes_read + to_skip
|
||||
end
|
||||
_G.getPlay = _G.setPlay
|
||||
_G.setPlay = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- 主播放循环
|
||||
while bytes_read < total_size and _G.Playopen do
|
||||
-- 检查是否需要设置播放位置
|
||||
if _G.setPlay > 0 then
|
||||
-- 重新打开所有连接并跳转
|
||||
if main_httpfile then main_httpfile.close() end
|
||||
if left_httpfile then left_httpfile.close() end
|
||||
if right_httpfile then right_httpfile.close() end
|
||||
|
||||
if main_dfpwm_url then
|
||||
main_httpfile = http.get(main_dfpwm_url)
|
||||
if not main_httpfile then error("Could not reopen main stream") end
|
||||
end
|
||||
|
||||
if left_dfpwm_url then
|
||||
left_httpfile = http.get(left_dfpwm_url)
|
||||
if not left_httpfile then error("Could not reopen left stream") end
|
||||
end
|
||||
|
||||
if right_dfpwm_url then
|
||||
right_httpfile = http.get(right_dfpwm_url)
|
||||
if not right_httpfile then error("Could not reopen right stream") end
|
||||
end
|
||||
|
||||
local skip_bytes = math.floor(_G.setPlay * 6000)
|
||||
if skip_bytes < total_size then
|
||||
local skipped = 0
|
||||
while skipped < skip_bytes and _G.Playopen do
|
||||
local to_skip = math.min(8192, skip_bytes - skipped)
|
||||
if main_httpfile then main_httpfile.read(to_skip) end
|
||||
if left_httpfile then left_httpfile.read(to_skip) end
|
||||
if right_httpfile then right_httpfile.read(to_skip) end
|
||||
skipped = skipped + to_skip
|
||||
bytes_read = skip_bytes
|
||||
end
|
||||
_G.getPlay = _G.setPlay
|
||||
_G.setPlay = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- 检查暂停状态
|
||||
while _G.Playstop and _G.Playopen do
|
||||
os.sleep(0.1)
|
||||
end
|
||||
|
||||
-- 检查停止状态
|
||||
if not _G.Playopen then
|
||||
break
|
||||
end
|
||||
|
||||
-- 读取音频数据
|
||||
local main_chunk, left_chunk, right_chunk
|
||||
local main_buffer, left_buffer, right_buffer
|
||||
|
||||
if main_httpfile then
|
||||
main_chunk = main_httpfile.read(chunk_size)
|
||||
end
|
||||
|
||||
if left_httpfile then
|
||||
left_chunk = left_httpfile.read(chunk_size)
|
||||
end
|
||||
|
||||
if right_httpfile then
|
||||
right_chunk = right_httpfile.read(chunk_size)
|
||||
end
|
||||
|
||||
-- 检查是否所有通道都没有数据
|
||||
if (not main_chunk or #main_chunk == 0) and
|
||||
(not left_chunk or #left_chunk == 0) and
|
||||
(not right_chunk or #right_chunk == 0) then
|
||||
break
|
||||
end
|
||||
|
||||
-- 解码音频数据
|
||||
if main_chunk and #main_chunk > 0 then
|
||||
main_buffer = decoder(main_chunk)
|
||||
end
|
||||
|
||||
if left_chunk and #left_chunk > 0 then
|
||||
left_buffer = left_decoder(left_chunk)
|
||||
end
|
||||
|
||||
if right_chunk and #right_chunk > 0 then
|
||||
right_buffer = right_decoder(right_chunk)
|
||||
end
|
||||
|
||||
-- 并行播放所有通道
|
||||
parallel.waitForAll(
|
||||
function()
|
||||
if main_buffer and #main_buffer > 0 then
|
||||
play_audio_chunk(speakerlist.main, main_buffer)
|
||||
end
|
||||
end,
|
||||
function()
|
||||
if right_buffer and #right_buffer > 0 then
|
||||
play_audio_chunk(speakerlist.right, right_buffer)
|
||||
end
|
||||
end,
|
||||
function()
|
||||
if left_buffer and #left_buffer > 0 then
|
||||
play_audio_chunk(speakerlist.left, left_buffer)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
-- 更新进度
|
||||
local max_chunk_size = math.max(
|
||||
main_chunk and #main_chunk or 0,
|
||||
left_chunk and #left_chunk or 0,
|
||||
right_chunk and #right_chunk or 0
|
||||
)
|
||||
bytes_read = bytes_read + max_chunk_size
|
||||
_G.getPlay = bytes_read / 6000
|
||||
|
||||
if _G.Playprint then
|
||||
term.setCursorPos(1, term.getCursorPos())
|
||||
printlog(("Playing: %ds / %ds"):format(math.floor(_G.getPlay), math.ceil(total_length)))
|
||||
end
|
||||
end
|
||||
|
||||
-- 关闭HTTP连接
|
||||
if main_httpfile then main_httpfile.close() end
|
||||
if left_httpfile then left_httpfile.close() end
|
||||
if right_httpfile then right_httpfile.close() end
|
||||
|
||||
if _G.Playprint and _G.Playopen then
|
||||
printlog("Playback finished.")
|
||||
end
|
||||
|
||||
-- 重置播放状态
|
||||
_G.Playopen = true
|
||||
_G.Playstop = false
|
||||
_G.getPlay = 0
|
||||
else
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
printlog("Usage:")
|
||||
printlog(programName .. " play <url>")
|
||||
printlog(programName .. " stop")
|
||||
end
|
||||
Reference in New Issue
Block a user