/* * Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package com.tulskiy.musique.audio.player; import java.util.logging.Logger; import com.tulskiy.musique.audio.Decoder; import com.tulskiy.musique.audio.player.io.Buffer; import com.tulskiy.musique.playlist.PlaybackOrder; import com.tulskiy.musique.playlist.Track; import com.tulskiy.musique.playlist.TrackData; import com.tulskiy.musique.system.Codecs; import com.tulskiy.musique.util.AudioMath; /** * Author: Denis Tulskiy * Date: 1/15/11 */ public class BufferingThread extends Actor implements Runnable { private final Logger logger = Logger.getLogger(getClass().getName()); private PlaybackOrder order; private final Object lock = new Object(); private long currentByte = 0; private Track currentTrack; private Track nextTrack; private Decoder decoder; private long cueTotalBytes; private boolean active; private Buffer buffer; private PlayingThread playingThread; private boolean stopAfterCurrent = false; public BufferingThread(Buffer buffer, PlayingThread playingThread) { this.buffer = buffer; this.playingThread = playingThread; } @Override public void process(Message message) { Object[] params = message.getParams(); switch (message) { case OPEN: if (params.length > 0 && params[0] instanceof Track) { Track track = (Track) params[0]; pause(true); open(track, true); } break; case SEEK: if (params.length > 0 && params[0] instanceof Long) { Long sample = (Long) params[0]; seek(sample); } break; case STOP: stop(true); break; } } @SuppressWarnings({"InfiniteLoopStatement"}) @Override public void run() { byte[] buf = new byte[65536]; int len; while (true) { synchronized (lock) { try { while (!active) { lock.wait(); } if (decoder == null) { stop(false); continue; } while (active) { if (nextTrack != null) { if (stopAfterCurrent) { stop(false); stopAfterCurrent = false; continue; } open(nextTrack, false); nextTrack = null; continue; } len = decoder.decode(buf); if (len == -1) { nextTrack = null; if (order != null) nextTrack = order.next(currentTrack); if (nextTrack == null) { stop(false); } continue; } if (currentTrack.getTrackData().isCue()) { if (cueTotalBytes <= currentByte + len) { Track s = null; if (order != null) s = order.next(currentTrack); len = (int) (cueTotalBytes - currentByte); if (s != null) { nextTrack = s; } else { stop(false); } } } currentByte += len; buffer.write(buf, 0, len); } } catch (Exception e) { e.printStackTrace(); } } } } public void stop(boolean flush) { logger.fine("Stop buffering"); nextTrack = null; pause(flush); buffer.addNextTrack(null, null, -1, false); if (decoder != null) { decoder.close(); } decoder = null; } private void pause(boolean flush) { active = false; if (flush) buffer.flush(); synchronized (lock) { } if (flush) buffer.flush(); } private void start() { active = true; synchronized (lock) { lock.notifyAll(); } } public synchronized void open(Track track, boolean forced) { if (decoder != null) { decoder.close(); } if (track != null) { TrackData trackData = track.getTrackData(); logger.fine("Opening track " + trackData.getLocation()); if (trackData.isFile() && !trackData.getFile().exists()) { //try to get the next one track = order.next(track); if (track == null || ( trackData.isFile() && !trackData.getFile().exists())) { stop(false); return; } } decoder = Codecs.getDecoder(track); currentTrack = track; currentByte = 0; if (decoder == null || !decoder.open(track)) { currentTrack = null; stop(false); return; } buffer.addNextTrack(currentTrack, decoder.getAudioFormat(), -1, forced); if (trackData.getStartPosition() > 0) decoder.seekSample(trackData.getStartPosition()); if (trackData.getSubsongIndex() > 0) { cueTotalBytes = AudioMath.samplesToBytes(trackData.getTotalSamples(), decoder.getAudioFormat().getFrameSize()); } else { cueTotalBytes = 0; } start(); logger.fine("Finished opening track"); if (forced) playingThread.send(Message.FLUSH); playingThread.send(Message.PLAY); } } public void seek(long sample) { boolean oldState = active; pause(true); if (decoder != null) { decoder.seekSample(currentTrack.getTrackData().getStartPosition() + sample); currentByte = AudioMath.samplesToBytes(sample, decoder.getAudioFormat().getFrameSize()); buffer.addNextTrack(currentTrack, decoder.getAudioFormat(), sample, true); if (oldState) { start(); } } } public PlaybackOrder getOrder() { return order; } public void setOrder(PlaybackOrder order) { this.order = order; } public boolean isActive() { return active; } public void setStopAfterCurrent(boolean stopAfterCurrent) { this.stopAfterCurrent = stopAfterCurrent; } }