// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer;

import java.nio.file.Path;
import java.nio.file.Paths;
import net.avadeaux.klipspringer.codec.*;

public class DevicePlayer {
    private void run(String[] args) {
        String device = null;
        String output = Terminal.inTty() ? null : "none";
        double obufsec = 2.0;
        int urunreco = 2;
        int xbufframes = 131072;
        String formatString = "";
        Path recdir = Paths.get("");
        double ibufsec = 4.0;
        String controlsock = null;
        int controlthreads = 20;
        boolean cmdintf = true;
        boolean quiet = false;
        OutputSpec.Notify noti = new OutputSpec.Notify();

        String helpText = "Arguments:\n"
            +"-device <string>        Input device. If not specified, use audio system default\n"
            +"-format <string>        Default device format options. Default: '-r 44100 -b 16 -c 2 -e signed -B'\n"
            +"-ibufsec <float>        Input device PCM buffer time. Default is "+ibufsec+"\n"
            +"-output <string>        PCM output device or URL. Default is standard sound device if interactive\n"
            +"-obufsec <float>        External buffer time on PCM output. Default is "+obufsec+"\n"
            +"-urunreco <int>         Times to attempt recovery after buffer underrun. Default is "+urunreco+"\n"
            +"-xbufframes <int>       Minimum number of sample frames in transfer buffer. Default is "+xbufframes+"\n"
            +"-controlsock <path>     Socket path for control server. By default, there is no control server\n"
            +"-controlthreads <int>   Max threads in control server. Default is "+controlthreads+"\n"
            +"-cmdintf <boolean>      Use interactive command interface when applicable. Default is "+cmdintf+"\n"
            +"-recdir <path>          Directory of recorded files. Default is working directory\n"
            +"-q                      Suppress TTY output\n"
            +"-help or --help         Print this message\n\n"
            +"TTY key commands:\n"
            +"  SPACE                 Stop/start capture and playback\n"
            +"  r                     Start/stop recording file\n"
            +"  q                     Quit\n";

        for (int argPos = 0; argPos < args.length;) {
            String o = args[argPos++];
            if      ("-q"               .equals(o)) { quiet = true; continue; }
            String v = argPos < args.length ? args[argPos++] : "";
            if      ("-device"          .equals(o)) { device           = v; }
            else if ("-format"          .equals(o)) { formatString     = v; }
            else if ("-ibufsec"         .equals(o)) { ibufsec          = Double.parseDouble(v); }
            else if ("-output"          .equals(o)) { output           = v; }
            else if ("-obufsec"         .equals(o)) { obufsec          = Double.parseDouble(v); }
            else if ("-urunreco"        .equals(o)) { urunreco         = Integer.parseInt(v); }
            else if ("-xbufframes"      .equals(o)) { xbufframes       = Integer.parseInt(v); }
            else if ("-controlsock"     .equals(o)) { controlsock      = v; }
            else if ("-controlthreads"  .equals(o)) { controlthreads   = Integer.parseInt(v); }
            else if ("-cmdintf"         .equals(o)) { cmdintf          = Boolean.parseBoolean(v); }
            else if ("-recdir"          .equals(o)) { recdir           = Paths.get(v); }
            else if (o.startsWith("-h") || o.startsWith("--h")) { System.out.println(helpText); return; }
            else {
                System.err.println("Unregognized argument "+o+" (use -help for usage)");
                System.exit(2);
            }
        }
        PcmFormat format = Track.rawFormat(formatString);

        DeviceTerminalDisplay display = null;
        try {
            DeviceFeeder feeder = new DeviceFeeder(device, format, ibufsec, recdir.toAbsolutePath(), xbufframes);
            Device.Player.Factory outFact = OutputSpec.process(output, obufsec, urunreco, noti);
            if (controlsock != null && controlsock.length() > 0) {
                Path sock = Path.of(controlsock);
                AsyncCommandServer cmdServ = new AsyncCommandServer(new DeviceCommandInterface(feeder), sock, controlthreads);
                new Thread("Control Server") {
                    public void run() {
                        System.out.println("Ready control socket "+sock);
                        cmdServ.run();
                    }
                }.start();
            }
            new Thread("Device feeder") {
                public void run() {
                    try {
                        feeder.runFeed();
                    } catch (Throwable th) {
                        th.printStackTrace();
                        System.exit(1);
                    } finally {
                        System.exit(0);     // in case some thread is stuck in wait state
                    }
                }
            }.start();
            if (!quiet && Terminal.outTty()) {
                DeviceTerminalDisplay d = display = new DeviceTerminalDisplay(format);
                new Thread("Terminal display") {
                    public void run() {
                        noti.verifyReady();
                        d.run();
                    }
                }.start();
            }
            DeviceFeeder.Play play = outFact == null ? null : feeder.fetchStream(outFact);
            if (display != null) { display.startPlay(play); }
            if (cmdintf && Terminal.inTty()) {
                DeviceKeyInterface intf = new DeviceKeyInterface(feeder, outFact, display);
                DeviceTerminalDisplay d = display;
                new Thread("Interactive Interface") {
                    public void run() {
                        try {
                            intf.runUntilQuit(play);
                        } catch (Throwable th) {
                            if (d != null && Terminal.errTty()) { d.nobackup(); }
                            System.err.println(th);
                            if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
                            System.exit(1);
                        }
                        System.exit(0);
                    }
                }.start();
            }
        } catch (Throwable th) {
            if (display != null && Terminal.errTty()) { display.nobackup(); }
            System.err.println(th);
            if (System.getProperty("klipspringer.debug") != null) { th.printStackTrace(); }
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        new DevicePlayer().run(args);
    }
}

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home