Changeset 12328 in josm for trunk/src/org
- Timestamp:
- 2017-06-07T21:41:26+02:00 (7 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 5 added
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/audio/AudioBackAction.java
r12326 r12328 14 14 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 15 15 import org.openstreetmap.josm.io.audio.AudioPlayer; 16 import org.openstreetmap.josm.io.audio.AudioUtil; 16 17 import org.openstreetmap.josm.tools.Shortcut; 17 18 … … 40 41 MarkerLayer.playAudio(); 41 42 } catch (IOException | InterruptedException ex) { 42 Audio Player.audioMalfunction(ex);43 AudioUtil.audioMalfunction(ex); 43 44 } 44 45 } -
trunk/src/org/openstreetmap/josm/actions/audio/AudioFastSlowAction.java
r12326 r12328 8 8 import org.openstreetmap.josm.actions.JosmAction; 9 9 import org.openstreetmap.josm.io.audio.AudioPlayer; 10 import org.openstreetmap.josm.io.audio.AudioUtil; 10 11 import org.openstreetmap.josm.tools.Shortcut; 11 12 … … 43 44 AudioPlayer.play(AudioPlayer.url(), AudioPlayer.position(), speed * multiplier); 44 45 } catch (IOException | InterruptedException ex) { 45 Audio Player.audioMalfunction(ex);46 AudioUtil.audioMalfunction(ex); 46 47 } 47 48 } -
trunk/src/org/openstreetmap/josm/actions/audio/AudioFwdAction.java
r12326 r12328 13 13 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 14 14 import org.openstreetmap.josm.io.audio.AudioPlayer; 15 import org.openstreetmap.josm.io.audio.AudioUtil; 15 16 import org.openstreetmap.josm.tools.Shortcut; 16 17 … … 38 39 MarkerLayer.playAudio(); 39 40 } catch (IOException | InterruptedException ex) { 40 Audio Player.audioMalfunction(ex);41 AudioUtil.audioMalfunction(ex); 41 42 } 42 43 } -
trunk/src/org/openstreetmap/josm/actions/audio/AudioPlayPauseAction.java
r12326 r12328 14 14 import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 15 15 import org.openstreetmap.josm.io.audio.AudioPlayer; 16 import org.openstreetmap.josm.io.audio.AudioUtil; 16 17 import org.openstreetmap.josm.tools.Shortcut; 17 18 import org.openstreetmap.josm.tools.Utils; … … 55 56 } 56 57 } catch (IOException | InterruptedException ex) { 57 Audio Player.audioMalfunction(ex);58 AudioUtil.audioMalfunction(ex); 58 59 } 59 60 } -
trunk/src/org/openstreetmap/josm/gui/layer/gpx/ImportAudioAction.java
r12326 r12328 41 41 private final transient GpxLayer layer; 42 42 43 static final class AudioFileFilter extends FileFilter { 43 /** 44 * Audio file filter. 45 * @since 12328 46 */ 47 public static final class AudioFileFilter extends FileFilter { 44 48 @Override 45 49 public boolean accept(File f) { 46 return f.isDirectory() || Utils.hasExtension(f, "wav" );50 return f.isDirectory() || Utils.hasExtension(f, "wav", "mp3", "aac", "aif", "aiff"); 47 51 } 48 52 49 53 @Override 50 54 public String getDescription() { 51 return tr(" Wave Audio files (*.wav)");55 return tr("Audio files (*.wav, *.mp3, *.aac, *.aif, *.aiff)"); 52 56 } 53 57 } … … 119 123 * which the given audio file is associated with. Markers are derived from the following (a) 120 124 * explict waypoints in the GPX layer, or (b) named trackpoints in the GPX layer, or (d) 121 * timestamp on the wavfile (e) (in future) voice recognised markers in the sound recording (f)125 * timestamp on the audio file (e) (in future) voice recognised markers in the sound recording (f) 122 126 * a single marker at the beginning of the track 123 * @param wavFile the file to be associated with the markers in the new marker layer127 * @param audioFile the file to be associated with the markers in the new marker layer 124 128 * @param ml marker layer 125 129 * @param firstStartTime first start time in milliseconds, used for (d) 126 130 * @param markers keeps track of warning messages to avoid repeated warnings 127 131 */ 128 private void importAudio(File wavFile, MarkerLayer ml, double firstStartTime, Markers markers) {129 URL url = Utils.fileToURL( wavFile);132 private void importAudio(File audioFile, MarkerLayer ml, double firstStartTime, Markers markers) { 133 URL url = Utils.fileToURL(audioFile); 130 134 boolean hasTracks = layer.data.tracks != null && !layer.data.tracks.isEmpty(); 131 135 boolean hasWaypoints = layer.data.waypoints != null && !layer.data.waypoints.isEmpty(); … … 212 216 // (d) use timestamp of file as location on track 213 217 if (hasTracks && Main.pref.getBoolean("marker.audiofromwavtimestamps", false)) { 214 double lastModified = wavFile.lastModified() / 1000.0; // lastModified is in 215 // milliseconds 216 double duration = AudioUtil.getCalibratedDuration(wavFile); 218 double lastModified = audioFile.lastModified() / 1000.0; // lastModified is in milliseconds 219 double duration = AudioUtil.getCalibratedDuration(audioFile); 217 220 double startTime = lastModified - duration; 218 221 startTime = firstStartTime + (startTime - firstStartTime) … … 242 245 (startTime - w1.time) / (w2.time - w1.time))); 243 246 wayPointFromTimeStamp.time = startTime; 244 String name = wavFile.getName();247 String name = audioFile.getName(); 245 248 int dot = name.lastIndexOf('.'); 246 249 if (dot > 0) { -
trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/AudioMarker.java
r12326 r12328 13 13 import org.openstreetmap.josm.data.gpx.WayPoint; 14 14 import org.openstreetmap.josm.io.audio.AudioPlayer; 15 import org.openstreetmap.josm.io.audio.AudioUtil; 15 16 import org.openstreetmap.josm.tools.template_engine.TemplateEngineDataProvider; 16 17 … … 60 61 recentlyPlayedMarker = this; 61 62 } catch (IOException | InterruptedException e) { 62 Audio Player.audioMalfunction(e);63 AudioUtil.audioMalfunction(e); 63 64 } 64 65 } -
trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/DefaultMarkerProducers.java
r11892 r12328 44 44 if (url == null) { 45 45 return Collections.singleton(marker); 46 } else if ( urlStr.endsWith(".wav")) {46 } else if (Utils.hasExtension(urlStr, "wav", "mp3", "aac", "aif", "aiff")) { 47 47 final AudioMarker audioMarker = new AudioMarker(wpt.getCoor(), wpt, url, parentLayer, time, offset); 48 48 Extensions exts = (Extensions) wpt.get(GpxConstants.META_EXTENSIONS); … … 55 55 } 56 56 return Arrays.asList(marker, audioMarker); 57 } else if (urlStr.endsWith(".png") || urlStr.endsWith(".jpg") || urlStr.endsWith(".jpeg") 58 || urlStr.endsWith(".gif")) { 57 } else if (Utils.hasExtension(urlStr, "png", "jpg", "jpeg", "gif")) { 59 58 return Arrays.asList(marker, new ImageMarker(wpt.getCoor(), url, parentLayer, time, offset)); 60 59 } else { -
trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/Marker.java
r11892 r12328 53 53 * 54 54 * By default, one the list contains one default "Maker" implementation that 55 * will create AudioMarkers for .wav files, ImageMarkers for .png/.jpg/.jpeg55 * will create AudioMarkers for supported audio files, ImageMarkers for supported image 56 56 * files, and WebMarkers for everything else. (The creation of a WebMarker will 57 57 * fail if there's no valid URL in the <link> tag, so it might still make sense -
trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java
r12326 r12328 25 25 import org.openstreetmap.josm.gui.layer.GpxLayer; 26 26 import org.openstreetmap.josm.io.audio.AudioPlayer; 27 import org.openstreetmap.josm.io.audio.AudioUtil; 27 28 28 29 /** … … 100 101 AudioPlayer.pause(); 101 102 } catch (IOException | InterruptedException ex) { 102 Audio Player.audioMalfunction(ex);103 AudioUtil.audioMalfunction(ex); 103 104 } 104 105 } … … 114 115 AudioPlayer.pause(); 115 116 } catch (IOException | InterruptedException ex) { 116 Audio Player.audioMalfunction(ex);117 AudioUtil.audioMalfunction(ex); 117 118 } 118 119 } -
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.