Adds local playback via VLC
This commit is contained in:
@@ -25,6 +25,7 @@ dependencies {
|
|||||||
implementation 'org.xerial:sqlite-jdbc:3.36.0.1'
|
implementation 'org.xerial:sqlite-jdbc:3.36.0.1'
|
||||||
implementation 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
implementation 'com.googlecode.soundlibs:jlayer:1.0.1.4'
|
||||||
implementation 'com.mpatric:mp3agic:0.9.1'
|
implementation 'com.mpatric:mp3agic:0.9.1'
|
||||||
|
implementation 'uk.co.caprica:vlcj:4.7.1'
|
||||||
implementation project(":libwave")
|
implementation project(":libwave")
|
||||||
implementation project(":browserCommands")
|
implementation project(":browserCommands")
|
||||||
|
|
||||||
@@ -42,10 +43,5 @@ targetCompatibility = JavaVersion.VERSION_16
|
|||||||
mainClassName = defaultPackage + '.player.Interface'
|
mainClassName = defaultPackage + '.player.Interface'
|
||||||
//mainClassName = defaultPackage + '.localPlayer.Player'
|
//mainClassName = defaultPackage + '.localPlayer.Player'
|
||||||
|
|
||||||
run {
|
|
||||||
systemProperty "java.library.path", file("${project(":player").buildDir}/lib/main/debug").absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
compileJava.dependsOn rootProject.bundleAddOn
|
compileJava.dependsOn rootProject.bundleAddOn
|
||||||
compileJava.dependsOn ':player:linkDebug'
|
|
||||||
compileJava.dependsOn ':addonInter:installAddon'
|
compileJava.dependsOn ':addonInter:installAddon'
|
||||||
@@ -21,8 +21,12 @@ public enum PlaybackStatus
|
|||||||
* The player has a song loaded, but is not playing it.
|
* The player has a song loaded, but is not playing it.
|
||||||
*/
|
*/
|
||||||
STOPPED,
|
STOPPED,
|
||||||
|
/**
|
||||||
|
* The player has is stopped, but just finished a song and may have more.
|
||||||
|
*/
|
||||||
|
FINISHED,
|
||||||
/**
|
/**
|
||||||
* No song is loaded.
|
* No song is loaded.
|
||||||
*/
|
*/
|
||||||
EMPTY;
|
EMPTY
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.browser;
|
package edu.regis.universeplayer.browser;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.PlaybackListener;
|
||||||
|
import edu.regis.universeplayer.PlaybackStatus;
|
||||||
import edu.regis.universeplayer.Player;
|
import edu.regis.universeplayer.Player;
|
||||||
import edu.regis.universeplayer.browserCommands.*;
|
import edu.regis.universeplayer.browserCommands.*;
|
||||||
import edu.regis.universeplayer.data.Song;
|
import edu.regis.universeplayer.data.Song;
|
||||||
@@ -161,7 +163,7 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new ForwardedFuture<Void>(this.sendObject(new CommandLoadSong(song.location)));
|
return new ForwardedFuture(this.sendObject(new CommandLoadSong(song.location)));
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
@@ -197,6 +199,40 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
return null;
|
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
|
@Override
|
||||||
public QueryFuture<Void> play()
|
public QueryFuture<Void> play()
|
||||||
{
|
{
|
||||||
@@ -215,14 +251,28 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playback of the current song.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> stopSong()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> seek(float time)
|
public QueryFuture<Void> seek(float time)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the player's current playback status.
|
||||||
|
*
|
||||||
|
* @return A future for the request.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Boolean> isPaused()
|
public QueryFuture<PlaybackStatus> getStatus()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -241,9 +291,9 @@ public class Browser extends MessageRunner implements Player<InternetSong>
|
|||||||
|
|
||||||
private class ForwardedFuture<T> implements QueryFuture<T>
|
private class ForwardedFuture<T> implements QueryFuture<T>
|
||||||
{
|
{
|
||||||
private final Future future;
|
private final Future<T> future;
|
||||||
|
|
||||||
ForwardedFuture(Future future)
|
ForwardedFuture(Future<T> future)
|
||||||
{
|
{
|
||||||
this.future = future;
|
this.future = future;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
*/
|
*/
|
||||||
public Song skipNext()
|
public Song skipNext()
|
||||||
{
|
{
|
||||||
|
int index = this.currentIndex;
|
||||||
if (++this.currentIndex >= this.queueOrder.size())
|
if (++this.currentIndex >= this.queueOrder.size())
|
||||||
{
|
{
|
||||||
if (this.repeat)
|
if (this.repeat)
|
||||||
@@ -122,7 +123,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
*/
|
*/
|
||||||
this.queueOrder.clear();
|
this.queueOrder.clear();
|
||||||
this.getOrder();
|
this.getOrder();
|
||||||
|
if (this.currentIndex != index)
|
||||||
|
{
|
||||||
this.triggerSongChangeListeners();
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return this.getCurrentSong();
|
return this.getCurrentSong();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -131,11 +135,17 @@ public class Queue extends ArrayList<Song>
|
|||||||
* Make sure we aren't infinitely increasing the current index.
|
* Make sure we aren't infinitely increasing the current index.
|
||||||
*/
|
*/
|
||||||
this.currentIndex = this.queueOrder.size() - 1;
|
this.currentIndex = this.queueOrder.size() - 1;
|
||||||
|
if (this.currentIndex != index)
|
||||||
|
{
|
||||||
this.triggerSongChangeListeners();
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.currentIndex != index)
|
||||||
|
{
|
||||||
this.triggerSongChangeListeners();
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return this.getCurrentSong();
|
return this.getCurrentSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +156,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
*/
|
*/
|
||||||
public Song skipPrev()
|
public Song skipPrev()
|
||||||
{
|
{
|
||||||
|
int index = this.currentIndex;
|
||||||
if (--this.currentIndex < 0)
|
if (--this.currentIndex < 0)
|
||||||
{
|
{
|
||||||
if (this.repeat)
|
if (this.repeat)
|
||||||
@@ -157,7 +168,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.currentIndex = 0;
|
this.currentIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.currentIndex != index)
|
||||||
|
{
|
||||||
this.triggerSongChangeListeners();
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return this.getCurrentSong();
|
return this.getCurrentSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +184,12 @@ public class Queue extends ArrayList<Song>
|
|||||||
public Song skipToSong(int index)
|
public Song skipToSong(int index)
|
||||||
{
|
{
|
||||||
Song song = this.get(index);
|
Song song = this.get(index);
|
||||||
this.currentIndex = this.queueOrder.indexOf(song);
|
index = this.queueOrder.indexOf(song);
|
||||||
|
if (this.currentIndex != index)
|
||||||
|
{
|
||||||
|
this.currentIndex = index;
|
||||||
this.triggerSongChangeListeners();
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return this.getCurrentSong();
|
return this.getCurrentSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +215,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
@Override
|
@Override
|
||||||
public boolean add(Song song)
|
public boolean add(Song song)
|
||||||
{
|
{
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
int newIndex;
|
int newIndex;
|
||||||
if (super.add(song))
|
if (super.add(song))
|
||||||
{
|
{
|
||||||
@@ -214,6 +233,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.queueOrder.add(song);
|
this.queueOrder.add(song);
|
||||||
}
|
}
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -222,6 +245,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
@Override
|
@Override
|
||||||
public void add(int index, Song song)
|
public void add(int index, Song song)
|
||||||
{
|
{
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
int newIndex;
|
int newIndex;
|
||||||
super.add(index, song);
|
super.add(index, song);
|
||||||
if (this.shuffle)
|
if (this.shuffle)
|
||||||
@@ -237,6 +261,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.currentIndex++;
|
this.currentIndex++;
|
||||||
}
|
}
|
||||||
this.queueOrder.add(newIndex, song);
|
this.queueOrder.add(newIndex, song);
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +272,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
public Song remove(int index)
|
public Song remove(int index)
|
||||||
{
|
{
|
||||||
Song removed = super.remove(index);
|
Song removed = super.remove(index);
|
||||||
|
boolean current = this.getCurrentSong() == removed;
|
||||||
if (removed != null)
|
if (removed != null)
|
||||||
{
|
{
|
||||||
index = this.queueOrder.indexOf(removed);
|
index = this.queueOrder.indexOf(removed);
|
||||||
@@ -253,6 +282,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
}
|
}
|
||||||
this.queueOrder.remove(removed);
|
this.queueOrder.remove(removed);
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (current)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
@@ -261,6 +294,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
public boolean remove(Object o)
|
public boolean remove(Object o)
|
||||||
{
|
{
|
||||||
int index = this.indexOf(o);
|
int index = this.indexOf(o);
|
||||||
|
boolean removed = this.getCurrentSong() == o;
|
||||||
if (super.remove(o))
|
if (super.remove(o))
|
||||||
{
|
{
|
||||||
if (index <= this.currentIndex)
|
if (index <= this.currentIndex)
|
||||||
@@ -269,6 +303,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
}
|
}
|
||||||
this.queueOrder.remove(o);
|
this.queueOrder.remove(o);
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -289,6 +327,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
{
|
{
|
||||||
int newIndex;
|
int newIndex;
|
||||||
int length = this.size();
|
int length = this.size();
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
if (super.addAll(c))
|
if (super.addAll(c))
|
||||||
{
|
{
|
||||||
for (Song song : c)
|
for (Song song : c)
|
||||||
@@ -308,6 +347,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -318,6 +361,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
{
|
{
|
||||||
int newIndex;
|
int newIndex;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
if (super.addAll(index, c))
|
if (super.addAll(index, c))
|
||||||
{
|
{
|
||||||
for (Song song : c)
|
for (Song song : c)
|
||||||
@@ -337,6 +381,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.queueOrder.add(newIndex, song);
|
this.queueOrder.add(newIndex, song);
|
||||||
}
|
}
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -345,6 +393,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
@Override
|
@Override
|
||||||
protected void removeRange(int fromIndex, int toIndex)
|
protected void removeRange(int fromIndex, int toIndex)
|
||||||
{
|
{
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
for (int i = fromIndex; i < toIndex; i++)
|
for (int i = fromIndex; i < toIndex; i++)
|
||||||
{
|
{
|
||||||
this.queueOrder.remove(this.get(i));
|
this.queueOrder.remove(this.get(i));
|
||||||
@@ -355,12 +404,17 @@ public class Queue extends ArrayList<Song>
|
|||||||
}
|
}
|
||||||
super.removeRange(fromIndex, toIndex);
|
super.removeRange(fromIndex, toIndex);
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeAll(Collection<?> c)
|
public boolean removeAll(Collection<?> c)
|
||||||
{
|
{
|
||||||
boolean success;
|
boolean success;
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
||||||
{
|
{
|
||||||
if (c.contains(this.get(i)))
|
if (c.contains(this.get(i)))
|
||||||
@@ -371,6 +425,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.queueOrder.removeAll(c);
|
this.queueOrder.removeAll(c);
|
||||||
success = super.removeAll(c);
|
success = super.removeAll(c);
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,6 +436,7 @@ public class Queue extends ArrayList<Song>
|
|||||||
public boolean retainAll(Collection<?> c)
|
public boolean retainAll(Collection<?> c)
|
||||||
{
|
{
|
||||||
boolean success;
|
boolean success;
|
||||||
|
Song oldSong = this.getCurrentSong();
|
||||||
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
for (int i = 0, removed = 0; i <= this.currentIndex && i < this.size(); i++)
|
||||||
{
|
{
|
||||||
if (!c.contains(this.get(i)))
|
if (!c.contains(this.get(i)))
|
||||||
@@ -388,6 +447,10 @@ public class Queue extends ArrayList<Song>
|
|||||||
this.queueOrder.retainAll(c);
|
this.queueOrder.retainAll(c);
|
||||||
success = super.retainAll(c);
|
success = super.retainAll(c);
|
||||||
this.triggerQueueChangeListeners();
|
this.triggerQueueChangeListeners();
|
||||||
|
if (this.getCurrentSong() != oldSong)
|
||||||
|
{
|
||||||
|
this.triggerSongChangeListeners();
|
||||||
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package edu.regis.universeplayer.localPlayer;
|
package edu.regis.universeplayer.localPlayer;
|
||||||
|
|
||||||
import com.intervigil.wave.WaveReader;
|
import com.intervigil.wave.WaveReader;
|
||||||
|
|
||||||
import edu.regis.universeplayer.PlaybackListener;
|
import edu.regis.universeplayer.PlaybackListener;
|
||||||
import edu.regis.universeplayer.PlaybackStatus;
|
import edu.regis.universeplayer.PlaybackStatus;
|
||||||
import edu.regis.universeplayer.Player;
|
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.browserCommands.QueryFuture;
|
||||||
import edu.regis.universeplayer.data.LocalSong;
|
import edu.regis.universeplayer.data.LocalSong;
|
||||||
import edu.regis.universeplayer.data.Song;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -28,14 +39,14 @@ import java.util.concurrent.*;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public class LocalPlayer implements Player<LocalSong>
|
public class LocalPlayer implements Player<LocalSong>, MediaPlayerEventListener
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class);
|
private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class);
|
||||||
|
|
||||||
static
|
private final MediaPlayerFactory playerFactory;
|
||||||
{
|
private final AudioPlayerComponent player;
|
||||||
System.loadLibrary("player");
|
|
||||||
}
|
private final LinkedList<PlaybackListener> listeners = new LinkedList<>();
|
||||||
|
|
||||||
private int currentId;
|
private int currentId;
|
||||||
|
|
||||||
@@ -44,6 +55,13 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
|
|
||||||
private final ExecutorService service = Executors.newSingleThreadExecutor();
|
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.
|
* Sets the file currently being used.
|
||||||
*
|
*
|
||||||
@@ -54,28 +72,8 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
WaveReader header = file.getHeader();
|
WaveReader header = file.getHeader();
|
||||||
this.currentFile = file;
|
this.currentFile = file;
|
||||||
// TODO - Ensure that bytes per sample and bits per sample don't bother things
|
// 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.
|
* Saves the current file as a WAVE file somewhere else. This method is meant for testing only.
|
||||||
*
|
*
|
||||||
@@ -119,115 +117,110 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> loadSong(LocalSong song)
|
public QueryFuture<Void> loadSong(LocalSong song)
|
||||||
{
|
{
|
||||||
if (this.currentSong == null)
|
if (this.currentSong != null)
|
||||||
{
|
{
|
||||||
this.stopSong();
|
this.stopSong();
|
||||||
}
|
}
|
||||||
this.currentSong = song;
|
this.currentSong = song;
|
||||||
Future<Void> runnable = service.submit(() -> {
|
this.player.mediaPlayer().media().play(song.file.getAbsolutePath());
|
||||||
try
|
return new NullFuture<>();
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> play()
|
public QueryFuture<Void> play()
|
||||||
{
|
{
|
||||||
return null;
|
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().play());
|
||||||
|
return new NullFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> pause()
|
public QueryFuture<Void> pause()
|
||||||
{
|
{
|
||||||
return null;
|
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().pause());
|
||||||
|
return new NullFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> togglePlayback()
|
public QueryFuture<Void> togglePlayback()
|
||||||
{
|
{
|
||||||
return null;
|
if (this.player.mediaPlayer().status().isPlaying())
|
||||||
|
{
|
||||||
|
return this.pause();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> stopSong()
|
public QueryFuture<Void> stopSong()
|
||||||
{
|
{
|
||||||
return null;
|
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls().stop());
|
||||||
}
|
return new NullFuture<>();
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addPlaybackListener(PlaybackListener listener)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> seek(float time)
|
public QueryFuture<Void> seek(float time)
|
||||||
{
|
{
|
||||||
return null;
|
this.player.mediaPlayer().submit((WrappedRunnable) () -> this.player.mediaPlayer().controls()
|
||||||
|
.setTime((long) (time * 1000)));
|
||||||
|
return new NullFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<PlaybackStatus> getStatus()
|
public QueryFuture<PlaybackStatus> 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
|
@Override
|
||||||
public QueryFuture<Float> getCurrentTime()
|
public QueryFuture<Float> getCurrentTime()
|
||||||
{
|
{
|
||||||
return null;
|
return new NullFuture<>(this.player.mediaPlayer().status().time() / 1000F);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Float> getLength()
|
public QueryFuture<Float> getLength()
|
||||||
{
|
{
|
||||||
return null;
|
return new NullFuture<>(this.player.mediaPlayer().status().length() / 1000F);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryFuture<Void> close()
|
public QueryFuture<Void> close()
|
||||||
{
|
{
|
||||||
return null;
|
this.player.release();
|
||||||
|
return new NullFuture<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Plays the current audio file.
|
public void addPlaybackListener(PlaybackListener listener)
|
||||||
*/
|
{
|
||||||
public native void playSong();
|
if (!this.hasPlaybackListener(listener))
|
||||||
|
{
|
||||||
/**
|
this.listeners.add(listener);
|
||||||
* 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
|
@Override
|
||||||
public boolean hasPlaybackListener(PlaybackListener listener)
|
public boolean hasPlaybackListener(PlaybackListener listener)
|
||||||
{
|
{
|
||||||
return false;
|
return this.listeners.contains(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePlaybackListener(PlaybackListener listener)
|
public void removePlaybackListener(PlaybackListener listener)
|
||||||
{
|
{
|
||||||
|
this.listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,35 +261,366 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
args.add("-f");
|
args.add("-f");
|
||||||
args.add("wav");
|
args.add("wav");
|
||||||
args.add("pipe:1");
|
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)
|
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)
|
|
||||||
|
/**
|
||||||
|
* 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.error("Could not save audio file " + file, e);
|
logger.debug("New media {} loaded", media);
|
||||||
}
|
}
|
||||||
// Compare JNI vs Java
|
|
||||||
try (AudioFile stream = getAudioStream(new File(args[0])))
|
/**
|
||||||
|
* Opening the media.
|
||||||
|
*
|
||||||
|
* @param mediaPlayer media player that raised the event
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void opening(MediaPlayer mediaPlayer)
|
||||||
{
|
{
|
||||||
LocalPlayer player = new LocalPlayer();
|
|
||||||
player.setCurrentFile(stream);
|
|
||||||
player.saveJava(args[0] + ".orig.wav");
|
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
{
|
{
|
||||||
logger.error("Could not save audio file " + file, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media started playing.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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 <code>true</code> if corked; otherwise <code>false</code>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void corked(MediaPlayer mediaPlayer, boolean corked)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The audio was muted/un-muted.
|
||||||
|
*
|
||||||
|
* @param mediaPlayer media player that raised the event
|
||||||
|
* @param muted <code>true</code> if muted; otherwise <code>false</code>
|
||||||
|
*/
|
||||||
|
@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.
|
||||||
|
* <p>
|
||||||
|
* The implementation will fire this event once on receipt of the first
|
||||||
|
* native position-changed event with a position value greater than zero.
|
||||||
|
* <p>
|
||||||
|
* The event will be fired again if the media is played again after a native
|
||||||
|
* stopped or finished event is received.
|
||||||
|
* <p>
|
||||||
|
* 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<T> implements QueryFuture<T>
|
private static class WrappedFuture<T> implements QueryFuture<T>
|
||||||
@@ -309,7 +633,7 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
|
public CommandConfirmation getConfirmation() throws CancellationException, InterruptedException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -323,7 +647,7 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -366,4 +690,78 @@ public class LocalPlayer implements Player<LocalSong>
|
|||||||
return this.runnable.get(timeout, unit);
|
return this.runnable.get(timeout, unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NullFuture<T> implements QueryFuture<T>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import edu.regis.universeplayer.Player;
|
|||||||
import edu.regis.universeplayer.browser.Browser;
|
import edu.regis.universeplayer.browser.Browser;
|
||||||
import edu.regis.universeplayer.data.Queue;
|
import edu.regis.universeplayer.data.Queue;
|
||||||
import edu.regis.universeplayer.data.*;
|
import edu.regis.universeplayer.data.*;
|
||||||
|
import edu.regis.universeplayer.localPlayer.LocalPlayer;
|
||||||
|
|
||||||
import net.harawata.appdirs.AppDirsFactory;
|
import net.harawata.appdirs.AppDirsFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -29,7 +31,7 @@ import java.util.concurrent.Future;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @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);
|
private static final Logger logger = LoggerFactory.getLogger(Interface.class);
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
SongProvider.INSTANCE.addUpdateListener(inter);
|
SongProvider.INSTANCE.addUpdateListener(inter);
|
||||||
inter.setVisible(true);
|
inter.setVisible(true);
|
||||||
|
|
||||||
|
Player.REGISTERED_PLAYERS.put(LocalSong.class, new LocalPlayer());
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -189,7 +192,6 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
|
|
||||||
this.controls = new PlayerControls();
|
this.controls = new PlayerControls();
|
||||||
this.controls.addFocusListener(this);
|
this.controls.addFocusListener(this);
|
||||||
controls.addCommandListener(this);
|
|
||||||
this.getContentPane().add(controls, BorderLayout.PAGE_END);
|
this.getContentPane().add(controls, BorderLayout.PAGE_END);
|
||||||
|
|
||||||
this.queueList = new QueueList();
|
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
|
@Override
|
||||||
public void onUpdate(int updated, int totalUpdate, String updating)
|
public void onUpdate(int updated, int totalUpdate, String updating)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.FocusAdapter;
|
import java.awt.event.FocusAdapter;
|
||||||
import java.awt.event.FocusEvent;
|
import java.awt.event.FocusEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
@@ -41,7 +43,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
private final JButton playButton;
|
private final JButton playButton;
|
||||||
private final JButton nextButton;
|
private final JButton nextButton;
|
||||||
private final JButton prevButton;
|
private final JButton prevButton;
|
||||||
private final JSlider progress;
|
private final JProgressBar progress;
|
||||||
private final JProgressBar updateProgress;
|
private final JProgressBar updateProgress;
|
||||||
|
|
||||||
private final ForkJoinPool service = new ForkJoinPool();
|
private final ForkJoinPool service = new ForkJoinPool();
|
||||||
@@ -100,8 +102,16 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
progressCont = new JPanel(progressLayout);
|
progressCont = new JPanel(progressLayout);
|
||||||
this.add(progressCont);
|
this.add(progressCont);
|
||||||
|
|
||||||
this.progress = new JSlider();
|
this.progress = new JProgressBar();
|
||||||
this.progress.addChangeListener(changeEvent -> this.seek(((JSlider) changeEvent.getSource()).getValue()));
|
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.add(this.progress);
|
||||||
|
|
||||||
this.updateProgress = new JProgressBar();
|
this.updateProgress = new JProgressBar();
|
||||||
@@ -143,13 +153,14 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress);
|
layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress);
|
||||||
layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this);
|
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.EAST, this, 5, SpringLayout.EAST, this.updateProgress);
|
||||||
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
|
layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress);
|
||||||
|
|
||||||
Queue.getInstance().addSongChangeListener(this);
|
Queue.getInstance().addSongChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seek(int value)
|
private void seek(float value)
|
||||||
{
|
{
|
||||||
this.service.execute(() -> {
|
this.service.execute(() -> {
|
||||||
if (this.currentPlayer != null)
|
if (this.currentPlayer != null)
|
||||||
@@ -172,12 +183,8 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
{
|
{
|
||||||
switch ((PlaybackStatus) this.currentPlayer.getStatus().get())
|
switch ((PlaybackStatus) this.currentPlayer.getStatus().get())
|
||||||
{
|
{
|
||||||
case PAUSED -> {
|
case PAUSED -> this.currentPlayer.play();
|
||||||
this.currentPlayer.play();
|
case PLAYING -> this.currentPlayer.pause();
|
||||||
}
|
|
||||||
case PLAYING -> {
|
|
||||||
this.currentPlayer.pause();
|
|
||||||
}
|
|
||||||
case STOPPED, EMPTY -> {
|
case STOPPED, EMPTY -> {
|
||||||
if (Queue.getInstance().size() > 0)
|
if (Queue.getInstance().size() > 0)
|
||||||
{
|
{
|
||||||
@@ -291,14 +298,15 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
if (this.currentSong != null)
|
if (this.currentSong != null)
|
||||||
{
|
{
|
||||||
this.currentPlayer = Player.REGISTERED_PLAYERS.get(this.currentSong.getClass());
|
this.currentPlayer = Player.REGISTERED_PLAYERS.get(this.currentSong.getClass());
|
||||||
|
this.progress.setMaximum((int) (this.currentSong.duration / 1000));
|
||||||
|
if (this.currentPlayer != null)
|
||||||
|
{
|
||||||
if (!this.currentPlayer.hasPlaybackListener(this))
|
if (!this.currentPlayer.hasPlaybackListener(this))
|
||||||
{
|
{
|
||||||
this.currentPlayer.addPlaybackListener(this);
|
this.currentPlayer.addPlaybackListener(this);
|
||||||
}
|
}
|
||||||
this.progress.setMaximum((int) (this.currentSong.duration / 1000));
|
this.currentPlayer.loadSong(this.currentSong);
|
||||||
if (this.currentPlayer != null)
|
// this.currentPlayer.play();
|
||||||
{
|
|
||||||
this.currentPlayer.play();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -320,6 +328,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener,
|
|||||||
switch (status.getStatus())
|
switch (status.getStatus())
|
||||||
{
|
{
|
||||||
case PLAYING -> this.playButton.setIcon(PAUSE_ICON);
|
case PLAYING -> this.playButton.setIcon(PAUSE_ICON);
|
||||||
|
case FINISHED -> Queue.getInstance().skipNext();
|
||||||
case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON);
|
case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON);
|
||||||
}
|
}
|
||||||
this.progress.setValue((int) status.getPlayTime());
|
this.progress.setValue((int) status.getPlayTime());
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<Root level="info">
|
<Root level="info">
|
||||||
<AppenderRef ref="File"/>
|
<AppenderRef ref="File"/>
|
||||||
</Root>
|
</Root>
|
||||||
<Logger name="edu.regis.universeplayer.data.LocalSongProvider" level="debug">
|
<Logger name="edu.regis.universeplayer.localPlayer.LocalPlayer" level="debug">
|
||||||
<AppenderRef ref="Console"/>
|
|
||||||
</Logger>
|
|
||||||
<Logger name="edu.regis.universeplayer.player.Interface" level="debug">
|
|
||||||
<AppenderRef ref="Console"/>
|
<AppenderRef ref="Console"/>
|
||||||
</Logger>
|
</Logger>
|
||||||
|
<!-- <Logger name="edu.regis.universeplayer.player.Interface" level="debug">-->
|
||||||
|
<!-- <AppenderRef ref="Console"/>-->
|
||||||
|
<!-- </Logger>-->
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
@@ -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"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#ifndef INTERFACE_H
|
|
||||||
#define INTERFACE_H
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
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 */
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
Simpleaudio Python Extension
|
|
||||||
Copyright (C) 2015, Joe Hamilton
|
|
||||||
MIT License (see LICENSE.txt)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SIMPLEAUDIO_H
|
|
||||||
#define SIMPLEAUDIO_H
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#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 */
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
#include <jni.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
|
||||||
{
|
|
||||||
printf("Updating File\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
Simpleaudio Python Extension
|
|
||||||
Copyright (C) 2015, Joe Hamilton
|
|
||||||
MIT License (see LICENSE.txt)
|
|
||||||
*/
|
|
||||||
#include "simpleaudio.h"
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <mmreg.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Simpleaudio Python Extension
|
|
||||||
Copyright (C) 2015, Joe Hamilton
|
|
||||||
MIT License (see LICENSE.txt)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "player.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -24,5 +24,4 @@ include ':interface'
|
|||||||
include ':add-on'
|
include ':add-on'
|
||||||
include ':addonInter'
|
include ':addonInter'
|
||||||
include ':browserCommands'
|
include ':browserCommands'
|
||||||
include ':player'
|
|
||||||
include ':libwave'
|
include ':libwave'
|
||||||
|
|||||||
Reference in New Issue
Block a user