diff --git a/interface/build.gradle b/interface/build.gradle index fbaefb6..b8c8c49 100644 --- a/interface/build.gradle +++ b/interface/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.xerial:sqlite-jdbc:3.36.0.1' implementation 'com.googlecode.soundlibs:jlayer:1.0.1.4' implementation 'com.mpatric:mp3agic:0.9.1' + implementation 'uk.co.caprica:vlcj:4.7.1' implementation project(":libwave") implementation project(":browserCommands") @@ -42,10 +43,5 @@ targetCompatibility = JavaVersion.VERSION_16 mainClassName = defaultPackage + '.player.Interface' //mainClassName = defaultPackage + '.localPlayer.Player' -run { - systemProperty "java.library.path", file("${project(":player").buildDir}/lib/main/debug").absolutePath -} - compileJava.dependsOn rootProject.bundleAddOn -compileJava.dependsOn ':player:linkDebug' compileJava.dependsOn ':addonInter:installAddon' \ No newline at end of file diff --git a/interface/src/main/java/edu/regis/universeplayer/PlaybackStatus.java b/interface/src/main/java/edu/regis/universeplayer/PlaybackStatus.java index 086df3a..e134896 100644 --- a/interface/src/main/java/edu/regis/universeplayer/PlaybackStatus.java +++ b/interface/src/main/java/edu/regis/universeplayer/PlaybackStatus.java @@ -21,8 +21,12 @@ public enum PlaybackStatus * The player has a song loaded, but is not playing it. */ STOPPED, + /** + * The player has is stopped, but just finished a song and may have more. + */ + FINISHED, /** * No song is loaded. */ - EMPTY; + EMPTY } diff --git a/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java b/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java index eb9744a..478962e 100644 --- a/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java +++ b/interface/src/main/java/edu/regis/universeplayer/browser/Browser.java @@ -4,6 +4,8 @@ package edu.regis.universeplayer.browser; +import edu.regis.universeplayer.PlaybackListener; +import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.Player; import edu.regis.universeplayer.browserCommands.*; import edu.regis.universeplayer.data.Song; @@ -161,7 +163,7 @@ public class Browser extends MessageRunner implements Player { try { - return new ForwardedFuture(this.sendObject(new CommandLoadSong(song.location))); + return new ForwardedFuture(this.sendObject(new CommandLoadSong(song.location))); } catch (IOException e) { @@ -196,7 +198,41 @@ public class Browser extends MessageRunner implements Player } return null; } - + + /** + * Adds a listener for playback status updates. + * + * @param listener - The listener to add. + */ + @Override + public void addPlaybackListener(PlaybackListener listener) + { + + } + + /** + * Checks to see if a listener has been added. + * + * @param listener - The listener to look for. + * @return True if the provided listener has been added, false otherwise. + */ + @Override + public boolean hasPlaybackListener(PlaybackListener listener) + { + return false; + } + + /** + * Removes a listener for playback status updates. + * + * @param listener - The listener to remove. + */ + @Override + public void removePlaybackListener(PlaybackListener listener) + { + + } + @Override public QueryFuture play() { @@ -214,15 +250,29 @@ public class Browser extends MessageRunner implements Player { return null; } - + + /** + * Stops playback of the current song. + */ + @Override + public QueryFuture stopSong() + { + return null; + } + @Override public QueryFuture seek(float time) { return null; } - + + /** + * Obtains the player's current playback status. + * + * @return A future for the request. + */ @Override - public QueryFuture isPaused() + public QueryFuture getStatus() { return null; } @@ -241,9 +291,9 @@ public class Browser extends MessageRunner implements Player private class ForwardedFuture implements QueryFuture { - private final Future future; + private final Future future; - ForwardedFuture(Future future) + ForwardedFuture(Future future) { this.future = future; } diff --git a/interface/src/main/java/edu/regis/universeplayer/data/Queue.java b/interface/src/main/java/edu/regis/universeplayer/data/Queue.java index 1d86068..6b716c6 100644 --- a/interface/src/main/java/edu/regis/universeplayer/data/Queue.java +++ b/interface/src/main/java/edu/regis/universeplayer/data/Queue.java @@ -112,6 +112,7 @@ public class Queue extends ArrayList */ public Song skipNext() { + int index = this.currentIndex; if (++this.currentIndex >= this.queueOrder.size()) { if (this.repeat) @@ -122,7 +123,10 @@ public class Queue extends ArrayList */ this.queueOrder.clear(); this.getOrder(); - this.triggerSongChangeListeners(); + if (this.currentIndex != index) + { + this.triggerSongChangeListeners(); + } return this.getCurrentSong(); } else @@ -131,11 +135,17 @@ public class Queue extends ArrayList * Make sure we aren't infinitely increasing the current index. */ this.currentIndex = this.queueOrder.size() - 1; - this.triggerSongChangeListeners(); + if (this.currentIndex != index) + { + this.triggerSongChangeListeners(); + } return null; } } - this.triggerSongChangeListeners(); + if (this.currentIndex != index) + { + this.triggerSongChangeListeners(); + } return this.getCurrentSong(); } @@ -146,6 +156,7 @@ public class Queue extends ArrayList */ public Song skipPrev() { + int index = this.currentIndex; if (--this.currentIndex < 0) { if (this.repeat) @@ -157,7 +168,10 @@ public class Queue extends ArrayList this.currentIndex = 0; } } - this.triggerSongChangeListeners(); + if (this.currentIndex != index) + { + this.triggerSongChangeListeners(); + } return this.getCurrentSong(); } @@ -170,8 +184,12 @@ public class Queue extends ArrayList public Song skipToSong(int index) { Song song = this.get(index); - this.currentIndex = this.queueOrder.indexOf(song); - this.triggerSongChangeListeners(); + index = this.queueOrder.indexOf(song); + if (this.currentIndex != index) + { + this.currentIndex = index; + this.triggerSongChangeListeners(); + } return this.getCurrentSong(); } @@ -197,6 +215,7 @@ public class Queue extends ArrayList @Override public boolean add(Song song) { + Song oldSong = this.getCurrentSong(); int newIndex; if (super.add(song)) { @@ -214,6 +233,10 @@ public class Queue extends ArrayList this.queueOrder.add(song); } this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } return true; } return false; @@ -222,6 +245,7 @@ public class Queue extends ArrayList @Override public void add(int index, Song song) { + Song oldSong = this.getCurrentSong(); int newIndex; super.add(index, song); if (this.shuffle) @@ -237,6 +261,10 @@ public class Queue extends ArrayList this.currentIndex++; } this.queueOrder.add(newIndex, song); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } this.triggerQueueChangeListeners(); } @@ -244,6 +272,7 @@ public class Queue extends ArrayList public Song remove(int index) { Song removed = super.remove(index); + boolean current = this.getCurrentSong() == removed; if (removed != null) { index = this.queueOrder.indexOf(removed); @@ -253,6 +282,10 @@ public class Queue extends ArrayList } this.queueOrder.remove(removed); this.triggerQueueChangeListeners(); + if (current) + { + this.triggerSongChangeListeners(); + } } return removed; } @@ -261,6 +294,7 @@ public class Queue extends ArrayList public boolean remove(Object o) { int index = this.indexOf(o); + boolean removed = this.getCurrentSong() == o; if (super.remove(o)) { if (index <= this.currentIndex) @@ -269,6 +303,10 @@ public class Queue extends ArrayList } this.queueOrder.remove(o); this.triggerQueueChangeListeners(); + if (removed) + { + this.triggerSongChangeListeners(); + } return true; } return false; @@ -289,6 +327,7 @@ public class Queue extends ArrayList { int newIndex; int length = this.size(); + Song oldSong = this.getCurrentSong(); if (super.addAll(c)) { for (Song song : c) @@ -308,6 +347,10 @@ public class Queue extends ArrayList } } this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } return true; } return false; @@ -318,6 +361,7 @@ public class Queue extends ArrayList { int newIndex; int i = 0; + Song oldSong = this.getCurrentSong(); if (super.addAll(index, c)) { for (Song song : c) @@ -337,6 +381,10 @@ public class Queue extends ArrayList this.queueOrder.add(newIndex, song); } this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } return true; } return false; @@ -345,6 +393,7 @@ public class Queue extends ArrayList @Override protected void removeRange(int fromIndex, int toIndex) { + Song oldSong = this.getCurrentSong(); for (int i = fromIndex; i < toIndex; i++) { this.queueOrder.remove(this.get(i)); @@ -355,12 +404,17 @@ public class Queue extends ArrayList } super.removeRange(fromIndex, toIndex); this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } } @Override public boolean removeAll(Collection c) { boolean success; + Song oldSong = this.getCurrentSong(); for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++) { if (c.contains(this.get(i))) @@ -371,6 +425,10 @@ public class Queue extends ArrayList this.queueOrder.removeAll(c); success = super.removeAll(c); this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } return success; } @@ -378,6 +436,7 @@ public class Queue extends ArrayList public boolean retainAll(Collection c) { boolean success; + Song oldSong = this.getCurrentSong(); for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++) { if (!c.contains(this.get(i))) @@ -388,6 +447,10 @@ public class Queue extends ArrayList this.queueOrder.retainAll(c); success = super.retainAll(c); this.triggerQueueChangeListeners(); + if (this.getCurrentSong() != oldSong) + { + this.triggerSongChangeListeners(); + } return success; } diff --git a/interface/src/main/java/edu/regis/universeplayer/localPlayer/LocalPlayer.java b/interface/src/main/java/edu/regis/universeplayer/localPlayer/LocalPlayer.java index 79e116f..4e6d18e 100644 --- a/interface/src/main/java/edu/regis/universeplayer/localPlayer/LocalPlayer.java +++ b/interface/src/main/java/edu/regis/universeplayer/localPlayer/LocalPlayer.java @@ -4,6 +4,7 @@ package edu.regis.universeplayer.localPlayer; import com.intervigil.wave.WaveReader; + import edu.regis.universeplayer.PlaybackListener; import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.Player; @@ -11,9 +12,19 @@ import edu.regis.universeplayer.browserCommands.CommandConfirmation; import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.data.LocalSong; import edu.regis.universeplayer.data.Song; +import uk.co.caprica.vlcj.factory.MediaPlayerFactory; +import uk.co.caprica.vlcj.media.MediaRef; +import uk.co.caprica.vlcj.media.TrackType; +import uk.co.caprica.vlcj.player.base.MediaPlayer; +import uk.co.caprica.vlcj.player.base.MediaPlayerEventListener; +import uk.co.caprica.vlcj.player.base.StatusApi; +import uk.co.caprica.vlcj.player.component.AudioPlayerComponent; +import uk.co.caprica.vlcj.player.component.EmbeddedMediaPlayerComponent; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.swing.*; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -28,22 +39,29 @@ import java.util.concurrent.*; * @author William Hubbard * @version 0.1 */ -public class LocalPlayer implements Player +public class LocalPlayer implements Player, MediaPlayerEventListener { private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class); - - static - { - System.loadLibrary("player"); - } - + + private final MediaPlayerFactory playerFactory; + private final AudioPlayerComponent player; + + private final LinkedList listeners = new LinkedList<>(); + private int currentId; - + private LocalSong currentSong; private AudioFile currentFile; - + private final ExecutorService service = Executors.newSingleThreadExecutor(); - + + public LocalPlayer() + { + this.playerFactory = new MediaPlayerFactory(); + this.player = new AudioPlayerComponent(); + this.player.mediaPlayer().events().addMediaPlayerEventListener(this); + } + /** * Sets the file currently being used. * @@ -54,28 +72,8 @@ public class LocalPlayer implements Player WaveReader header = file.getHeader(); this.currentFile = file; // TODO - Ensure that bytes per sample and bits per sample don't bother things - this.currentId = this.setCurrentFile(this.currentFile, (short) header.getChannels(), (short) header - .getPcmFormat(), header.getSampleRate()); } - - /** - * Updates the file currently being played. - * - * @param stream - A reference to the audio data stream. - * @param numChannels - The number of audio channels contained in the file. - * @param bitsPerSample - The number if bits in every sample. - * @param sampleRate - How many samples need to play every second. - * @return An ID for the current song. - */ - private native int setCurrentFile(InputStream stream, short numChannels, short bitsPerSample, int sampleRate); - - /** - * Saves the current file to - * - * @param file - The file to save to. - */ - public native void save(String file); - + /** * Saves the current file as a WAVE file somewhere else. This method is meant for testing only. * @@ -99,7 +97,7 @@ public class LocalPlayer implements Player logger.error("Could not save WAVE file", e); } } - + /** * Obtains the current file. * @@ -109,127 +107,122 @@ public class LocalPlayer implements Player { return this.currentFile; } - + @Override public Song getCurrentSong() { return this.currentSong; } - + @Override public QueryFuture loadSong(LocalSong song) { - if (this.currentSong == null) + if (this.currentSong != null) { this.stopSong(); } this.currentSong = song; - Future runnable = service.submit(() -> { - try - { - AudioFile stream = getAudioStream(song.file); - this.setCurrentFile(stream); - } - catch (IOException e) - { - logger.error("Could not load local file " + song.file.getAbsolutePath(), e); - throw new RuntimeException(e); - } - return null; - }); - - return new WrappedFuture<>(runnable); + this.player.mediaPlayer().media().play(song.file.getAbsolutePath()); + return new NullFuture<>(); } - + @Override public QueryFuture play() { - return null; + this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().play()); + return new NullFuture<>(); } - + @Override public QueryFuture pause() { - return null; + this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().pause()); + return new NullFuture<>(); } - + @Override public QueryFuture togglePlayback() { - return null; + if (this.player.mediaPlayer().status().isPlaying()) + { + return this.pause(); + } + else + { + return this.play(); + } } - + @Override public QueryFuture stopSong() { - return null; + this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().stop()); + return new NullFuture<>(); } - - @Override - public void addPlaybackListener(PlaybackListener listener) - { - - } - + @Override public QueryFuture seek(float time) { - return null; + this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls() + .setTime((long) (time * 1000))); + return new NullFuture<>(); } - + @Override public QueryFuture getStatus() { - return null; + PlaybackStatus returnStatus; + StatusApi status = this.player.mediaPlayer().status(); + switch (status.state()) + { + case NOTHING_SPECIAL -> returnStatus = PlaybackStatus.EMPTY; + case PLAYING -> returnStatus = PlaybackStatus.PLAYING; + case PAUSED -> returnStatus = PlaybackStatus.PAUSED; + default -> returnStatus = PlaybackStatus.STOPPED; + } + return new NullFuture<>(returnStatus); } - + @Override public QueryFuture getCurrentTime() { - return null; + return new NullFuture<>(this.player.mediaPlayer().status().time() / 1000F); } - + @Override public QueryFuture getLength() { - return null; + return new NullFuture<>(this.player.mediaPlayer().status().length() / 1000F); } - + @Override public QueryFuture close() { - return null; + this.player.release(); + return new NullFuture<>(); } - - /** - * Plays the current audio file. - */ - public native void playSong(); - - /** - * Pauses playback for the current audio file. - */ - public native void pauseSong(); - - /** - * Checks to see whether the current audio file is paused. - * - * @return True if there is an audio file present and it is paused, false otherwise. - */ - public native boolean isSongPaused(); - + + @Override + public void addPlaybackListener(PlaybackListener listener) + { + if (!this.hasPlaybackListener(listener)) + { + this.listeners.add(listener); + } + } + @Override public boolean hasPlaybackListener(PlaybackListener listener) { - return false; + return this.listeners.contains(listener); } - + @Override public void removePlaybackListener(PlaybackListener listener) { - + this.listeners.remove(listener); } - + /** * Obtains an input stream for the requested file. * @@ -242,7 +235,7 @@ public class LocalPlayer implements Player { return new AudioFile(convertFile(file)); } - + /** * Converts any audio file to a stream containing WAV audio file data (courtesy of FFMPEG). * @@ -268,48 +261,379 @@ public class LocalPlayer implements Player args.add("-f"); args.add("wav"); args.add("pipe:1"); - return Runtime.getRuntime().exec(args.toArray(new String[args.size()])); + return Runtime.getRuntime().exec(args.toArray(String[]::new)); } - + public static void main(String[] args) { - byte[] buffer = new byte[200]; - int len; - File file = new File(args[0]); - try (AudioFile stream = getAudioStream(new File(args[0]))) - { - LocalPlayer player = new LocalPlayer(); - player.setCurrentFile(stream); - player.save(args[0] + ".wav"); - } - catch (IOException e) - { - logger.error("Could not save audio file " + file, e); - } - // Compare JNI vs Java - try (AudioFile stream = getAudioStream(new File(args[0]))) - { - LocalPlayer player = new LocalPlayer(); - player.setCurrentFile(stream); - player.saveJava(args[0] + ".orig.wav"); - } - catch (IOException e) - { - logger.error("Could not save audio file " + file, e); - } } - + + /** + * The media changed. + * + * @param mediaPlayer media player that raised the event + * @param media new media instance + */ + @Override + public void mediaChanged(MediaPlayer mediaPlayer, MediaRef media) + { + logger.debug("New media {} loaded", media); + } + + /** + * Opening the media. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void opening(MediaPlayer mediaPlayer) + { + + } + + /** + * Buffering media. + * + * @param mediaPlayer media player that raised the event + * @param newCache percentage complete, ranging from 0.0 to 100.0 + */ + @Override + public void buffering(MediaPlayer mediaPlayer, float newCache) + { + + } + + /** + * The media started playing. + *

+ * There is no guarantee that a video output has been created at this point. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void playing(MediaPlayer mediaPlayer) + { + logger.debug("Local player playing."); + SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackListener.PlaybackInfo(this, this.currentSong, mediaPlayer.status().time(), PlaybackStatus.PLAYING)))); + } + + /** + * Media paused. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void paused(MediaPlayer mediaPlayer) + { + logger.debug("Local player paused."); + SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackListener.PlaybackInfo(this, this.currentSong, mediaPlayer.status().time(), PlaybackStatus.PAUSED)))); + } + + /** + * Media stopped. + *

+ * A stopped event may be raised under certain circumstances even if the media player is not playing (e.g. as part + * of the associated media list player sub-item handling). Client applications must therefore be prepared to handle + * such a situation. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void stopped(MediaPlayer mediaPlayer) + { + logger.debug("Local player stopped prematurely."); + SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackListener.PlaybackInfo(this, this.currentSong, mediaPlayer.status().time(), PlaybackStatus.STOPPED)))); + } + + /** + * Media skipped forward. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void forward(MediaPlayer mediaPlayer) + { + + } + + /** + * Media skipped backward. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void backward(MediaPlayer mediaPlayer) + { + + } + + /** + * Media finished playing (i.e. the end was reached without being stopped). + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void finished(MediaPlayer mediaPlayer) + { + logger.debug("Local player finished."); + SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackListener.PlaybackInfo(this, this.currentSong, mediaPlayer.status().time(), PlaybackStatus.FINISHED)))); + } + + /** + * Media play-back time changed. + * + * @param mediaPlayer media player that raised the event + * @param newTime new time + */ + @Override + public void timeChanged(MediaPlayer mediaPlayer, long newTime) + { + SwingUtilities.invokeLater(() -> this.listeners.forEach(playbackListener -> playbackListener.onPlaybackChanged(new PlaybackListener.PlaybackInfo(this, this.currentSong, newTime / 1000F, PlaybackStatus.PLAYING)))); + } + + /** + * Media play-back position changed. + * + * @param mediaPlayer media player that raised the event + * @param newPosition percentage between 0.0 and 1.0 + */ + @Override + public void positionChanged(MediaPlayer mediaPlayer, float newPosition) + { + + } + + /** + * Media seekable status changed. + * + * @param mediaPlayer media player that raised the event + * @param newSeekable new seekable status + */ + @Override + public void seekableChanged(MediaPlayer mediaPlayer, int newSeekable) + { + + } + + /** + * Media pausable status changed. + * + * @param mediaPlayer media player that raised the event + * @param newPausable new pausable status + */ + @Override + public void pausableChanged(MediaPlayer mediaPlayer, int newPausable) + { + + } + + /** + * Media title changed. + * + * @param mediaPlayer media player that raised the event + * @param newTitle new title + */ + @Override + public void titleChanged(MediaPlayer mediaPlayer, int newTitle) + { + + } + + /** + * A snapshot was taken. + * + * @param mediaPlayer media player that raised the event + * @param filename name of the file containing the snapshot + */ + @Override + public void snapshotTaken(MediaPlayer mediaPlayer, String filename) + { + + } + + /** + * Media length changed. + * + * @param mediaPlayer media player that raised the event + * @param newLength new length (number of milliseconds) + */ + @Override + public void lengthChanged(MediaPlayer mediaPlayer, long newLength) + { + + } + + /** + * The number of video outputs changed. + * + * @param mediaPlayer media player that raised the event + * @param newCount new number of video outputs + */ + @Override + public void videoOutput(MediaPlayer mediaPlayer, int newCount) + { + + } + + /** + * Program scrambled changed. + * + * @param mediaPlayer media player that raised the event + * @param newScrambled new scrambled value + */ + @Override + public void scrambledChanged(MediaPlayer mediaPlayer, int newScrambled) + { + + } + + /** + * An elementary stream was added. + * + * @param mediaPlayer media player that raised the event + * @param type type of stream + * @param id identifier of stream + */ + @Override + public void elementaryStreamAdded(MediaPlayer mediaPlayer, TrackType type, int id) + { + + } + + /** + * An elementary stream was deleted. + * + * @param mediaPlayer media player that raised the event + * @param type type of stream + * @param id identifier of stream + */ + @Override + public void elementaryStreamDeleted(MediaPlayer mediaPlayer, TrackType type, int id) + { + + } + + /** + * An elementary stream was selected. + * + * @param mediaPlayer media player that raised the event + * @param type type of stream + * @param id identifier of stream + */ + @Override + public void elementaryStreamSelected(MediaPlayer mediaPlayer, TrackType type, int id) + { + + } + + /** + * The media player was corked/un-corked. + *

+ * Corking/un-corking can occur e.g. when another media player (or some + * other application) starts/stops playing media. + * + * @param mediaPlayer media player that raised the event + * @param corked true if corked; otherwise false + */ + @Override + public void corked(MediaPlayer mediaPlayer, boolean corked) + { + + } + + /** + * The audio was muted/un-muted. + * + * @param mediaPlayer media player that raised the event + * @param muted true if muted; otherwise false + */ + @Override + public void muted(MediaPlayer mediaPlayer, boolean muted) + { + + } + + /** + * The volume changed. + * + * @param mediaPlayer media player that raised the event + * @param volume new volume + */ + @Override + public void volumeChanged(MediaPlayer mediaPlayer, float volume) + { + + } + + /** + * The audio device changed. + * + * @param mediaPlayer media player that raised the event + * @param audioDevice new audio device + */ + @Override + public void audioDeviceChanged(MediaPlayer mediaPlayer, String audioDevice) + { + + } + + /** + * The chapter changed. + * + * @param mediaPlayer media player that raised the event + * @param newChapter new chapter + */ + @Override + public void chapterChanged(MediaPlayer mediaPlayer, int newChapter) + { + + } + + /** + * An error occurred. + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void error(MediaPlayer mediaPlayer) + { + logger.error("Local player error"); + + } + + /** + * Media player is ready (to enable features like logo and marquee) after + * the media has started playing. + *

+ * The implementation will fire this event once on receipt of the first + * native position-changed event with a position value greater than zero. + *

+ * The event will be fired again if the media is played again after a native + * stopped or finished event is received. + *

+ * Waiting for this event may be more reliable than using {@link #playing(MediaPlayer)} + * or {@link #videoOutput(MediaPlayer, int)} in some cases (logo and marquee + * already mentioned, also setting audio tracks, sub-title tracks and so on). + * + * @param mediaPlayer media player that raised the event + */ + @Override + public void mediaPlayerReady(MediaPlayer mediaPlayer) + { + logger.debug("Local player ready."); + } + private static class WrappedFuture implements QueryFuture { private final Future runnable; - + WrappedFuture(Future runnable) { this.runnable = runnable; } - + @Override - public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException + public CommandConfirmation getConfirmation() throws CancellationException, InterruptedException { try { @@ -321,9 +645,9 @@ public class LocalPlayer implements Player return new CommandConfirmation(e); } } - + @Override - public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { try { @@ -335,35 +659,109 @@ public class LocalPlayer implements Player return new CommandConfirmation(e); } } - + @Override public boolean cancel(boolean mayInterruptIfRunning) { return this.runnable.cancel(mayInterruptIfRunning); } - + @Override public boolean isCancelled() { return this.runnable.isCancelled(); } - + @Override public boolean isDone() { return this.runnable.isDone(); } - + @Override public T get() throws InterruptedException, ExecutionException { return this.runnable.get(); } - + @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return this.runnable.get(timeout, unit); } } + + private static class NullFuture implements QueryFuture + { + private final T value; + + NullFuture() + { + this(null); + } + + NullFuture(T returnVal) + { + this.value = returnVal; + } + + @Override + public CommandConfirmation getConfirmation() throws CancellationException + { + return new CommandConfirmation(); + } + + @Override + public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) + { + return new CommandConfirmation(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) + { + return false; + } + + @Override + public boolean isCancelled() + { + return false; + } + + @Override + public boolean isDone() + { + return true; + } + + @Override + public T get() throws InterruptedException, ExecutionException + { + return this.value; + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException + { + return this.value; + } + } + + private interface WrappedRunnable extends Runnable + { + default void run() + { + try + { + runLogic(); + } + catch (Throwable e) + { + logger.error("Could not execute command", e); + } + } + + void runLogic(); + } } diff --git a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java index 7c43486..62c9439 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java @@ -8,6 +8,8 @@ import edu.regis.universeplayer.Player; import edu.regis.universeplayer.browser.Browser; import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.*; +import edu.regis.universeplayer.localPlayer.LocalPlayer; + import net.harawata.appdirs.AppDirsFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +31,7 @@ import java.util.concurrent.Future; * @author William Hubbard * @version 0.1 */ -public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, PlaybackCommandListener, UpdateListener, FocusListener +public class Interface extends JFrame implements SongDisplayListener, ComponentListener, WindowListener, UpdateListener, FocusListener { private static final Logger logger = LoggerFactory.getLogger(Interface.class); @@ -84,7 +86,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL inter.setSize(700, 500); SongProvider.INSTANCE.addUpdateListener(inter); inter.setVisible(true); - + + Player.REGISTERED_PLAYERS.put(LocalSong.class, new LocalPlayer()); try { @@ -189,7 +192,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL this.controls = new PlayerControls(); this.controls.addFocusListener(this); - controls.addCommandListener(this); this.getContentPane().add(controls, BorderLayout.PAGE_END); this.queueList = new QueueList(); @@ -321,39 +323,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL } - /** - * Called when a playback command is issued. - * - * @param command - The command issued. - * @param data - Additional data relevent to the command. - */ - @Override - public void onCommand(PlaybackCommand command, Object data) - { - Player player = null; - if (this.currentPlayer >= 0 && this.currentPlayer < this.players.size()) - { - player = this.players.get(this.currentPlayer); - } - switch (command) - { - case PLAY -> { - if (data instanceof Song) - { - player.loadSong((Song) data); - } - } - case PAUSE -> { - } - case NEXT -> { - - } - case PREVIOUS -> Queue.getInstance().skipPrev(); - case SEEK -> { - } - } - } - @Override public void onUpdate(int updated, int totalUpdate, String updating) { diff --git a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java index 02d5cd2..b634e11 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java @@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory; import java.awt.*; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.util.LinkedList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; @@ -41,7 +43,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, private final JButton playButton; private final JButton nextButton; private final JButton prevButton; - private final JSlider progress; + private final JProgressBar progress; private final JProgressBar updateProgress; private final ForkJoinPool service = new ForkJoinPool(); @@ -100,8 +102,16 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, progressCont = new JPanel(progressLayout); this.add(progressCont); - this.progress = new JSlider(); - this.progress.addChangeListener(changeEvent -> this.seek(((JSlider) changeEvent.getSource()).getValue())); + this.progress = new JProgressBar(); + this.progress.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + seek((float) e.getX() / (float) e.getComponent().getWidth() * ((JProgressBar) e.getComponent()).getMaximum()); + logger.debug("Changing time"); + } + }); this.add(this.progress); this.updateProgress = new JProgressBar(); @@ -142,14 +152,15 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this); layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress); layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this); - + + layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.progress); layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress); layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress); Queue.getInstance().addSongChangeListener(this); } - private void seek(int value) + private void seek(float value) { this.service.execute(() -> { if (this.currentPlayer != null) @@ -172,12 +183,8 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { switch ((PlaybackStatus) this.currentPlayer.getStatus().get()) { - case PAUSED -> { - this.currentPlayer.play(); - } - case PLAYING -> { - this.currentPlayer.pause(); - } + case PAUSED -> this.currentPlayer.play(); + case PLAYING -> this.currentPlayer.pause(); case STOPPED, EMPTY -> { if (Queue.getInstance().size() > 0) { @@ -291,14 +298,15 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, if (this.currentSong != null) { this.currentPlayer = Player.REGISTERED_PLAYERS.get(this.currentSong.getClass()); - if (!this.currentPlayer.hasPlaybackListener(this)) - { - this.currentPlayer.addPlaybackListener(this); - } this.progress.setMaximum((int) (this.currentSong.duration / 1000)); if (this.currentPlayer != null) { - this.currentPlayer.play(); + if (!this.currentPlayer.hasPlaybackListener(this)) + { + this.currentPlayer.addPlaybackListener(this); + } + this.currentPlayer.loadSong(this.currentSong); +// this.currentPlayer.play(); } else { @@ -320,6 +328,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, switch (status.getStatus()) { case PLAYING -> this.playButton.setIcon(PAUSE_ICON); + case FINISHED -> Queue.getInstance().skipNext(); case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON); } this.progress.setValue((int) status.getPlayTime()); diff --git a/interface/src/main/resources/log4j2.xml b/interface/src/main/resources/log4j2.xml index 51746ff..bb59faf 100644 --- a/interface/src/main/resources/log4j2.xml +++ b/interface/src/main/resources/log4j2.xml @@ -17,11 +17,11 @@ - - - - + + + + \ No newline at end of file diff --git a/player/build.gradle b/player/build.gradle deleted file mode 100644 index 5a7f413..0000000 --- a/player/build.gradle +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 William Hubbard. All Rights Reserved. - */ -import org.gradle.internal.jvm.Jvm - -/* - * There is currently no "C application" plugin, so this build uses the "C++ application" plugin and then reconfigures it - * to build C instead. - */ -plugins { - id 'cpp-library' -} - -library { - binaries.configureEach { CppBinary binary -> - def compileTask = binary.compileTask.get() - compileTask.includes.from("${Jvm.current().javaHome}/include") - - compileTask.source.from fileTree(dir: "src/main/c", include: "interface.h") - compileTask.source.from fileTree(dir: "src/main/c", include: "interface.c") - compileTask.source.from fileTree(dir: "src/main/c", include: "player.h") - compileTask.source.from fileTree(dir: "src/main/c", include: "player.c") - - def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily - if (osFamily.macOs) { - compileTask.includes.from("${Jvm.current().javaHome}/include/darwin") - compileTask.source.from fileTree(dir: "src/main/c", include: "player_mac.c") - } else if (osFamily.linux) { - compileTask.includes.from("${Jvm.current().javaHome}/include/linux") - compileTask.source.from fileTree(dir: "src/main/c", include: "player_alsa.c") - } else if (osFamily.windows) { - compileTask.includes.from("${Jvm.current().javaHome}/include/win32") - compileTask.source.from fileTree(dir: "src/main/c", include: "player_win.c") - compileTask.source.from fileTree(dir: "src/main/c", include: "windows_mutex.c") - } - - def toolChain = binary.toolChain - if (toolChain instanceof VisualCpp) { - compileTask.compilerArgs.addAll(["/TC"]) - } else if (toolChain instanceof GccCompatibleToolChain) { - compileTask.compilerArgs.addAll(["-x", "c", "-std=c11"]) - } - } -} \ No newline at end of file diff --git a/player/src/main/c/interface.c b/player/src/main/c/interface.c deleted file mode 100644 index 500df5c..0000000 --- a/player/src/main/c/interface.c +++ /dev/null @@ -1,259 +0,0 @@ -#include "interface.h" - -/** - * This function is used to initialize the static reference to java.io.InputStream class. - * - * @param env - A reference to the JVM. - * @param audio - An InputStream instance - * @return A reference to the InputStream class. - */ -jclass getInputStreamClass(JNIEnv *env) -{ - if (InputStream == 0 ) - { - jclass tempClass = (*env)->FindClass(env, "java/io/InputStream"); - if (tempClass == 0) - { - fprintf(stderr, "Could not find class java/io/InputStream"); - return 0; - } - InputStream = (*env)->NewGlobalRef(env, tempClass); - } - return InputStream; -} - -/** - * This function is used to initialize the static reference to the AudioStream class. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the AudioStream class. - */ -jclass getAudioStreamClass(JNIEnv *env, jobject audio) -{ - if (AudioStream == 0 ) - { - jclass tempClass = (*env)->GetObjectClass(env, audio); - if (tempClass == 0) - { - fprintf(stderr, "Could not find class edu/regis/universeplayer_localPlayer/AudioFile"); - return 0; - } - AudioStream = (*env)->NewGlobalRef(env, tempClass); - } - return AudioStream; -} - -jclass getHeaderClass(JNIEnv *env) -{ - if (Header == 0) - { - jclass tempClass = (*env)->FindClass(env, "wave/WavHeader"); - if (tempClass == 0) - { - fprintf(stderr, "Could not find wave/WavHeader"); - return 0; - } - Header = (*env)->NewGlobalRef(env, tempClass); - } - return Header; -} - -/** - * This function is used to initialize the static reference to AudioStream's getHeader method. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getHeader method. - */ -jmethodID getHeaderMethod(JNIEnv *env, jobject audio) -{ - if (AudioStream_getHeader == 0 ) - { - jclass tempClass = getAudioStreamClass(env, audio); - if (tempClass == 0) - { - return 0; - } - AudioStream_getHeader = (*env)->GetMethodID(env, tempClass, "getHeader", "()Lwave/WavHeader;"); - } - return AudioStream_getHeader; -} - -/** - * This function is used to initialize the static reference to AudioStream's getByteStream method. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getByteStream method. - */ -jmethodID getByteStreamMethod(JNIEnv *env, jobject audio) -{ - if (AudioStream_getByteStream == 0 ) - { - jclass tempClass = getAudioStreamClass(env, audio); - if (tempClass == 0) - { - return 0; - } - AudioStream_getByteStream = (*env)->GetMethodID(env, tempClass, "getByteStream", "()[B"); - } - return AudioStream_getByteStream; -} - -/** - * This function is used to initialize the static references to AudioStream's read methods. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getByteStream method. - */ -jmethodID getReadMethod(JNIEnv *env, jobject audio) -{ - jclass tempClass; - if (AudioStream_readInt == 0) - { - tempClass = getAudioStreamClass(env, audio); - if (tempClass == 0) - { - fprintf(stderr, "Could not find AudioStream class"); - } - else - { - AudioStream_readInt = (*env)->GetMethodID(env, tempClass, "read", "()I"); - printf("Found class AudioStream"); - } - } - if (InputStream_readBuffer == 0 ) - { - tempClass = getInputStreamClass(env); - if (tempClass == 0) - { - fprintf(stderr, "Could not find InputStream class"); - } - else - { - InputStream_readBuffer = (*env)->GetMethodID(env, tempClass, "read", "([B)I"); - printf("Found class java/io/InputStream"); - } - } - return AudioStream_readInt; -} - -/** - * This function is used to initialize the static reference to WavHeader's various methods. - * - * @param env - A reference to the JVM. - */ -void getHeaderMethods(JNIEnv *env) -{ - jclass tempClass = getHeaderClass(env); - if (tempClass == 0) - { - return; - } - if (Header_getChunkId == 0) - { - Header_getChunkId = (*env)->GetMethodID(env, tempClass, "getChunkID", "()[B"); - if (Header_getChunkId == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getChunkID\n"); - } - } - if (Header_getChunkSize == 0) - { - Header_getChunkSize = (*env)->GetMethodID(env, tempClass, "getChunkSize", "()I"); - if (Header_getChunkSize == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getChunkSize\n"); - } - } - if (Header_getFormat == 0) - { - Header_getFormat = (*env)->GetMethodID(env, tempClass, "getFormat", "()[B"); - if (Header_getFormat == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getFormat\n"); - } - } - if (Header_getSubChunk1ID == 0) - { - Header_getSubChunk1ID = (*env)->GetMethodID(env, tempClass, "getSubChunk1ID", "()[B"); - if (Header_getSubChunk1ID == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getSubChunk1ID\n"); - } - } - if (Header_getSubChunk1Size == 0) - { - Header_getSubChunk1Size = (*env)->GetMethodID(env, tempClass, "getSubChunk1Size", "()I"); - if (Header_getSubChunk1Size == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getSubChunk1Size\n"); - } - } - if (Header_getAudioFormat == 0) - { - Header_getAudioFormat = (*env)->GetMethodID(env, tempClass, "getAudioFormat", "()S"); - if (Header_getAudioFormat == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getAudioFormat\n"); - } - } - if (Header_getNumChannels == 0) - { - Header_getNumChannels = (*env)->GetMethodID(env, tempClass, "getNumChannels", "()S"); - if (Header_getNumChannels == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getNumChannels\n"); - } - } - if (Header_getSampleRate == 0) - { - Header_getSampleRate = (*env)->GetMethodID(env, tempClass, "getSampleRate", "()I"); - if (Header_getSampleRate == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getSampleRate\n"); - } - } - if (Header_getByteRate == 0) - { - Header_getByteRate = (*env)->GetMethodID(env, tempClass, "getByteRate", "()I"); - if (Header_getByteRate == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getByteRate\n"); - } - } - if (Header_getBlockAlign == 0) - { - Header_getBlockAlign = (*env)->GetMethodID(env, tempClass, "getBlockAlign", "()S"); - if (Header_getBlockAlign == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getBlockAlign\n"); - } - } - if (Header_getBitsPerSample == 0) - { - Header_getBitsPerSample = (*env)->GetMethodID(env, tempClass, "getBitsPerSample", "()S"); - if (Header_getBitsPerSample == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getBitsPerSample\n"); - } - } - if (Header_getSubChunk2ID == 0) - { - Header_getSubChunk2ID = (*env)->GetMethodID(env, tempClass, "getSubChunk2ID", "()[B"); - if (Header_getSubChunk2ID == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getSubChunk2ID\n"); - } - } - if (Header_getSubChunk2Size == 0) - { - Header_getSubChunk2Size = (*env)->GetMethodID(env, tempClass, "getSubChunk2Size", "()I"); - if (Header_getSubChunk2Size == 0) - { - fprintf(stderr, "Could not find wave/WavHeader#getSubChunk2Size\n"); - } - } -} \ No newline at end of file diff --git a/player/src/main/c/interface.h b/player/src/main/c/interface.h deleted file mode 100644 index 5062915..0000000 --- a/player/src/main/c/interface.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef INTERFACE_H -#define INTERFACE_H - -#include -#include - -typedef struct { - short numChannels; - short bytesPerChannel; - int sampleRate; -} HeaderData; - -static jclass InputStream; -static jclass AudioStream; -static jclass Header; -static jmethodID AudioStream_getHeader; -static jmethodID AudioStream_getByteStream; -static jmethodID AudioStream_readInt; -static jmethodID InputStream_readBuffer; -static jmethodID Header_getChunkId; -static jmethodID Header_getChunkSize; -static jmethodID Header_getFormat; -static jmethodID Header_getSubChunk1ID; -static jmethodID Header_getSubChunk1Size; -static jmethodID Header_getAudioFormat; -static jmethodID Header_getNumChannels; -static jmethodID Header_getSampleRate; -static jmethodID Header_getByteRate; -static jmethodID Header_getBlockAlign; -static jmethodID Header_getBitsPerSample; -static jmethodID Header_getSubChunk2ID; -static jmethodID Header_getSubChunk2Size; - -/** - * This function is used to initialize the static reference to java.io.InputStream class. - * - * @param env - A reference to the JVM. - * @param audio - An InputStream instance - * @return A reference to the InputStream class. - */ -jclass getInputStreamClass(JNIEnv *env); - -/** - * This function is used to initialize the static reference to the AudioStream class. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the AudioStream class. - */ -jclass getAudioStreamClass(JNIEnv *env, jobject audio); - -jclass getHeaderClass(JNIEnv *env); - -/** - * This function is used to initialize the static reference to AudioStream's getHeader method. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getHeader method. - */ -jmethodID getHeaderMethod(JNIEnv *env, jobject audio); - -/** - * This function is used to initialize the static reference to AudioStream's getByteStream method. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getByteStream method. - */ -jmethodID getByteStreamMethod(JNIEnv *env, jobject audio); - -/** - * This function is used to initialize the static references to AudioStream's read methods. - * - * @param env - A reference to the JVM. - * @param audio - An AudioStream instance - * @return A reference to the getByteStream method. - */ -jmethodID getReadMethod(JNIEnv *env, jobject audio); - -/** - * This function is used to initialize the static reference to WavHeader's various methods. - * - * @param env - A reference to the JVM. - */ -void getHeaderMethods(JNIEnv *env); - -#endif /* INTERFACE_H */ \ No newline at end of file diff --git a/player/src/main/c/player.c b/player/src/main/c/player.c deleted file mode 100644 index a3b77a5..0000000 --- a/player/src/main/c/player.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2021 William Hubbard. All Rights Reserved. - */ -#include "interface.h" -#include "player.h" - -play_item_t play_list_head = { - 0, /* play_id */ - SA_CLEAR, /* stop_flag */ - NULL, /* prev_item */ - NULL, /* next_item */ - NULL /* mutex */ -}; - -static jobject currentFile = NULL; -static HeaderData currentHeader; - -/** - * Obtains an audio stream's header. - * @param env - A reference to the JVM. - * @param stream - The audio stream to obtain the header from - * @return The header information. - */ -jobject getHeader(JNIEnv *env, jobject stream) -{ - if (AudioStream_getHeader == 0) - { - getHeaderMethod(env, stream); - if (AudioStream_getHeader == 0) - { - return 0; - } - } - jobject returnValue = (*env)->CallObjectMethod(env, stream, AudioStream_getHeader); - return returnValue; -} - -/** - * Obtains the original raw byte data for an audio stream's header. - * @param env - A reference to the JVM. - * @param stream - The audio stream to obtain the header from. - * @return The header information. - */ -jbyteArray getByteStream(JNIEnv *env, jobject stream) -{ - if (AudioStream_getByteStream == 0) - { - getByteStreamMethod(env, stream); - if (AudioStream_getByteStream == 0) - { - return 0; - } - } - jbyteArray returnValue = (jbyteArray) (*env)->CallObjectMethod(env, stream, AudioStream_getByteStream); - return returnValue; -} - -/** - * Reads a single integer from the audio stream. - * @param env - A reference to the JVM. - * @param stream - The audio stream to obtain the header from. - * @return The integer read. - */ -int readInt(JNIEnv *env, jobject stream) -{ - if (AudioStream_readInt == 0) - { - getReadMethod(env, stream); - if (AudioStream_readInt == 0) - { - return 0; - } - } - jint returnValue = (*env)->CallIntMethod(env, stream, AudioStream_readInt); - return (int) returnValue; -} - -/** - * Fills a provided buffer with input from the audio stream. - * @param env - A reference to the JVM. - * @param stream - The audio stream to obtain the header from. - * @param buffer - The buffer to read information to - * @param bufferSize - The size of the buffer - * @return How much information was actually read. - */ -int readBuffer(JNIEnv *env, jobject stream, char* buffer, int bufferSize) -{ - if (InputStream_readBuffer == 0) - { - getReadMethod(env, stream); - if (InputStream_readBuffer == 0) - { - fprintf(stderr, "readBuffer method not found\n"); - return 0; - } - if (AudioStream_readInt == 0) - { - fprintf(stderr, "readInt method not found\n"); - return 0; - } - } - - jbyteArray jbuffer = (*env)->NewByteArray(env, bufferSize); - jint returnValue = (*env)->CallIntMethod(env, stream, InputStream_readBuffer, jbuffer); - jbyte *jbufferEl = (*env)->GetByteArrayElements(env, jbuffer, 0); - for (int i = 0; i < returnValue; i++) - { - buffer[i] = jbufferEl[i]; - } - (*env)->ReleaseByteArrayElements(env, jbuffer, jbufferEl, 0); - return returnValue; -} - -/** - * Converts a WavHeader into something more easily accessible by native code. - * - * @param env - The JVM. - * @param header - The header to read. - * @param headerStruct - The header to write the data to. - */ -HeaderData readHeader(JNIEnv *env, jobject header, HeaderData *headerStruct) -{ - if (Header == 0) - { - getHeaderMethods(env); - } - headerStruct->numChannels = (short) (*env)->CallShortMethod(env, header, Header_getNumChannels); - headerStruct->bytesPerChannel = (short) (*env)->CallShortMethod(env, header, Header_getBitsPerSample); - headerStruct->sampleRate = (int) (*env)->CallIntMethod(env, header, Header_getSampleRate); -} - -/** - * Sets the file currently being used - */ -JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_setCurrentFile(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate) -{ - jobject header; - if (currentFile != NULL) - { - (*env)->DeleteLocalRef(env, currentFile); - } - currentFile = (*env)->NewGlobalRef(env, stream); - header = getHeader(env, stream); - readHeader(env, header, ¤tHeader); - - return updatePlayer(env, obj, stream, numChannels, bitsPerSample, sampleRate); -} - -JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_save(JNIEnv *env, jobject obj, jstring location) -{ - const int BUFFER_SIZE = 256; - /* - * Windows doesn't seem to like using even a constant for determining array - * size. - */ - char buffer[256]; - int read; - - FILE *output; - - char buf[128]; - const char *locationPath = (*env)->GetStringUTFChars(env, location, 0); - output = fopen(locationPath, "w"); - - if (output == NULL) - { - fprintf(stderr, "File %s can't be opened\n", locationPath); - return; - } - - // Obtains header data - jbyteArray headerData = getByteStream(env, currentFile); - jsize headerLength = (*env)->GetArrayLength(env, headerData); - jbyte* header = (*env)->GetByteArrayElements(env, headerData, 0); - char headerBuff[256]; - for (int i = 0; i < headerLength; i++) - { - headerBuff[i] = header[i]; - } - fwrite(headerBuff, headerLength, 1, output); - (*env)->ReleaseByteArrayElements(env, headerData, header, 0); - - // Writes the actual file - read = readBuffer(env, currentFile, headerBuff, BUFFER_SIZE); - while (read > 0) - { - fwrite(headerBuff, read, 1, output); - read = readBuffer(env, currentFile, headerBuff, BUFFER_SIZE); - } - - fclose(output); - (*env)->ReleaseStringUTFChars(env, location, locationPath); - - return; -} - -JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_playSong(JNIEnv *env, jobject obj) -{ - printf("Playing\n"); - return; -} - -JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_pauseSong(JNIEnv *env, jobject obj) -{ - printf("Pausing\n"); - return; -} - -JNIEXPORT jboolean JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_isSongPaused(JNIEnv *env, jobject obj) -{ - printf("Is Paused?\n"); - return JNI_FALSE; -} \ No newline at end of file diff --git a/player/src/main/c/player.h b/player/src/main/c/player.h deleted file mode 100644 index 7c27f1e..0000000 --- a/player/src/main/c/player.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -Simpleaudio Python Extension -Copyright (C) 2015, Joe Hamilton -MIT License (see LICENSE.txt) -*/ - -#ifndef SIMPLEAUDIO_H -#define SIMPLEAUDIO_H - -#include -#include - -#define SA_ERR_STR_LEN (256) - -#define SA_CLEAR (0) -#define SA_STOP (1) - -enum { - NOT_LAST_ITEM = 0, - LAST_ITEM = 1 -}; - -typedef unsigned long long play_id_t; - -/* linked list structure used to track the active playback items/threads */ -typedef struct play_item_s { - /* the play_id of the list head is used to store the next play_id value - used by a new play list item */ - play_id_t play_id; - int stop_flag; - struct play_item_s* prev_item; - struct play_item_s* next_item; - /* the mutex of the list head is used as a 'global' mutex for modifying - and accessing the list itself */ - void* mutex; -} play_item_t; - -typedef struct { - jobject buffer_obj; - void* handle; - int used_bytes; - int len_bytes; - int num_buffers; - int frame_size; - int buffer_size; - play_item_t* play_list_item; - void* list_mutex; -} audio_blob_t; - -jint* play_os(jobject buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head, int latency_us); - -void delete_list_item(play_item_t* play_item); -play_item_t* new_list_item(play_item_t* list_head); - -void destroy_audio_blob(audio_blob_t* audio_blob); -audio_blob_t* create_audio_blob(void); - -int get_buffer_size(int latency_us, int sample_rate, int frame_size); - -void* create_mutex(void); -void destroy_mutex(void* mutex); -void grab_mutex(void* mutex); -void release_mutex(void* mutex); - -#endif /* SIMPLEAUDIO_H */ \ No newline at end of file diff --git a/player/src/main/c/player_alsa.c b/player/src/main/c/player_alsa.c deleted file mode 100644 index 5b5bedb..0000000 --- a/player/src/main/c/player_alsa.c +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) 2021 William Hubbard. All Rights Reserved. - */ -#include -#include - -int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate) -{ - printf("Updating File\n"); - return 0; -} \ No newline at end of file diff --git a/player/src/main/c/player_win.c b/player/src/main/c/player_win.c deleted file mode 100644 index df0ab3c..0000000 --- a/player/src/main/c/player_win.c +++ /dev/null @@ -1,112 +0,0 @@ -/* -Simpleaudio Python Extension -Copyright (C) 2015, Joe Hamilton -MIT License (see LICENSE.txt) -*/ -#include "simpleaudio.h" -#include -#include -#include - -int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate) -{ - printf("Updating File\n"); - return 0; -} - -jint play_os(jobject buffer_obj, int len_samples, int num_channels, int bytes_per_chan, int sample_rate, play_item_t* play_list_head, int latency_us) -{ - char err_msg_buf[SA_ERR_STR_LEN]; - char sys_msg_buf[SA_ERR_STR_LEN / 2]; - audio_blob_t* audio_blob; - - WAVEFORMATEX audio_format; - MMRESULT result; - HANDLE thread_handle = NULL; - DWORD thread_id; - int bytes_per_frame = bytes_per_chan * num_channels; - WAVEHDR* temp_wave_hdr; - int buffer_size; - int i; - - DBG_PLAY_OS_CALL - - buffer_size = get_buffer_size(latency_us / NUM_BUFS, sample_rate, bytes_per_chan * num_channels); - - audio_blob = create_audio_blob(); - audio_blob->buffer_obj = buffer_obj; - audio_blob->list_mutex = play_list_head->mutex; - audio_blob->len_bytes = len_samples * bytes_per_frame; - audio_blob->num_buffers = NUM_BUFS; - - /* setup the linked list item for this playback buffer */ - grab_mutex(play_list_head->mutex); - audio_blob->play_list_item = new_list_item(play_list_head); - release_mutex(play_list_head->mutex); - - /* windows audio device and format headers setup */ - if (bytes_per_chan < 4) { - audio_format.wFormatTag = WAVE_FORMAT_PCM; - } else { - audio_format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - } - audio_format.nChannels = num_channels; - - audio_format.nSamplesPerSec = sample_rate; - audio_format.nBlockAlign = bytes_per_frame; - /* per MSDN WAVEFORMATEX documentation */ - audio_format.nAvgBytesPerSec = audio_format.nSamplesPerSec * audio_format.nBlockAlign; - audio_format.wBitsPerSample = bytes_per_chan * 8; - audio_format.cbSize = 0; - - /* create the cleanup thread so we can return after calling waveOutWrite - SEE :http://msdn.microsoft.com/en-us/library/windows/desktop/ms682516(v=vs.85).aspx - */ - thread_handle = CreateThread(NULL, 0, bufferThread, audio_blob, 0, &thread_id); - if (thread_handle != NULL) { - /* Close so we don't leak handles - similar to detatched POSIX threads */ - CloseHandle(thread_handle); - } else { - DWORD lastError = GetLastError(); - /* lang code : US En */ - FormatMessage((FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS), NULL, lastError, 0x0409, sys_msg_buf, SYS_STR_LEN, NULL); - WIN_EXCEPTION("Failed to start cleanup thread.", 0, sys_msg_buf, err_msg_buf); - - destroy_audio_blob(audio_blob); - return NULL; - } - - /* open a handle to the default audio device */ - result = waveOutOpen((HWAVEOUT*)&audio_blob->handle, WAVE_MAPPER, &audio_format, thread_id, 0, CALLBACK_THREAD); - if (result != MMSYSERR_NOERROR) { - waveOutGetErrorText(result, sys_msg_buf, SYS_STR_LEN); - WIN_EXCEPTION("Failed to open audio device.", result, sys_msg_buf, err_msg_buf); - - PostThreadMessage(thread_id, WM_QUIT, 0, 0); - destroy_audio_blob(audio_blob); - return NULL; - } - - dbg1("allocating %d buffers of %d bytes\n", NUM_BUFS, buffer_size); - - for (i = 0; i < NUM_BUFS; i++) { - temp_wave_hdr = PyMem_Malloc(sizeof(WAVEHDR)); - memset(temp_wave_hdr, 0, sizeof(WAVEHDR)); - temp_wave_hdr->lpData = PyMem_Malloc(buffer_size); - temp_wave_hdr->dwBufferLength = buffer_size; - - result = fill_buffer(temp_wave_hdr, audio_blob); - if (result != MMSYSERR_NOERROR) { - waveOutGetErrorText(result, sys_msg_buf, SYS_STR_LEN); - WIN_EXCEPTION("Failed to buffer audio.", result, sys_msg_buf, err_msg_buf); - - PostThreadMessage(thread_id, WM_QUIT, 0, 0); - waveOutUnprepareHeader(audio_blob->handle, temp_wave_hdr, sizeof(WAVEHDR)); - waveOutClose(audio_blob->handle); - destroy_audio_blob(audio_blob); - return NULL; - } - } - - return PyLong_FromUnsignedLongLong(audio_blob->play_list_item->play_id); -} \ No newline at end of file diff --git a/player/src/main/c/windows_mutex.c b/player/src/main/c/windows_mutex.c deleted file mode 100644 index 7822ca3..0000000 --- a/player/src/main/c/windows_mutex.c +++ /dev/null @@ -1,27 +0,0 @@ -/* -Simpleaudio Python Extension -Copyright (C) 2015, Joe Hamilton -MIT License (see LICENSE.txt) -*/ - -#include "player.h" -#include -#include - -void* create_mutex() { - void* mutex; - mutex = (void*)CreateMutex(NULL, FALSE, NULL); - return mutex; -} - -void destroy_mutex(void* mutex) { - CloseHandle((HANDLE)mutex); -} - -void grab_mutex(void* mutex) { - WaitForSingleObject((HANDLE)mutex, INFINITE); -} - -void release_mutex(void* mutex) { - ReleaseMutex((HANDLE)mutex); -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index bbbfa0b..fd5acb4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,5 +24,4 @@ include ':interface' include ':add-on' include ':addonInter' include ':browserCommands' -include ':player' include ':libwave'