// 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.IOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Executor;
import javax.sound.sampled.*;
import net.avadeaux.klipspringer.codec.*;
public abstract class CommandInterface {
@FunctionalInterface
protected interface Command {
void exec(Query q, Writer w) throws IOException;
}
protected final String NO_PLAYER_ID = "";
protected final Map<String, Command> cmds = new HashMap<String, Command>();
protected final Map<String, AudioStream> streamHandles = new HashMap<String, AudioStream>();
private final Executor streamKeepAliveExecutor;
private final double streamKeepAliveTimeoutSecs;
public CommandInterface(Executor streamKeepAliveExecutor, double streamKeepAliveTimeoutSecs) {
this.streamKeepAliveExecutor = streamKeepAliveExecutor;
this.streamKeepAliveTimeoutSecs = streamKeepAliveTimeoutSecs;
}
private Device.Player.Factory streamFactory(Query q, WritableByteChannel out) {
String type = q.value("type", "flac");
double writeaheadSecs = q.doubleValue("writeahead", 10.0);
double timeoutSecs = q.doubleValue("timeout", 0);
int bufferFrames = q.intValue("buf_frames", 4096);
String id = q.value("player_id", NO_PLAYER_ID);
if ("oga".equals(type) || "flac".equals(type)) {
int holdingBytes = q.intValue("clientbuf", 49152);
boolean oga = "oga".equals(type);
int comprLevel = q.intValue("compr_level", 5);
return new AudioStream.Factory() {
public AudioStream open(PcmFormat fmt) throws IOException {
FlacStream stream = new FlacStream(fmt, oga, comprLevel, bufferFrames, writeaheadSecs, timeoutSecs, holdingBytes, out, streamKeepAliveExecutor, streamKeepAliveTimeoutSecs);
streamHandles.put(id, stream);
return stream;
}
public Device.BitLayout layout(PcmFormat fmt) { return FlacStream.BIT_LAYOUT; }
public PcmFormat selectFormat(AudioFormat fmt) throws UnsupportedFormatException {
return FlacStream.selectFormat(fmt);
}
};
} else if ("ogg".equals(type)) {
int holdingBytes = q.intValue("clientbuf", 49152);
float quality = (float) q.doubleValue("quality", 0.9);
return new AudioStream.Factory() {
public AudioStream open(PcmFormat fmt) throws IOException {
VorbisStream stream = new VorbisStream(fmt, quality, writeaheadSecs, timeoutSecs, holdingBytes, out, streamKeepAliveExecutor, streamKeepAliveTimeoutSecs);
streamHandles.put(id, stream);
return stream;
}
public Device.BitLayout layout(PcmFormat fmt) { return VorbisStream.BIT_LAYOUT; }
public PcmFormat selectFormat(AudioFormat fmt) throws UnsupportedFormatException {
return VorbisStream.selectFormat(fmt);
}
};
} else if ("mp3".equals(type)) {
int bitrate = q.intValue("rate", 320);
int quality = q.intValue("quality", 2);
return new AudioStream.Factory() {
public AudioStream open(PcmFormat fmt) throws IOException {
LameStream stream = new LameStream(fmt, bitrate, quality, bufferFrames, writeaheadSecs, timeoutSecs, out, streamKeepAliveExecutor, streamKeepAliveTimeoutSecs);
streamHandles.put(id, stream);
return stream;
}
public Device.BitLayout layout(PcmFormat fmt) { return LameStream.BIT_LAYOUT; }
public PcmFormat selectFormat(AudioFormat fmt) throws UnsupportedFormatException {
return LameStream.selectFormat(fmt);
}
};
} else {
throw new IllegalArgumentException("Unrecognized stream type: "+type);
}
}
public void exec(Query q, Writer w) throws IOException {
String cmd = q.value("cmd", null);
if (cmd == null) {
w.append("{ \"err\": \"No cmd in query string\" }");
return;
}
Command c = cmds.get(cmd);
if (c == null) {
w.append("{ \"err\": \"unknown command: "+cmd+"\" }");
return;
}
c.exec(q, w);
}
public void process(Query q, WritableByteChannel out) throws IOException {
if ("fetchstream".equals(q.value("cmd", null))) {
fetchStream(q, streamFactory(q, out));
} else {
Writer w = Channels.newWriter(out, StandardCharsets.UTF_8);
try {
exec(q, w);
} finally {
w.append("\n").flush();
w.close();
}
}
}
protected abstract void fetchStream(Query q, Device.Player.Factory streamFact) throws IOException;
}
Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home