上传文件至 /

This commit is contained in:
2026-01-10 02:14:32 +08:00
parent c1bc227490
commit 8a456f3253
3 changed files with 815 additions and 0 deletions

343
speakerlib.lua Normal file
View File

@@ -0,0 +1,343 @@
-- 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 mypath = "/"..fs.getDir(shell.getRunningProgram())
-- 扬声器配置
local speakerlist = {
main = {},
left = {},
right = {}
}
local function printlog(...)
if _G.Playprint then
print(...)
end
end
local function loadSpeakerConfig()
-- 默认配置所有扬声器都在main组
speakerlist = {
main = { peripheral.find("speaker") },
left = {},
right = {}
}
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
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(url .. "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 _, mono_url, left_url, right_url = ...
-- 加载扬声器配置
loadSpeakerConfig()
print("main speaker:"..#speakerlist.main)
print("left speaker:"..#speakerlist.left)
print("right speaker:"..#speakerlist.right)
-- 检查是否有扬声器
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
-- 检查是否至少有一个音频URL
if not mono_url and not left_url and not right_url then
error("Usage: speaker play [mono_url] [left_url] [right_url]\nAt least one audio URL is required", 0)
end
-- 计算总时长(使用任意一个通道)
local total_length, total_size
if mono_url then
total_length, total_size = get_total_duration(mono_url)
elseif left_url then
total_length, total_size = get_total_duration(left_url)
elseif right_url then
total_length, total_size = get_total_duration(right_url)
end
-- 设置总时间
_G.getPlaymax = total_length
_G.getPlay = 0
if _G.Playprint then
printlog("Playing audio (" .. math.ceil(total_length) .. "s)")
end
-- 创建HTTP连接
local mono_httpfile, left_httpfile, right_httpfile
if mono_url and #speakerlist.main > 0 then
mono_httpfile = http.get(mono_url)
if not mono_httpfile then
error("Could not open mono audio stream")
end
end
if left_url and #speakerlist.left > 0 then
left_httpfile = http.get(left_url)
if not left_httpfile then
error("Could not open left audio stream")
end
end
if right_url and #speakerlist.right > 0 then
right_httpfile = http.get(right_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 mono_httpfile then mono_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
-- 主播放循环
os.pullEvent("audio_start")
while bytes_read < total_size and _G.Playopen do
-- 检查是否需要设置播放位置
if _G.setPlay > 0 then
-- 重新打开所有连接并跳转
if mono_httpfile then mono_httpfile.close() end
if left_httpfile then left_httpfile.close() end
if right_httpfile then right_httpfile.close() end
if mono_url and #speakerlist.main > 0 then
mono_httpfile = http.get(mono_url)
if not mono_httpfile then error("Could not reopen mono stream") end
end
if left_url and #speakerlist.left > 0 then
left_httpfile = http.get(left_url)
if not left_httpfile then error("Could not reopen left stream") end
end
if right_url and #speakerlist.right > 0 then
right_httpfile = http.get(right_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 mono_httpfile then mono_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 mono_chunk, left_chunk, right_chunk
local mono_buffer, left_buffer, right_buffer
if mono_httpfile then
mono_chunk = mono_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 mono_chunk or #mono_chunk == 0) and
(not left_chunk or #left_chunk == 0) and
(not right_chunk or #right_chunk == 0) then
break
end
-- 解码音频数据
if mono_chunk and #mono_chunk > 0 then
mono_buffer = decoder(mono_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 mono_buffer and #mono_buffer > 0 then
play_audio_chunk(speakerlist.main, mono_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(
mono_chunk and #mono_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 mono_httpfile then mono_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 .. " stop")
printlog(programName .. " play [mono_url] [left_url] [right_url]")
printlog("Note: At least one URL is required")
end