Changeset 12328 in josm for trunk/src/org/openstreetmap/josm/io
- Timestamp:
- 2017-06-07T21:41:26+02:00 (7 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/io/audio
- Files:
-
- 5 added
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/io/audio/AudioPlayer.java
r12326 r12328 2 2 package org.openstreetmap.josm.io.audio; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr;5 6 import java.awt.GraphicsEnvironment;7 4 import java.io.IOException; 8 5 import java.net.URL; 9 6 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 19 7 import org.openstreetmap.josm.Main; 20 8 import org.openstreetmap.josm.tools.JosmRuntimeException; 21 import org.openstreetmap.josm.tools.Utils;22 9 23 10 /** … … 28 15 * @since 547 29 16 */ 30 public final class AudioPlayer extends Thread {17 public final class AudioPlayer extends Thread implements AudioListener { 31 18 32 19 private static volatile AudioPlayer audioPlayer; 33 20 34 privateenum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED }35 36 privateenum Command { PLAY, PAUSE }37 38 privateenum Result { WAITING, OK, FAILED }21 enum State { INITIALIZING, NOTPLAYING, PLAYING, PAUSED, INTERRUPTED } 22 23 enum Command { PLAY, PAUSE } 24 25 enum Result { WAITING, OK, FAILED } 39 26 40 27 private State state; 28 private SoundPlayer soundPlayer; 41 29 private URL playingUrl; 42 private final double leadIn; // seconds43 private final double calibration; // ratio of purported duration of samples to true duration44 private double position; // seconds45 private double bytesPerSecond;46 private static long chunk = 4000; /* bytes */47 private double speed = 1.0;48 30 49 31 /** 50 32 * Passes information from the control thread to the playing thread 51 33 */ 52 privateclass Execute {34 class Execute { 53 35 private Command command; 54 36 private Result result; … … 85 67 } 86 68 87 pr ivatevoid possiblyInterrupt() throws InterruptedException {69 protected void possiblyInterrupt() throws InterruptedException { 88 70 if (interrupted() || result == Result.WAITING) 89 71 throw new InterruptedException(); … … 204 186 public static double position() { 205 187 AudioPlayer instance = AudioPlayer.getInstance(); 206 return instance == null ? -1 : instance. position;188 return instance == null ? -1 : instance.soundPlayer.position(); 207 189 } 208 190 … … 213 195 public static double speed() { 214 196 AudioPlayer instance = AudioPlayer.getInstance(); 215 return instance == null ? -1 : instance.s peed;197 return instance == null ? -1 : instance.soundPlayer.speed(); 216 198 } 217 199 … … 251 233 command = new Execute(); 252 234 playingUrl = null; 253 leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */); 254 calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */); 235 double leadIn = Main.pref.getDouble("audio.leadin", 1.0 /* default, seconds */); 236 double calibration = Main.pref.getDouble("audio.calibration", 1.0 /* default, ratio */); 237 try { 238 soundPlayer = new JavaFxMediaPlayer(); 239 } catch (NoClassDefFoundError | InterruptedException e) { 240 Main.debug(e); 241 Main.warn("Java FX is unavailable. Falling back to Java Sound API"); 242 soundPlayer = new JavaSoundPlayer(leadIn, calibration); 243 } 244 soundPlayer.addAudioListener(this); 255 245 start(); 256 246 while (state == State.INITIALIZING) { … … 263 253 * Not to be used as public, though Thread interface doesn't allow it to be made private 264 254 */ 265 @Override public void run() { 255 @Override 256 public void run() { 266 257 /* code running in separate thread */ 267 258 268 259 playingUrl = null; 269 AudioInputStream audioInputStream = null;270 SourceDataLine audioOutputLine = null;271 AudioFormat audioFormat;272 byte[] abData = new byte[(int) chunk];273 260 274 261 for (;;) { … … 285 272 case PLAYING: 286 273 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(); 274 if (soundPlayer.playing(command)) { 275 playingUrl = null; 276 state = State.NOTPLAYING; 299 277 } 300 // end of audio, clean up301 if (audioOutputLine != null) {302 audioOutputLine.drain();303 audioOutputLine.close();304 }305 audioOutputLine = null;306 Utils.close(audioInputStream);307 audioInputStream = null;308 playingUrl = null;309 state = State.NOTPLAYING;310 278 command.possiblyInterrupt(); 311 279 break; … … 319 287 switch (command.command()) { 320 288 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 } 289 soundPlayer.play(command, stateChange, playingUrl); 376 290 stateChange = State.PLAYING; 377 291 break; 378 292 case PAUSE: 293 soundPlayer.pause(command, stateChange, playingUrl); 379 294 stateChange = State.PAUSED; 380 295 break; … … 382 297 } 383 298 command.ok(stateChange); 384 } catch (LineUnavailableException | IOException | UnsupportedAudioFileException | 385 SecurityException | IllegalArgumentException startPlayingException) { 299 } catch (AudioException | IOException | SecurityException | IllegalArgumentException startPlayingException) { 386 300 Main.error(startPlayingException); 387 301 command.failed(startPlayingException); // sets state 388 302 } 389 } catch ( IOException e) {303 } catch (AudioException | IOException e) { 390 304 state = State.NOTPLAYING; 391 305 Main.error(e); … … 394 308 } 395 309 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) { 312 this.playingUrl = playingURL; 412 313 } 413 314 } -
trunk/src/org/openstreetmap/josm/io/audio/AudioUtil.java
r12326 r12328 2 2 package org.openstreetmap.josm.io.audio; 3 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.awt.GraphicsEnvironment; 4 7 import java.io.File; 5 8 import java.io.IOException; … … 10 13 import javax.sound.sampled.AudioSystem; 11 14 import javax.sound.sampled.UnsupportedAudioFileException; 15 import javax.swing.JOptionPane; 12 16 13 17 import org.openstreetmap.josm.Main; … … 46 50 } 47 51 } 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 * @since 12328 57 */ 58 public static void audioMalfunction(Exception ex) { 59 String msg = ex.getMessage(); 60 if (msg == null) 61 msg = tr("unspecified reason"); 62 else 63 msg = tr(msg); 64 Main.error(msg); 65 if (!GraphicsEnvironment.isHeadless()) { 66 JOptionPane.showMessageDialog(Main.parent, 67 "<html><p>" + msg + "</p></html>", 68 tr("Error playing sound"), JOptionPane.ERROR_MESSAGE); 69 } 70 } 48 71 }
Note:
See TracChangeset
for help on using the changeset viewer.