Something went wrong. Try again.
A decentralized music tracking and discovery platform built on AT Protocol ๐ต rocksky.app
spotify atproto lastfm musicbrainz scrobbling listenbrainz
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980import { QueryClientProvider } from "@tanstack/react-query";import { render } from "ink";import { loadToken } from "lib/token";import React from "react";import { App } from "../tui/App";import { closeNfc } from "../tui/nfc";import { playerController } from "../tui/player";import { queryClient } from "../tui/queryClient";import { startRockskyRemote } from "../tui/rockskyWs";import { saveSession } from "../tui/session";import { loadSession } from "../tui/session";import { loadSettings, saveSettings } from "../tui/settings";
// Leave the alternate screen buffer and re-show the cursor, restoring whatever// was on the terminal before the TUI launched.function restoreTerminal() { process.stdout.write("\x1b[?1049l\x1b[?25h");}
export async function tui(opts: { output?: string } = {}) { if (!process.stdin.isTTY) { console.error("The Rocksky TUI requires an interactive terminal."); process.exit(1); }
// Safety net: restore the terminal however the process ends (q, Ctrl-C, crash). process.on("exit", restoreTerminal);
// Restore persisted sound / transport preferences and keep them saved // (debounced) whenever they change. A `--output` flag overrides the persisted // audio backend for this run without being written back to settings.toml. const settings = loadSettings(); if (opts.output) settings.output = opts.output; playerController.applySettings(settings); let saveTimer: NodeJS.Timeout; playerController.onSettingsChange = () => { clearTimeout(saveTimer); saveTimer = setTimeout( () => saveSettings(playerController.snapshotSettings()), 400, ); };
// Restore the last playback session (queue + position) โ not auto-played; // the user resumes from the player bar. const session = loadSession(); if (session) { playerController.restoreSession({ items: session.queue, index: session.index, positionMs: session.positionMs, }); }
// Expose this player to the web/mobile miniplayers as a controllable device // and mirror its now-playing there in real time. const remote = startRockskyRemote(() => loadToken());
const { waitUntilExit } = render( <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider>, ); await waitUntilExit();
// Flush pending settings + final session on exit. // Ink has fully unmounted here; leave the alternate screen last so the TUI is // cleared and the pre-launch terminal is restored without artifacts. clearTimeout(saveTimer!); remote.stop(); closeNfc(); saveSettings(playerController.snapshotSettings()); saveSession(playerController.sessionSnapshot()); restoreTerminal();
// The native audio engine keeps handles on the event loop (and its close() // can block), so force a clean exit โ this also frees the audio device. process.exit(0);}