288 lines
6.4 KiB
Lua
288 lines
6.4 KiB
Lua
local createVersion = require('/apis/libversion');
|
|
|
|
local command = ...;
|
|
|
|
local function printUsage()
|
|
print('music usage:');
|
|
print();
|
|
print('\t\t\tmusic');
|
|
print('\t\t\tmusic version');
|
|
print('\t\t\tmusic help');
|
|
end
|
|
|
|
if command == 'version' or command == '-version' or command == '--version' then
|
|
print('v' .. createVersion().forSelf());
|
|
return;
|
|
end
|
|
|
|
if command == 'help' or command == '-help' or command == '--help' then
|
|
printUsage();
|
|
return;
|
|
end
|
|
|
|
if command ~= nil and command ~= '' then
|
|
printUsage();
|
|
return;
|
|
end
|
|
|
|
local createMusic = require('/apis/libmusic');
|
|
local music = createMusic();
|
|
|
|
local tracks = music.listTracks();
|
|
if #tracks == 0 then
|
|
print('No music found in /data/musics');
|
|
return;
|
|
end
|
|
|
|
if not peripheral.find('speaker') then
|
|
print('No speaker attached');
|
|
return;
|
|
end
|
|
|
|
local createEventLoop = require('/apis/eventloop');
|
|
local createTui = require('/apis/libtui');
|
|
|
|
local eventloop = createEventLoop();
|
|
local ui = createTui(eventloop);
|
|
|
|
local Text = ui.Text;
|
|
local Button = ui.Button;
|
|
local Box = ui.Box;
|
|
local List = ui.List;
|
|
|
|
local player = music.createPlayer(eventloop);
|
|
|
|
-- Rows of UI chrome around the track list (header, now-playing, controls,
|
|
-- footer, plus the track box border). Used to size the scroll window.
|
|
local LIST_CHROME = 6;
|
|
|
|
local selectedIndex = 1; -- list cursor
|
|
local scrollOffset = 0; -- index of the first visible row - 1
|
|
local playingIndex = nil; -- track currently loaded in the player
|
|
local autoplay = false;
|
|
|
|
local function clampSelection()
|
|
if selectedIndex < 1 then
|
|
selectedIndex = 1;
|
|
elseif selectedIndex > #tracks then
|
|
selectedIndex = #tracks;
|
|
end
|
|
end
|
|
|
|
-- Keeps the selected row inside the visible window.
|
|
local function ensureVisible()
|
|
local _, height = term.getSize();
|
|
local visible = math.max(1, height - LIST_CHROME);
|
|
if selectedIndex <= scrollOffset then
|
|
scrollOffset = selectedIndex - 1;
|
|
elseif selectedIndex > scrollOffset + visible then
|
|
scrollOffset = selectedIndex - visible;
|
|
end
|
|
end
|
|
|
|
local function playIndex(index)
|
|
if index < 1 or index > #tracks then
|
|
return;
|
|
end
|
|
playingIndex = index;
|
|
selectedIndex = index;
|
|
player.play(tracks[index].path);
|
|
ensureVisible();
|
|
ui.rerender();
|
|
end
|
|
|
|
local function nextIndex()
|
|
return (playingIndex or selectedIndex) % #tracks + 1;
|
|
end
|
|
|
|
local function previousIndex()
|
|
return ((playingIndex or selectedIndex) - 2) % #tracks + 1;
|
|
end
|
|
|
|
player.onFinish(function()
|
|
if autoplay then
|
|
playIndex(nextIndex());
|
|
else
|
|
playingIndex = nil;
|
|
end
|
|
ui.rerender();
|
|
end);
|
|
|
|
local function Header()
|
|
return Box({
|
|
direction = 'row',
|
|
bgColor = colors.gray,
|
|
children = {
|
|
Text('Music', {
|
|
flex = 1,
|
|
color = colors.white,
|
|
bgColor = colors.gray,
|
|
}),
|
|
Button('X', {
|
|
color = colors.white,
|
|
bgColor = colors.red,
|
|
onClick = function(tui)
|
|
tui.exitUI('quit');
|
|
end,
|
|
}),
|
|
},
|
|
});
|
|
end
|
|
|
|
local function TrackRow(index)
|
|
local track = tracks[index];
|
|
local isCursor = index == selectedIndex;
|
|
local isPlaying = index == playingIndex;
|
|
local label = (isPlaying and '> ' or ' ') .. track.name;
|
|
return Button(label, {
|
|
color = isCursor and colors.black or colors.white,
|
|
bgColor = isCursor and colors.white or colors.black,
|
|
onClick = function()
|
|
playIndex(index);
|
|
end,
|
|
});
|
|
end
|
|
|
|
local function TrackList()
|
|
local _, height = term.getSize();
|
|
local visible = math.max(1, height - LIST_CHROME);
|
|
local items = {};
|
|
for offset = 1, visible do
|
|
local index = scrollOffset + offset;
|
|
if index > #tracks then
|
|
break;
|
|
end
|
|
items[#items + 1] = TrackRow(index);
|
|
end
|
|
return List({
|
|
padding = 1,
|
|
children = items,
|
|
});
|
|
end
|
|
|
|
local function statusLabel()
|
|
local s = player.getStatus();
|
|
if s == 'playing' then return 'Playing'; end
|
|
if s == 'paused' then return 'Paused'; end
|
|
return 'Stopped';
|
|
end
|
|
|
|
local function NowPlaying()
|
|
local name = playingIndex and tracks[playingIndex].name or '(none)';
|
|
return Box({
|
|
direction = 'row',
|
|
children = {
|
|
Text(statusLabel() .. ': ' .. name, { flex = 1 }),
|
|
},
|
|
});
|
|
end
|
|
|
|
local function Controls()
|
|
return Box({
|
|
direction = 'row',
|
|
gap = 1,
|
|
children = {
|
|
Button('|<', {
|
|
onClick = function()
|
|
playIndex(previousIndex());
|
|
end,
|
|
}),
|
|
Button(player.getStatus() == 'playing' and 'Pause' or 'Play', {
|
|
onClick = function()
|
|
if player.getStatus() == 'stopped' then
|
|
playIndex(selectedIndex);
|
|
else
|
|
player.togglePause();
|
|
ui.rerender();
|
|
end
|
|
end,
|
|
}),
|
|
Button('Stop', {
|
|
onClick = function()
|
|
player.stop();
|
|
playingIndex = nil;
|
|
ui.rerender();
|
|
end,
|
|
}),
|
|
Button('>|', {
|
|
onClick = function()
|
|
playIndex(nextIndex());
|
|
end,
|
|
}),
|
|
Button('Autoplay: ' .. (autoplay and 'on' or 'off'), {
|
|
flex = 1,
|
|
color = autoplay and colors.black or colors.white,
|
|
bgColor = autoplay and colors.lime or colors.black,
|
|
onClick = function()
|
|
autoplay = not autoplay;
|
|
ui.rerender();
|
|
end,
|
|
}),
|
|
},
|
|
});
|
|
end
|
|
|
|
local function Footer()
|
|
return Text(
|
|
'up/down select enter play space pause s stop n/p next/prev q quit',
|
|
{ color = colors.lightGray }
|
|
);
|
|
end
|
|
|
|
local function App()
|
|
return Box({
|
|
direction = 'column',
|
|
children = {
|
|
Header(),
|
|
Box({
|
|
flex = 1,
|
|
border = true,
|
|
title = 'Tracks',
|
|
children = {
|
|
TrackList(),
|
|
},
|
|
}),
|
|
NowPlaying(),
|
|
Controls(),
|
|
Footer(),
|
|
},
|
|
});
|
|
end
|
|
|
|
eventloop.register('key', function(key)
|
|
if key == keys.up then
|
|
selectedIndex = selectedIndex - 1;
|
|
clampSelection();
|
|
ensureVisible();
|
|
ui.rerender();
|
|
elseif key == keys.down then
|
|
selectedIndex = selectedIndex + 1;
|
|
clampSelection();
|
|
ensureVisible();
|
|
ui.rerender();
|
|
elseif key == keys.enter then
|
|
playIndex(selectedIndex);
|
|
elseif key == keys.space then
|
|
if player.getStatus() == 'stopped' then
|
|
playIndex(selectedIndex);
|
|
else
|
|
player.togglePause();
|
|
ui.rerender();
|
|
end
|
|
elseif key == keys.s then
|
|
player.stop();
|
|
playingIndex = nil;
|
|
ui.rerender();
|
|
elseif key == keys.n then
|
|
playIndex(nextIndex());
|
|
elseif key == keys.p then
|
|
playIndex(previousIndex());
|
|
elseif key == keys.q then
|
|
ui.exitUI('quit');
|
|
end
|
|
end);
|
|
|
|
ui.render(App);
|
|
|
|
player.stop();
|