// 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.io.*;
import net.avadeaux.klipspringer.codec.Device;
/**
* Keypress interface of the track player. Should not run simultaneously with {@link
* InteractiveCommandInterface}.
*/
public class TrackKeyInterface {
public interface Control {
void seek(int trackIx, double time, Device.Player.Factory outFact, Object... extra) throws IOException;
default void pause(Device.Player.Factory resumeFact) throws IOException { }
default void unpause() throws IOException { }
}
private final Control control;
private final Device.Player.Factory serverOutFact;
private final PlayMonitor state;
private final int maxTrackIx;
private boolean quit = false;
private double restartTime;
/** Creates a command interface with text input and output, acting on the given control and
* querying the given state service for current state.
*/
public TrackKeyInterface(Control control, Device.Player.Factory serverOutFact, PlayMonitor state, int maxTrackIx, double restartTime) {
this.control = control;
this.serverOutFact = serverOutFact;
this.state = state;
this.maxTrackIx = maxTrackIx;
this.restartTime = restartTime;
}
public TrackKeyInterface(Control control, Device.Player.Factory serverOutFact, PlayMonitor state, int maxTrackIx) {
this(control, serverOutFact, state, maxTrackIx, Double.NaN);
}
private void skip(int step) throws IOException {
int trackIx = state.playingTrackIndex();
if (trackIx < 0) { return; }
trackIx += step;
if (trackIx > maxTrackIx) { return; }
control.seek(trackIx, 0, serverOutFact);
}
private void toggle(Query.Onoff extra) throws IOException {
if (extra == Query.Onoff.DETECT) { return; }
double time = state.playingTime();
if (Double.isNaN(time)) { return; }
control.seek(0, time, serverOutFact, Query.toggle(extra));
}
private void seekr(double off) throws IOException {
double time = state.playingTime();
time += off;
if (Double.isNaN(time)) { return; }
control.seek(0, time, serverOutFact, Query.Onoff.DETECT);
}
/** Call {@link Terminal#enableInterruptGetChar()} if this method is to be used. */
public synchronized void quit() {
if (quit) { return; }
try {
Terminal.interruptGetChar();
} catch (Exception ex) {
if (System.getProperty("klipspringer.debug") != null) {
ex.printStackTrace();
}
}
quit = true;
}
public void runUntilQuit() throws IOException {
Terminal.startKeyListen();
try {
long lasttime = 0;
int lastch = 0;
while (true) {
synchronized (this) {
if (quit) { break; }
}
int ch;
ch = Terminal.getChar();
long now = System.currentTimeMillis();
switch (ch) {
case ' ':
if (state.paused()) { control.unpause(); }
else { control.pause(serverOutFact); }
break;
case 'f':
toggle(state.fading());
break;
case 'm':
toggle(state.monomixing());
break;
case 'p':
restartTime = state.playingTime();
break;
case 'q':
quit = true;
break;
case 'r':
if (!Double.isNaN(restartTime)) { control.seek(0, restartTime, serverOutFact); }
break;
case '<':
skip(lastch == '<' && now - lasttime < 1000 ? -1 : 0);
break;
case '>':
skip(1);
break;
case 0x1b: // ESC
if (Terminal.getChar() != '[') { break; }
case 0x9b: // C1 CSI
ch = Terminal.getChar();
switch (ch) {
case 'A': seekr(+30); break;
case 'B': seekr(-30); break;
case 'C': seekr(+5); break;
case 'D': seekr(-5); break;
default: // gobble to end of control sequence
while (ch < 0x40 || ch > 0x7E) { ch = Terminal.getChar(); }
}
break;
default:
// ignore
}
lasttime = now;
lastch = ch;
}
} finally {
try {
Terminal.stopKeyListen();
} catch (IOException ex) {
if (System.getProperty("klipspringer.debug") != null) {
System.err.println("Error resetting terminal: "+ex);
ex.printStackTrace();
}
}
}
}
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home