Ticket #2089: 2089_alpha1.patch

File 2089_alpha1.patch, 29.8 KB (added by Don-vip, 4 months ago)
  • src/org/openstreetmap/josm/actions/audio/AudioBackAction.java

     
    1313import org.openstreetmap.josm.actions.JosmAction;
    1414import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    1515import org.openstreetmap.josm.io.audio.AudioPlayer;
     16import org.openstreetmap.josm.io.audio.AudioUtil;
    1617import org.openstreetmap.josm.tools.Shortcut;
    1718
    1819/**
     
    3940            else
    4041                MarkerLayer.playAudio();
    4142        } catch (IOException | InterruptedException ex) {
    42             AudioPlayer.audioMalfunction(ex);
     43            AudioUtil.audioMalfunction(ex);
    4344        }
    4445    }
    4546}
  • src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java

     
    77import org.openstreetmap.josm.Main;
    88import org.openstreetmap.josm.actions.JosmAction;
    99import org.openstreetmap.josm.io.audio.AudioPlayer;
     10import org.openstreetmap.josm.io.audio.AudioUtil;
    1011import org.openstreetmap.josm.tools.Shortcut;
    1112
    1213/**
     
    4243            if (AudioPlayer.playing() || AudioPlayer.paused())
    4344                AudioPlayer.play(AudioPlayer.url(), AudioPlayer.position(), speed * multiplier);
    4445        } catch (IOException | InterruptedException ex) {
    45             AudioPlayer.audioMalfunction(ex);
     46            AudioUtil.audioMalfunction(ex);
    4647        }
    4748    }
    4849}
  • src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java

     
    1212import org.openstreetmap.josm.actions.JosmAction;
    1313import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    1414import org.openstreetmap.josm.io.audio.AudioPlayer;
     15import org.openstreetmap.josm.io.audio.AudioUtil;
    1516import org.openstreetmap.josm.tools.Shortcut;
    1617
    1718/**
     
    3738            else
    3839                MarkerLayer.playAudio();
    3940        } catch (IOException | InterruptedException ex) {
    40             AudioPlayer.audioMalfunction(ex);
     41            AudioUtil.audioMalfunction(ex);
    4142        }
    4243    }
    4344}
  • src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java

     
    1313import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
    1414import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    1515import org.openstreetmap.josm.io.audio.AudioPlayer;
     16import org.openstreetmap.josm.io.audio.AudioUtil;
    1617import org.openstreetmap.josm.tools.Shortcut;
    1718import org.openstreetmap.josm.tools.Utils;
    1819
     
    5455                }
    5556            }
    5657        } catch (IOException | InterruptedException ex) {
    57             AudioPlayer.audioMalfunction(ex);
     58            AudioUtil.audioMalfunction(ex);
    5859        }
    5960    }
    6061}
  • src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java

     
    1212import org.openstreetmap.josm.data.gpx.GpxLink;
    1313import org.openstreetmap.josm.data.gpx.WayPoint;
    1414import org.openstreetmap.josm.io.audio.AudioPlayer;
     15import org.openstreetmap.josm.io.audio.AudioUtil;
    1516import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider;
    1617
    1718/**
     
    5960            AudioPlayer.play(audioUrl, offset + syncOffset + after);
    6061            recentlyPlayedMarker = this;
    6162        } catch (IOException | InterruptedException e) {
    62             AudioPlayer.audioMalfunction(e);
     63            AudioUtil.audioMalfunction(e);
    6364        }
    6465    }
    6566
  • src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java

     
    2424import org.openstreetmap.josm.gui.MapView;
    2525import org.openstreetmap.josm.gui.layer.GpxLayer;
    2626import org.openstreetmap.josm.io.audio.AudioPlayer;
     27import org.openstreetmap.josm.io.audio.AudioUtil;
    2728
    2829/**
    2930 * Singleton marker class to track position of audio.
     
    99100            try {
    100101                AudioPlayer.pause();
    101102            } catch (IOException | InterruptedException ex) {
    102                 AudioPlayer.audioMalfunction(ex);
     103                AudioUtil.audioMalfunction(ex);
    103104            }
    104105        }
    105106    }
     
    113114            try {
    114115                AudioPlayer.pause();
    115116            } catch (IOException | InterruptedException ex) {
    116                 AudioPlayer.audioMalfunction(ex);
     117                AudioUtil.audioMalfunction(ex);
    117118            }
    118119        }
    119120        if (reset) {
  • src/org/openstreetmap/josm/io/audio/AudioException.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.audio;
     3
     4/**
     5 * Generic audio exception. Mainly used to wrap backend exceptions varying between implementations.
     6 * @since xxx
     7 */
     8public class AudioException extends Exception {
     9
     10    /**
     11     * Constructs a new {@code AudioException}.
     12     * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
     13     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
     14     */
     15    public AudioException(String message, Throwable cause) {
     16        super(message, cause);
     17    }
     18
     19    /**
     20     * Constructs a new {@code AudioException}.
     21     * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
     22     */
     23    public AudioException(String message) {
     24        super(message);
     25    }
     26
     27    /**
     28     * Constructs a new {@code AudioException}.
     29     * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
     30     */
     31    public AudioException(Throwable cause) {
     32        super(cause);
     33    }
     34}
  • src/org/openstreetmap/josm/io/audio/AudioListener.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.audio;
     3
     4import java.net.URL;
     5
     6/**
     7 * Listener receiving audio playing events.
     8 * @since xxx
     9 */
     10interface AudioListener {
     11
     12    /**
     13     * Called when a new URL is being played.
     14     * @param playingURL new URL being played
     15     * @param position position, in seconds
     16     * @param speed speed factor
     17     */
     18    void playing(URL playingURL, double position, double speed);
     19}
  • src/org/openstreetmap/josm/io/audio/AudioPlayer.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.io.audio;
    33
    4 import static org.openstreetmap.josm.tools.I18n.tr;
    5 
    6 import java.awt.GraphicsEnvironment;
    74import java.io.IOException;
    85import java.net.URL;
    96
    10 import javax.sound.sampled.AudioFormat;
    11 import javax.sound.sampled.AudioInputStream;
    12 import javax.sound.sampled.AudioSystem;
    13 import javax.sound.sampled.DataLine;
    14 import javax.sound.sampled.LineUnavailableException;
    15 import javax.sound.sampled.SourceDataLine;
    16 import javax.sound.sampled.UnsupportedAudioFileException;
    17 import javax.swing.JOptionPane;
    18 
    197import org.openstreetmap.josm.Main;
    208import org.openstreetmap.josm.tools.JosmRuntimeException;
    21 import org.openstreetmap.josm.tools.Utils;
    229
    2310/**
    2411 * Creates and controls a separate audio player thread.
     
    2714 * @since 12326 (move to new package)
    2815 * @since 547
    2916 */
    30 public final class AudioPlayer extends Thread {
     17public final class AudioPlayer extends Thread implements AudioListener {
    3118
    3219    private static volatile AudioPlayer audioPlayer;
    3320
    34     private enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
     21    enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }
    3522
    36     private enum Command { PLAY, PAUSE }
     23    enum Command { PLAY, PAUSE }
    3724
    38     private enum Result { WAITING, OK, FAILED }
     25    enum Result { WAITING, OK, FAILED }
    3926
    4027    private State state;
     28    private SoundPlayer soundPlayer;
    4129    private URL playingUrl;
    42     private final double leadIn; // seconds
    43     private final double calibration; // ratio of purported duration of samples to true duration
    4430    private double position; // seconds
    45     private double bytesPerSecond;
    46     private static long chunk = 4000; /* bytes */
    4731    private double speed = 1.0;
    4832
    4933    /**
    5034     * Passes information from the control thread to the playing thread
    5135     */
    52     private class Execute {
     36    class Execute {
    5337        private Command command;
    5438        private Result result;
    5539        private Exception exception;
     
    8468                throw new IOException(exception);
    8569        }
    8670
    87         private void possiblyInterrupt() throws InterruptedException {
     71        protected void possiblyInterrupt() throws InterruptedException {
    8872            if (interrupted() || result == Result.WAITING)
    8973                throw new InterruptedException();
    9074        }
     
    250234        state = State.INITIALIZING;
    251235        command = new Execute();
    252236        playingUrl = null;
    253         leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */);
    254         calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
     237        double leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */);
     238        double calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */);
     239        try {
     240            soundPlayer = new JavaFxMediaPlayer();
     241        } catch (NoClassDefFoundError e) {
     242            Main.debug(e);
     243            Main.warn("Java FX is unavailable. Falling back to Java Sound API");
     244            soundPlayer = new JavaSoundPlayer(leadIn, calibration);
     245        }
     246        soundPlayer.addAudioListener(this);
    255247        start();
    256248        while (state == State.INITIALIZING) {
    257249            yield();
     
    262254     * Starts the thread to actually play the audio, per Thread interface
    263255     * Not to be used as public, though Thread interface doesn't allow it to be made private
    264256     */
    265     @Override public void run() {
     257    @Override
     258    public void run() {
    266259        /* code running in separate thread */
    267260
    268261        playingUrl = null;
    269         AudioInputStream audioInputStream = null;
    270         SourceDataLine audioOutputLine = null;
    271         AudioFormat audioFormat;
    272         byte[] abData = new byte[(int) chunk];
    273262
    274263        for (;;) {
    275264            try {
     
    284273                        break;
    285274                    case PLAYING:
    286275                        command.possiblyInterrupt();
    287                         for (;;) {
    288                             int nBytesRead = 0;
    289                             if (audioInputStream != null) {
    290                                 nBytesRead = audioInputStream.read(abData, 0, abData.length);
    291                                 position += nBytesRead / bytesPerSecond;
    292                             }
    293                             command.possiblyInterrupt();
    294                             if (nBytesRead < 0 || audioInputStream == null || audioOutputLine == null) {
    295                                 break;
    296                             }
    297                             audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
    298                             command.possiblyInterrupt();
    299                         }
    300                         // end of audio, clean up
    301                         if (audioOutputLine != null) {
    302                             audioOutputLine.drain();
    303                             audioOutputLine.close();
    304                         }
    305                         audioOutputLine = null;
    306                         Utils.close(audioInputStream);
    307                         audioInputStream = null;
     276                        soundPlayer.play(command);
    308277                        playingUrl = null;
    309278                        state = State.NOTPLAYING;
    310279                        command.possiblyInterrupt();
     
    318287                try {
    319288                    switch (command.command()) {
    320289                        case PLAY:
    321                             double offset = command.offset();
    322                             speed = command.speed();
    323                             if (playingUrl != command.url() ||
    324                                     stateChange != State.PAUSED ||
    325                                     offset != 0) {
    326                                 if (audioInputStream != null) {
    327                                     Utils.close(audioInputStream);
    328                                 }
    329                                 playingUrl = command.url();
    330                                 audioInputStream = AudioSystem.getAudioInputStream(playingUrl);
    331                                 audioFormat = audioInputStream.getFormat();
    332                                 long nBytesRead;
    333                                 position = 0.0;
    334                                 offset -= leadIn;
    335                                 double calibratedOffset = offset * calibration;
    336                                 bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
    337                                 * audioFormat.getFrameSize() /* bytes per frame */;
    338                                 if (speed * bytesPerSecond > 256_000.0) {
    339                                     speed = 256_000 / bytesPerSecond;
    340                                 }
    341                                 if (calibratedOffset > 0.0) {
    342                                     long bytesToSkip = (long) (calibratedOffset /* seconds (double) */ * bytesPerSecond);
    343                                     // skip doesn't seem to want to skip big chunks, so reduce it to smaller ones
    344                                     while (bytesToSkip > chunk) {
    345                                         nBytesRead = audioInputStream.skip(chunk);
    346                                         if (nBytesRead <= 0)
    347                                             throw new IOException(tr("This is after the end of the recording"));
    348                                         bytesToSkip -= nBytesRead;
    349                                     }
    350                                     while (bytesToSkip > 0) {
    351                                         long skippedBytes = audioInputStream.skip(bytesToSkip);
    352                                         bytesToSkip -= skippedBytes;
    353                                         if (skippedBytes == 0) {
    354                                             // Avoid inifinite loop
    355                                             Main.warn("Unable to skip bytes from audio input stream");
    356                                             bytesToSkip = 0;
    357                                         }
    358                                     }
    359                                     position = offset;
    360                                 }
    361                                 if (audioOutputLine != null) {
    362                                     audioOutputLine.close();
    363                                 }
    364                                 audioFormat = new AudioFormat(audioFormat.getEncoding(),
    365                                         audioFormat.getSampleRate() * (float) (speed * calibration),
    366                                         audioFormat.getSampleSizeInBits(),
    367                                         audioFormat.getChannels(),
    368                                         audioFormat.getFrameSize(),
    369                                         audioFormat.getFrameRate() * (float) (speed * calibration),
    370                                         audioFormat.isBigEndian());
    371                                 DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
    372                                 audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
    373                                 audioOutputLine.open(audioFormat);
    374                                 audioOutputLine.start();
    375                             }
     290                            soundPlayer.playInterrupted(command, stateChange, playingUrl);
    376291                            stateChange = State.PLAYING;
    377292                            break;
    378293                        case PAUSE:
     
    381296                        default: // Do nothing
    382297                    }
    383298                    command.ok(stateChange);
    384                 } catch (LineUnavailableException | IOException | UnsupportedAudioFileException |
    385                         SecurityException | IllegalArgumentException startPlayingException) {
     299                } catch (AudioException | IOException | SecurityException | IllegalArgumentException startPlayingException) {
    386300                    Main.error(startPlayingException);
    387301                    command.failed(startPlayingException); // sets state
    388302                }
    389             } catch (IOException e) {
     303            } catch (AudioException | IOException e) {
    390304                state = State.NOTPLAYING;
    391305                Main.error(e);
    392306            }
     
    393307        }
    394308    }
    395309
    396     /**
    397      * Shows a popup audio error message for the given exception.
    398      * @param ex The exception used as error reason. Cannot be {@code null}.
    399      */
    400     public static void audioMalfunction(Exception ex) {
    401         String msg = ex.getMessage();
    402         if (msg == null)
    403             msg = tr("unspecified reason");
    404         else
    405             msg = tr(msg);
    406         Main.error(msg);
    407         if (!GraphicsEnvironment.isHeadless()) {
    408             JOptionPane.showMessageDialog(Main.parent,
    409                     "<html><p>" + msg + "</p></html>",
    410                     tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
    411         }
     310    @Override
     311    public void playing(URL playingURL, double position, double speed) {
     312        this.playingUrl = playingURL;
     313        this.position = position;
     314        this.speed = speed;
    412315    }
    413316}
  • src/org/openstreetmap/josm/io/audio/AudioUtil.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.io.audio;
    33
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.GraphicsEnvironment;
    47import java.io.File;
    58import java.io.IOException;
    69import java.net.URL;
     
    912import javax.sound.sampled.AudioInputStream;
    1013import javax.sound.sampled.AudioSystem;
    1114import javax.sound.sampled.UnsupportedAudioFileException;
     15import javax.swing.JOptionPane;
    1216
    1317import org.openstreetmap.josm.Main;
    1418
     
    4549            return 0.0;
    4650        }
    4751    }
     52
     53    /**
     54     * Shows a popup audio error message for the given exception.
     55     * @param ex The exception used as error reason. Cannot be {@code null}.
     56     */
     57    public static void audioMalfunction(Exception ex) {
     58        String msg = ex.getMessage();
     59        if (msg == null)
     60            msg = tr("unspecified reason");
     61        else
     62            msg = tr(msg);
     63        Main.error(msg);
     64        if (!GraphicsEnvironment.isHeadless()) {
     65            JOptionPane.showMessageDialog(Main.parent,
     66                    "<html><p>" + msg + "</p></html>",
     67                    tr("Error playing sound"), JOptionPane.ERROR_MESSAGE);
     68        }
     69    }
    4870}
  • src/org/openstreetmap/josm/io/audio/JavaFxMediaPlayer.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.audio;
     3
     4import java.io.IOException;
     5import java.net.URL;
     6
     7import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
     8import org.openstreetmap.josm.io.audio.AudioPlayer.State;
     9import org.openstreetmap.josm.tools.ListenerList;
     10
     11/**
     12 * Default sound player based on the Java FX Media API.
     13 * Used on platforms where Java FX is available. It supports the following audio codecs:<ul>
     14 * <li>MP3</li>
     15 * <li>AIFF containing uncompressed PCM</li>
     16 * <li>WAV containing uncompressed PCM</li>
     17 * <li>MPEG-4 multimedia container with Advanced Audio Coding (AAC) audio</li>
     18 * </ul>
     19 * @since xxx
     20 */
     21class JavaFxMediaPlayer implements SoundPlayer {
     22
     23    private final ListenerList<AudioListener> listeners = ListenerList.create();
     24
     25    JavaFxMediaPlayer() {
     26        throw new NoClassDefFoundError("test legacy first");
     27    }
     28
     29    @Override
     30    public void play(Execute command) throws AudioException, IOException {
     31        // TODO Auto-generated method stub
     32    }
     33
     34    @Override
     35    public void playInterrupted(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
     36        // TODO Auto-generated method stub
     37    }
     38
     39    @Override
     40    public void addAudioListener(AudioListener listener) {
     41        listeners.addWeakListener(listener);
     42    }
     43}
  • src/org/openstreetmap/josm/io/audio/JavaSoundPlayer.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.audio;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.IOException;
     7import java.net.URL;
     8
     9import javax.sound.sampled.AudioFormat;
     10import javax.sound.sampled.AudioInputStream;
     11import javax.sound.sampled.AudioSystem;
     12import javax.sound.sampled.DataLine;
     13import javax.sound.sampled.LineUnavailableException;
     14import javax.sound.sampled.SourceDataLine;
     15import javax.sound.sampled.UnsupportedAudioFileException;
     16
     17import org.openstreetmap.josm.Main;
     18import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
     19import org.openstreetmap.josm.io.audio.AudioPlayer.State;
     20import org.openstreetmap.josm.tools.ListenerList;
     21import org.openstreetmap.josm.tools.Utils;
     22
     23/**
     24 * Legacy sound player based on the Java Sound API.
     25 * Used on platforms where Java FX is not yet available. It supports only WAV files.
     26 * @since xxx
     27 */
     28class JavaSoundPlayer implements SoundPlayer {
     29
     30    private static int chunk = 4000; /* bytes */
     31
     32    private AudioInputStream audioInputStream;
     33    private SourceDataLine audioOutputLine;
     34    private AudioFormat audioFormat;
     35
     36    private final double leadIn; // seconds
     37    private final double calibration; // ratio of purported duration of samples to true duration
     38
     39    private double bytesPerSecond;
     40    private byte[] abData = new byte[chunk];
     41
     42    private double position; // seconds
     43    private double speed = 1.0;
     44
     45    private final ListenerList<AudioListener> listeners = ListenerList.create();
     46
     47    JavaSoundPlayer(double leadIn, double calibration) {
     48        this.leadIn = leadIn;
     49        this.calibration = calibration;
     50    }
     51
     52    @Override
     53    public void play(Execute command) throws AudioException, IOException, InterruptedException {
     54        for (;;) {
     55            int nBytesRead = 0;
     56            if (audioInputStream != null) {
     57                nBytesRead = audioInputStream.read(abData, 0, abData.length);
     58                position += nBytesRead / bytesPerSecond;
     59                listeners.fireEvent(l -> l.playing(command.url(), position, speed));
     60            }
     61            command.possiblyInterrupt();
     62            if (nBytesRead < 0 || audioInputStream == null || audioOutputLine == null) {
     63                break;
     64            }
     65            audioOutputLine.write(abData, 0, nBytesRead); // => int nBytesWritten
     66            command.possiblyInterrupt();
     67        }
     68        // end of audio, clean up
     69        if (audioOutputLine != null) {
     70            audioOutputLine.drain();
     71            audioOutputLine.close();
     72        }
     73        audioOutputLine = null;
     74        Utils.close(audioInputStream);
     75        audioInputStream = null;
     76    }
     77
     78    @Override
     79    public void playInterrupted(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException {
     80        final URL url = command.url();
     81        double offset = command.offset();
     82        speed = command.speed();
     83        if (playingUrl != url ||
     84                stateChange != State.PAUSED ||
     85                offset != 0) {
     86            if (audioInputStream != null) {
     87                Utils.close(audioInputStream);
     88            }
     89            listeners.fireEvent(l -> l.playing(url, position, speed));
     90            try {
     91                audioInputStream = AudioSystem.getAudioInputStream(url);
     92            } catch (UnsupportedAudioFileException e) {
     93                throw new AudioException(e);
     94            }
     95            audioFormat = audioInputStream.getFormat();
     96            long nBytesRead;
     97            position = 0.0;
     98            listeners.fireEvent(l -> l.playing(url, position, speed));
     99            offset -= leadIn;
     100            double calibratedOffset = offset * calibration;
     101            bytesPerSecond = audioFormat.getFrameRate() /* frames per second */
     102            * audioFormat.getFrameSize() /* bytes per frame */;
     103            if (speed * bytesPerSecond > 256_000.0) {
     104                speed = 256_000 / bytesPerSecond;
     105                listeners.fireEvent(l -> l.playing(url, position, speed));
     106            }
     107            if (calibratedOffset > 0.0) {
     108                long bytesToSkip = (long) (calibratedOffset /* seconds (double) */ * bytesPerSecond);
     109                // skip doesn't seem to want to skip big chunks, so reduce it to smaller ones
     110                while (bytesToSkip > chunk) {
     111                    nBytesRead = audioInputStream.skip(chunk);
     112                    if (nBytesRead <= 0)
     113                        throw new IOException(tr("This is after the end of the recording"));
     114                    bytesToSkip -= nBytesRead;
     115                }
     116                while (bytesToSkip > 0) {
     117                    long skippedBytes = audioInputStream.skip(bytesToSkip);
     118                    bytesToSkip -= skippedBytes;
     119                    if (skippedBytes == 0) {
     120                        // Avoid inifinite loop
     121                        Main.warn("Unable to skip bytes from audio input stream");
     122                        bytesToSkip = 0;
     123                    }
     124                }
     125                position = offset;
     126                listeners.fireEvent(l -> l.playing(url, position, speed));
     127            }
     128            if (audioOutputLine != null) {
     129                audioOutputLine.close();
     130            }
     131            audioFormat = new AudioFormat(audioFormat.getEncoding(),
     132                    audioFormat.getSampleRate() * (float) (speed * calibration),
     133                    audioFormat.getSampleSizeInBits(),
     134                    audioFormat.getChannels(),
     135                    audioFormat.getFrameSize(),
     136                    audioFormat.getFrameRate() * (float) (speed * calibration),
     137                    audioFormat.isBigEndian());
     138            try {
     139                DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
     140                audioOutputLine = (SourceDataLine) AudioSystem.getLine(info);
     141                audioOutputLine.open(audioFormat);
     142                audioOutputLine.start();
     143            } catch (LineUnavailableException e) {
     144                throw new AudioException(e);
     145            }
     146        }
     147    }
     148
     149    @Override
     150    public void addAudioListener(AudioListener listener) {
     151        listeners.addWeakListener(listener);
     152    }
     153}
  • src/org/openstreetmap/josm/io/audio/SoundPlayer.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.audio;
     3
     4import java.io.IOException;
     5import java.net.URL;
     6
     7import org.openstreetmap.josm.io.audio.AudioPlayer.Execute;
     8import org.openstreetmap.josm.io.audio.AudioPlayer.State;
     9
     10/**
     11 * Sound player interface. Implementations can be backed up by Java Sound API or Java FX Media API.
     12 * @since xxx
     13 */
     14interface SoundPlayer {
     15
     16    void play(Execute command) throws AudioException, IOException, InterruptedException;
     17
     18    void playInterrupted(Execute command, State stateChange, URL playingUrl) throws AudioException, IOException;
     19
     20    void addAudioListener(AudioListener listener);
     21}