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();