// 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 net.avadeaux.klipspringer.codec.PcmFormat;

/** Runnable that continuously updates terminal display with current status. Terminates and updates
  * the display with a shutdown hook, and should therefore not be run in a daemon thread.
  */
public class DeviceTerminalDisplay implements Runnable {
    private final PcmFormat format;
    private DeviceFeeder.Play playState = null, recState = null;
    private boolean terminate = false;
    private volatile String backup = "";

    public DeviceTerminalDisplay(PcmFormat format) {
        this.format = format;
    }

    public synchronized void startPlay(DeviceFeeder.Play playState) {
        this.playState = playState;
        notifyAll();
    }

    public synchronized void stopPlay() {
        this.playState = null;
        notifyAll();
    }

    public synchronized void startRecord(DeviceFeeder.Play recState) {
        this.recState = recState;
        notifyAll();
    }

    public synchronized void stopRecord() {
        this.recState = null;
        notifyAll();
    }

    /** Gets display into a state where output (for instance an error message) can be printed
      * without being erased. The behavior is not thread safe, but it should work in the vast
      * majority of cases.
      */
    public void nobackup() {
        System.out.print(backup);
        backup = "";
    }

    public void run() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    synchronized (DeviceTerminalDisplay.this) {
                        terminate = true;
                        DeviceTerminalDisplay.this.notifyAll();
                    }
                }
            });
        System.out.print("\n");
        backup = "\u001b[A\u001b[K";
        while (true) {
            boolean rec = false;
            double ti = Double.NaN;
            boolean quit = false;
            synchronized (this) {
                try { wait(1000); } catch (InterruptedException e) { }
                if (terminate) {
                    quit = true;
                } else if (recState != null) {
                    rec = true;
                    try { ti = recState.time(); } catch (Exception ex) { }
                } else {
                    try { ti = playState == null ? Double.NaN : playState.time(); } catch (Exception ex) { }
                }
            }
            System.out.print(backup);
            if (quit) { break; }
            if (rec) { System.out.print("\u001b[31m\u001b[5mREC\u001b[25m\u001b[0m "); }
            if (!Double.isNaN(ti)) {
                long s = Math.round(ti);
                if (s >= 3600) {
                    System.out.print(String.format("%d:%02d:%02d", s/3600, (s%3600)/60, s%60));
                } else {
                    System.out.print(String.format("%d:%02d", s/60, s%60));
                }
                System.out.print(" ");
            }
            int ch = format.channels();
            System.out.print(format.significantBips()+"-bit "+format.rate()+" Hz "+ (ch == 2 ? "stereo" : (ch == 1 ? "mono" : ch+"-channel")));
            System.out.print("\n");
        }
    }
}

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