// 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.split;

import java.io.IOException;
import net.avadeaux.klipspringer.*;
import net.avadeaux.klipspringer.codec.*;

public class PlayProcess implements TrackKeyInterface.Control, PlayMonitor, Runnable {
    public static class Info implements PlayMonitor.Info {
        private String trackName;
        private int trackIndex;
        private PcmFormat format;
        private String quality;
        private double time;
        private long timestamp = 0;

        public String trackName() { return trackName; }
        public int trackIndex() { return trackIndex; }
        public PcmFormat format() { return format; }
        public double time() { return time; }
        public String quality() { return quality; }
    }

    private final Track track;
    private final TrackSplitter.Segment[] segs;
    private final TrackSplitter.Hop[] hops;
    private final long endFrame;                                // upper bound for playing
    private final double flushSilenceSecs;

    private SplitPlayer play = null;
    private long seekFrame;
    private long changeNo = 1;
    private boolean fading;

    public void run() {
        while (true) {
            SplitPlayer p;
            long f;
            synchronized (this) {
                if (play == null) { break; }
                p = play;
                f = seekFrame;
            }
            try {
                track.decode(f, p);
                p.drain();
            } catch (Exception ex) {
                if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
            } finally {
                synchronized (this) {
                    if (p == play) {
                        play = null;
                        changeNo++;
                        notifyAll();
                    } else {
                        p = null;
                    }
                }
                try {
                    if (p != null) { p.close(); }
                } catch (Exception ex) {
                    System.err.println(ex);
                    if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
                }
            }
        }
    }

    public PlayProcess(Track track,
                       TrackSplitter.Segment[] segs,
                       TrackSplitter.Hop[] hops,
                       long endFrame,
                       boolean fading,
                       double flushSilenceSecs)
    {
        this.track = track;
        this.segs = segs;
        this.hops = hops;
        this.endFrame = endFrame;
        this.fading = fading;
        this.flushSilenceSecs = flushSilenceSecs;
    }

    public synchronized void seek(int trackIx, double time, Device.Player.Factory outFact, Object... extra) throws IOException {
        Query.Onoff setFading = extra.length > 0 ? (Query.Onoff) extra[0] : Query.Onoff.DETECT;
        if (setFading != Query.Onoff.DETECT) { fading = setFading == Query.Onoff.ON; }
        seek(trackIx < 0 ? 0 : segs[trackIx].lo(0) + (long) (time*track.format().rate()), outFact);
    }

    public synchronized void seek(long fromFrame, Device.Player.Factory outFact) throws IOException {
        if (play != null) { play.close(); }
        seekFrame = fromFrame;
        PcmFormat inFmt = track.format();
        PcmFormat outFmt = outFact.selectFormat(inFmt);
        Device.Player outPlay = outFact.open(outFmt);
        Device.BitLayout lout = outFact.layout(outFmt);
        play = SplitPlayer.create(segs, hops, inFmt, outPlay, lout, seekFrame, endFrame, fading, flushSilenceSecs);
        changeNo++;
        notifyAll();
    }

    public Info getInfoObject() { return new Info(); }

    public synchronized void getStatus(PlayMonitor.Info istatus, long timeoutMillis) {
        Info status = (Info) istatus;
        for (long now = System.currentTimeMillis(), end = now+timeoutMillis;
             status.timestamp == changeNo && now < end;
             now = System.currentTimeMillis())
            { try { wait(end-now); } catch (InterruptedException e) { } }
        status.timestamp = changeNo;
        if (play != null) {
            long frame = -1;
            try {
                frame = play.currentFrame();
            } catch (IOException ex) {
                if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
            }
            if (frame > -1) {
                int i = TrackSplitter.segmentIndex(segs, frame);
                status.trackName = "Track "+(i+1);
                status.trackIndex = i;
                status.format = track.format();
                status.quality = track.quality();
                status.time = (double) (frame-segs[i].lo(0))/status.format.rate();
                return;
            }
        }
        status.trackName = null;
        status.format = null;
        status.quality = null;
        status.time = Double.NaN;
    }

    public synchronized void quit() {
        try {
            if (play != null) {
                SplitPlayer p = play;
                play = null;
                p.close();
                changeNo++;
            }
        } catch (IOException ex) {
            if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
        } finally {
            notifyAll();
        }
    }

    public synchronized int playingTrackIndex() {
        if (play == null) { return -1; }
        long frame;
        try {
            frame = play.currentFrame();
        } catch (IOException ex) {
            if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
            return -1;
        }
        return TrackSplitter.segmentIndex(segs, frame);
    }

    public synchronized double playingTime() {
        if (play == null) { return Double.NaN; }
        long frame;
        try {
            frame = play.currentFrame();
        } catch (IOException ex) {
            if (System.getProperty("klipspringer.debug") != null) { ex.printStackTrace(); }
            return Double.NaN;
        }
        return (double) (frame-segs[0].lo(0)) / play.format().rate();
    }

    public synchronized Query.Onoff fading() { return fading ? Query.Onoff.ON : Query.Onoff.OFF; }
}

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