Some more work with playback.
WARNING: UNSTABLE
This commit is contained in:
@@ -17,7 +17,7 @@ public class CommandConfirmation implements Serializable
|
|||||||
/**
|
/**
|
||||||
* The error returned by the browser, or null if execution was successful.
|
* The error returned by the browser, or null if execution was successful.
|
||||||
*/
|
*/
|
||||||
private BrowserError errorCode;
|
private Throwable errorCode;
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +44,7 @@ public class CommandConfirmation implements Serializable
|
|||||||
*
|
*
|
||||||
* @param error - The error thrown, or null if the command was successful.
|
* @param error - The error thrown, or null if the command was successful.
|
||||||
*/
|
*/
|
||||||
public CommandConfirmation(BrowserError error)
|
public CommandConfirmation(Throwable error)
|
||||||
{
|
{
|
||||||
this.errorCode = error;
|
this.errorCode = error;
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ public class CommandConfirmation implements Serializable
|
|||||||
*
|
*
|
||||||
* @return The error, or null if the command was successful.
|
* @return The error, or null if the command was successful.
|
||||||
*/
|
*/
|
||||||
public BrowserError getError()
|
public Throwable getError()
|
||||||
{
|
{
|
||||||
return this.errorCode;
|
return this.errorCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.data.Song;
|
||||||
|
|
||||||
|
import java.util.EventListener;
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A playback listener allows a class to listen for events regarding player
|
||||||
|
* updates.
|
||||||
|
*
|
||||||
|
* @author William Hubbard
|
||||||
|
* @verison 0.1
|
||||||
|
*/
|
||||||
|
public interface PlaybackListener extends EventListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called when playback is changed.
|
||||||
|
*
|
||||||
|
* @param status - The playback status.
|
||||||
|
*/
|
||||||
|
void onPlaybackChanged(PlaybackInfo status);
|
||||||
|
|
||||||
|
class PlaybackInfo extends EventObject
|
||||||
|
{
|
||||||
|
private final Song currentSong;
|
||||||
|
/**
|
||||||
|
* The time we are currently at in the song, in seconds.
|
||||||
|
*/
|
||||||
|
private final float playTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current status of the player.
|
||||||
|
*/
|
||||||
|
private final PlaybackStatus status;
|
||||||
|
|
||||||
|
public PlaybackInfo(Player<? extends Song> player, Song song, float playTime, PlaybackStatus status)
|
||||||
|
{
|
||||||
|
super(player);
|
||||||
|
this.currentSong = song;
|
||||||
|
this.playTime = playTime;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player<? extends Song> getSource()
|
||||||
|
{
|
||||||
|
return (Player<? extends Song>) super.getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the song currently playing.
|
||||||
|
*
|
||||||
|
* @return The current song, or null if none is loaded.
|
||||||
|
*/
|
||||||
|
public Song getSong()
|
||||||
|
{
|
||||||
|
return this.currentSong;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the current play time.
|
||||||
|
*
|
||||||
|
* @return The play time of the player. This will be zero if the song is
|
||||||
|
* stopped or not loaded.
|
||||||
|
*/
|
||||||
|
public float getPlayTime()
|
||||||
|
{
|
||||||
|
return this.playTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the current status of the player.
|
||||||
|
*
|
||||||
|
* @return - The player's playback status.
|
||||||
|
*/
|
||||||
|
public PlaybackStatus getStatus()
|
||||||
|
{
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This contains possible states that a player can be in.
|
||||||
|
*/
|
||||||
|
public enum PlaybackStatus
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The player is currently playing a song.
|
||||||
|
*/
|
||||||
|
PLAYING,
|
||||||
|
/**
|
||||||
|
* The player has been paused.
|
||||||
|
*/
|
||||||
|
PAUSED,
|
||||||
|
/**
|
||||||
|
* The player has a song loaded, but is not playing it.
|
||||||
|
*/
|
||||||
|
STOPPED,
|
||||||
|
/**
|
||||||
|
* No song is loaded.
|
||||||
|
*/
|
||||||
|
EMPTY;
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ package edu.regis.universeplayer;
|
|||||||
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
import edu.regis.universeplayer.browserCommands.QueryFuture;
|
||||||
import edu.regis.universeplayer.data.Song;
|
import edu.regis.universeplayer.data.Song;
|
||||||
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface serves as the connection to a music player of some sort, whether it be
|
* This interface serves as the connection to a music player of some sort, whether it be
|
||||||
@@ -19,6 +19,31 @@ import java.util.concurrent.Future;
|
|||||||
*/
|
*/
|
||||||
public interface Player<T extends Song>
|
public interface Player<T extends Song>
|
||||||
{
|
{
|
||||||
|
HashMap<Class<? extends Song>, Player<?>> REGISTERED_PLAYERS = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains a song player that can play the provided song.
|
||||||
|
*
|
||||||
|
* @param song - The song to play.
|
||||||
|
* @return A compatible player, or null if none is found.
|
||||||
|
*/
|
||||||
|
static Player<?> getCompatiblePlayer(Song song)
|
||||||
|
{
|
||||||
|
Player<?> player = REGISTERED_PLAYERS.get(song.getClass());
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
for (Class<? extends Song> songClass : REGISTERED_PLAYERS.keySet())
|
||||||
|
{
|
||||||
|
if (songClass.isAssignableFrom(song.getClass()))
|
||||||
|
{
|
||||||
|
player = REGISTERED_PLAYERS.get(songClass);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the song currently playing.
|
* Obtains the song currently playing.
|
||||||
*
|
*
|
||||||
@@ -36,22 +61,30 @@ public interface Player<T extends Song>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables playback of the current song, if one is active.
|
* Enables playback of the current song, if one is active.
|
||||||
|
*
|
||||||
* @return A confirmation of whether the command was successful or not.
|
* @return A confirmation of whether the command was successful or not.
|
||||||
*/
|
*/
|
||||||
QueryFuture<Void> play();
|
QueryFuture<Void> play();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses playback of the current song.
|
* Pauses playback of the current song.
|
||||||
|
*
|
||||||
* @return A confirmation of whether the command was successful or not.
|
* @return A confirmation of whether the command was successful or not.
|
||||||
*/
|
*/
|
||||||
QueryFuture<Void> pause();
|
QueryFuture<Void> pause();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles between playing and pausing the current song.
|
* Toggles between playing and pausing the current song.
|
||||||
|
*
|
||||||
* @return A confirmation of whether the command was successful or not.
|
* @return A confirmation of whether the command was successful or not.
|
||||||
*/
|
*/
|
||||||
QueryFuture<Void> togglePlayback();
|
QueryFuture<Void> togglePlayback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops playback of the current song.
|
||||||
|
*/
|
||||||
|
QueryFuture<Void> stopSong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the current song time to the specified position.
|
* Sets the current song time to the specified position.
|
||||||
*
|
*
|
||||||
@@ -61,11 +94,10 @@ public interface Player<T extends Song>
|
|||||||
QueryFuture<Void> seek(float time);
|
QueryFuture<Void> seek(float time);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if the song is paused.
|
* Obtains the player's current playback status.
|
||||||
*
|
* @return A future for the request.
|
||||||
* @return Whether or not the song is paused.
|
|
||||||
*/
|
*/
|
||||||
QueryFuture<Boolean> isPaused();
|
QueryFuture<PlaybackStatus> getStatus();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the time we are currently at in the current song.
|
* Obtains the time we are currently at in the current song.
|
||||||
@@ -83,7 +115,30 @@ public interface Player<T extends Song>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the player.
|
* Closes the player.
|
||||||
|
*
|
||||||
* @return A confirmation of whether the player was successfully closed.
|
* @return A confirmation of whether the player was successfully closed.
|
||||||
*/
|
*/
|
||||||
QueryFuture<Void> close();
|
QueryFuture<Void> close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener for playback status updates.
|
||||||
|
*
|
||||||
|
* @param listener - The listener to add.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
boolean hasPlaybackListener(PlaybackListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener for playback status updates.
|
||||||
|
*
|
||||||
|
* @param listener - The listener to remove.
|
||||||
|
*/
|
||||||
|
void removePlaybackListener(PlaybackListener listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package edu.regis.universeplayer.browser;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.data.Song;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class InternetSong extends Song
|
||||||
|
{
|
||||||
|
public URL location;
|
||||||
|
}
|
||||||
@@ -4,6 +4,13 @@
|
|||||||
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.PlaybackStatus;
|
||||||
|
import edu.regis.universeplayer.Player;
|
||||||
|
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 org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -13,6 +20,7 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This allows for the control of playback of files on the local file system
|
* This allows for the control of playback of files on the local file system
|
||||||
@@ -20,9 +28,9 @@ import java.util.LinkedList;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public class Player
|
public class LocalPlayer implements Player<LocalSong>
|
||||||
{
|
{
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Player.class);
|
private static final Logger logger = LoggerFactory.getLogger(LocalPlayer.class);
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
@@ -31,8 +39,11 @@ public class Player
|
|||||||
|
|
||||||
private int currentId;
|
private int currentId;
|
||||||
|
|
||||||
|
private LocalSong currentSong;
|
||||||
private AudioFile currentFile;
|
private AudioFile currentFile;
|
||||||
|
|
||||||
|
private final ExecutorService service = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file currently being used.
|
* Sets the file currently being used.
|
||||||
*
|
*
|
||||||
@@ -99,22 +110,125 @@ public class Player
|
|||||||
return this.currentFile;
|
return this.currentFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Song getCurrentSong()
|
||||||
|
{
|
||||||
|
return this.currentSong;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> loadSong(LocalSong song)
|
||||||
|
{
|
||||||
|
if (this.currentSong == null)
|
||||||
|
{
|
||||||
|
this.stopSong();
|
||||||
|
}
|
||||||
|
this.currentSong = song;
|
||||||
|
Future<Void> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> play()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> pause()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> togglePlayback()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> stopSong()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addPlaybackListener(PlaybackListener listener)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> seek(float time)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<PlaybackStatus> getStatus()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Float> getCurrentTime()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Float> getLength()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public QueryFuture<Void> close()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the current audio file.
|
* Plays the current audio file.
|
||||||
*/
|
*/
|
||||||
public native void play();
|
public native void playSong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses playback for the current audio file.
|
* Pauses playback for the current audio file.
|
||||||
*/
|
*/
|
||||||
public native void pause();
|
public native void pauseSong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see whether the current audio file is paused.
|
* 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.
|
* @return True if there is an audio file present and it is paused, false otherwise.
|
||||||
*/
|
*/
|
||||||
public native boolean isPaused();
|
public native boolean isSongPaused();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPlaybackListener(PlaybackListener listener)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removePlaybackListener(PlaybackListener listener)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains an input stream for the requested file.
|
* Obtains an input stream for the requested file.
|
||||||
@@ -164,7 +278,7 @@ public class Player
|
|||||||
File file = new File(args[0]);
|
File file = new File(args[0]);
|
||||||
try (AudioFile stream = getAudioStream(new File(args[0])))
|
try (AudioFile stream = getAudioStream(new File(args[0])))
|
||||||
{
|
{
|
||||||
Player player = new Player();
|
LocalPlayer player = new LocalPlayer();
|
||||||
player.setCurrentFile(stream);
|
player.setCurrentFile(stream);
|
||||||
player.save(args[0] + ".wav");
|
player.save(args[0] + ".wav");
|
||||||
}
|
}
|
||||||
@@ -175,7 +289,7 @@ public class Player
|
|||||||
// Compare JNI vs Java
|
// Compare JNI vs Java
|
||||||
try (AudioFile stream = getAudioStream(new File(args[0])))
|
try (AudioFile stream = getAudioStream(new File(args[0])))
|
||||||
{
|
{
|
||||||
Player player = new Player();
|
LocalPlayer player = new LocalPlayer();
|
||||||
player.setCurrentFile(stream);
|
player.setCurrentFile(stream);
|
||||||
player.saveJava(args[0] + ".orig.wav");
|
player.saveJava(args[0] + ".orig.wav");
|
||||||
}
|
}
|
||||||
@@ -184,4 +298,72 @@ public class Player
|
|||||||
logger.error("Could not save audio file " + file, e);
|
logger.error("Could not save audio file " + file, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class WrappedFuture<T> implements QueryFuture<T>
|
||||||
|
{
|
||||||
|
private final Future<T> runnable;
|
||||||
|
|
||||||
|
WrappedFuture(Future<T> runnable)
|
||||||
|
{
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.runnable.get();
|
||||||
|
return new CommandConfirmation();
|
||||||
|
}
|
||||||
|
catch (ExecutionException e)
|
||||||
|
{
|
||||||
|
return new CommandConfirmation(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.runnable.get(timeout, unit);
|
||||||
|
return new CommandConfirmation();
|
||||||
|
}
|
||||||
|
catch (ExecutionException e)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -345,7 +345,9 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL
|
|||||||
}
|
}
|
||||||
case PAUSE -> {
|
case PAUSE -> {
|
||||||
}
|
}
|
||||||
case NEXT -> Queue.getInstance().skipNext();
|
case NEXT -> {
|
||||||
|
|
||||||
|
}
|
||||||
case PREVIOUS -> Queue.getInstance().skipPrev();
|
case PREVIOUS -> Queue.getInstance().skipPrev();
|
||||||
case SEEK -> {
|
case SEEK -> {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,20 @@
|
|||||||
|
|
||||||
package edu.regis.universeplayer.player;
|
package edu.regis.universeplayer.player;
|
||||||
|
|
||||||
|
import edu.regis.universeplayer.PlaybackListener;
|
||||||
|
import edu.regis.universeplayer.PlaybackStatus;
|
||||||
|
import edu.regis.universeplayer.Player;
|
||||||
|
import edu.regis.universeplayer.data.Queue;
|
||||||
|
import edu.regis.universeplayer.data.Song;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
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.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -17,14 +27,25 @@ import javax.swing.*;
|
|||||||
* @author William Hubbard
|
* @author William Hubbard
|
||||||
* @version 0.1
|
* @version 0.1
|
||||||
*/
|
*/
|
||||||
public class PlayerControls extends JPanel
|
public class PlayerControls extends JPanel implements Queue.SongChangeListener, PlaybackListener
|
||||||
{
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PlayerControls.class);
|
||||||
|
private final ImageIcon PLAY_ICON, PAUSE_ICON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to the song currently playing.
|
||||||
|
*/
|
||||||
|
private Song currentSong = null;
|
||||||
|
private Player currentPlayer = null;
|
||||||
|
|
||||||
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 JSlider progress;
|
||||||
private final JProgressBar updateProgress;
|
private final JProgressBar updateProgress;
|
||||||
|
|
||||||
|
private final ForkJoinPool service = new ForkJoinPool();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all things interested in knowing when we trigger a command.
|
* A list of all things interested in knowing when we trigger a command.
|
||||||
*/
|
*/
|
||||||
@@ -54,15 +75,17 @@ public class PlayerControls extends JPanel
|
|||||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||||
this.prevButton.setIcon(icon);
|
this.prevButton.setIcon(icon);
|
||||||
this.prevButton.setPreferredSize(BUTTON_SIZE);
|
this.prevButton.setPreferredSize(BUTTON_SIZE);
|
||||||
this.prevButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null));
|
this.prevButton.addActionListener(actionEvent -> this.previousSong());
|
||||||
buttonCont.add(this.prevButton);
|
buttonCont.add(this.prevButton);
|
||||||
|
|
||||||
this.playButton = new JButton();
|
this.playButton = new JButton();
|
||||||
icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button");
|
PAUSE_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/pause.png"), "Pause Button");
|
||||||
|
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||||
|
PLAY_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button");
|
||||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||||
this.playButton.setIcon(icon);
|
this.playButton.setIcon(icon);
|
||||||
this.playButton.setPreferredSize(BUTTON_SIZE);
|
this.playButton.setPreferredSize(BUTTON_SIZE);
|
||||||
this.playButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.PLAY, null));
|
this.playButton.addActionListener(actionEvent -> this.togglePlayback());
|
||||||
buttonCont.add(this.playButton);
|
buttonCont.add(this.playButton);
|
||||||
|
|
||||||
this.nextButton = new JButton();
|
this.nextButton = new JButton();
|
||||||
@@ -70,7 +93,7 @@ public class PlayerControls extends JPanel
|
|||||||
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0));
|
||||||
this.nextButton.setIcon(icon);
|
this.nextButton.setIcon(icon);
|
||||||
this.nextButton.setPreferredSize(BUTTON_SIZE);
|
this.nextButton.setPreferredSize(BUTTON_SIZE);
|
||||||
this.nextButton.addActionListener(actionEvent -> this.triggerCommandListeners(PlaybackCommand.NEXT, null));
|
this.nextButton.addActionListener(actionEvent -> this.nextSong());
|
||||||
buttonCont.add(this.nextButton);
|
buttonCont.add(this.nextButton);
|
||||||
|
|
||||||
progressLayout = new SpringLayout();
|
progressLayout = new SpringLayout();
|
||||||
@@ -78,7 +101,7 @@ public class PlayerControls extends JPanel
|
|||||||
this.add(progressCont);
|
this.add(progressCont);
|
||||||
|
|
||||||
this.progress = new JSlider();
|
this.progress = new JSlider();
|
||||||
this.progress.addChangeListener(changeEvent -> this.triggerCommandListeners(PlaybackCommand.SEEK, this.progress.getValue()));
|
this.progress.addChangeListener(changeEvent -> this.seek(((JSlider) changeEvent.getSource()).getValue()));
|
||||||
this.add(this.progress);
|
this.add(this.progress);
|
||||||
|
|
||||||
this.updateProgress = new JProgressBar();
|
this.updateProgress = new JProgressBar();
|
||||||
@@ -122,6 +145,79 @@ public class PlayerControls extends JPanel
|
|||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seek(int value)
|
||||||
|
{
|
||||||
|
this.service.execute(() -> {
|
||||||
|
if (this.currentPlayer != null)
|
||||||
|
{
|
||||||
|
this.currentPlayer.seek(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerCommandListeners(PlaybackCommand.SEEK, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the playback of the current song.
|
||||||
|
*/
|
||||||
|
private void togglePlayback()
|
||||||
|
{
|
||||||
|
this.service.execute(() -> {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.currentPlayer != null)
|
||||||
|
{
|
||||||
|
switch ((PlaybackStatus) this.currentPlayer.getStatus().get())
|
||||||
|
{
|
||||||
|
case PAUSED -> {
|
||||||
|
this.currentPlayer.play();
|
||||||
|
}
|
||||||
|
case PLAYING -> {
|
||||||
|
this.currentPlayer.pause();
|
||||||
|
}
|
||||||
|
case STOPPED, EMPTY -> {
|
||||||
|
if (Queue.getInstance().size() > 0)
|
||||||
|
{
|
||||||
|
if (Queue.getInstance().getCurrentSong() == null)
|
||||||
|
{
|
||||||
|
Queue.getInstance().skipToSong(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.currentPlayer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ExecutionException | InterruptedException e)
|
||||||
|
{
|
||||||
|
logger.error("Could not get current playback status", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.triggerCommandListeners(PlaybackCommand.PLAY, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips to the next song.
|
||||||
|
*/
|
||||||
|
private void previousSong()
|
||||||
|
{
|
||||||
|
Queue.getInstance().skipPrev();
|
||||||
|
this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips to the next song.
|
||||||
|
*/
|
||||||
|
private void nextSong()
|
||||||
|
{
|
||||||
|
Queue.getInstance().skipNext();
|
||||||
|
this.triggerCommandListeners(PlaybackCommand.NEXT, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUpdateProgress(int updated, int toUpdate, String updating)
|
void setUpdateProgress(int updated, int toUpdate, String updating)
|
||||||
@@ -182,4 +278,51 @@ public class PlayerControls extends JPanel
|
|||||||
listener.onCommand(command, data);
|
listener.onCommand(command, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSongChange(Queue queue)
|
||||||
|
{
|
||||||
|
if (this.currentSong != null)
|
||||||
|
{
|
||||||
|
this.currentPlayer.stopSong();
|
||||||
|
}
|
||||||
|
this.currentSong = queue.getCurrentSong();
|
||||||
|
this.playButton.setIcon(PLAY_ICON);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.error("No logger found for song {}", this.currentSong.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.currentPlayer = null;
|
||||||
|
this.progress.setMaximum(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackChanged(PlaybackInfo status)
|
||||||
|
{
|
||||||
|
if (status.getSource() != null && status.getSource() == this.currentPlayer)
|
||||||
|
{
|
||||||
|
switch (status.getStatus())
|
||||||
|
{
|
||||||
|
case PLAYING -> this.playButton.setIcon(PAUSE_ICON);
|
||||||
|
case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON);
|
||||||
|
}
|
||||||
|
this.progress.setValue((int) status.getPlayTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ library {
|
|||||||
def compileTask = binary.compileTask.get()
|
def compileTask = binary.compileTask.get()
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include")
|
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")
|
compileTask.source.from fileTree(dir: "src/main/c", include: "player.c")
|
||||||
|
|
||||||
def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily
|
def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily
|
||||||
@@ -28,6 +31,7 @@ library {
|
|||||||
} else if (osFamily.windows) {
|
} else if (osFamily.windows) {
|
||||||
compileTask.includes.from("${Jvm.current().javaHome}/include/win32")
|
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: "player_win.c")
|
||||||
|
compileTask.source.from fileTree(dir: "src/main/c", include: "windows_mutex.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
def toolChain = binary.toolChain
|
def toolChain = binary.toolChain
|
||||||
|
|||||||
259
player/src/main/c/interface.c
Normal file
259
player/src/main/c/interface.c
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
player/src/main/c/interface.h
Normal file
88
player/src/main/c/interface.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#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,297 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
#include <jni.h>
|
#include "interface.h"
|
||||||
#include <stdio.h>
|
#include "player.h"
|
||||||
|
|
||||||
typedef struct {
|
play_item_t play_list_head = {
|
||||||
short numChannels;
|
0, /* play_id */
|
||||||
short bytesPerChannel;
|
SA_CLEAR, /* stop_flag */
|
||||||
int sampleRate;
|
NULL, /* prev_item */
|
||||||
} HeaderData;
|
NULL, /* next_item */
|
||||||
|
NULL /* mutex */
|
||||||
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;
|
|
||||||
|
|
||||||
static jobject currentFile = NULL;
|
static jobject currentFile = NULL;
|
||||||
static HeaderData currentHeader;
|
static HeaderData currentHeader;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains an audio stream's header.
|
* Obtains an audio stream's header.
|
||||||
* @param env - A reference to the JVM.
|
* @param env - A reference to the JVM.
|
||||||
@@ -409,7 +132,7 @@ HeaderData readHeader(JNIEnv *env, jobject header, HeaderData *headerStruct)
|
|||||||
/**
|
/**
|
||||||
* Sets the file currently being used
|
* Sets the file currently being used
|
||||||
*/
|
*/
|
||||||
JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_Player_setCurrentFile(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_setCurrentFile(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
||||||
{
|
{
|
||||||
jobject header;
|
jobject header;
|
||||||
if (currentFile != NULL)
|
if (currentFile != NULL)
|
||||||
@@ -423,7 +146,7 @@ JNIEXPORT jint JNICALL Java_edu_regis_universeplayer_localPlayer_Player_setCurre
|
|||||||
return updatePlayer(env, obj, stream, numChannels, bitsPerSample, sampleRate);
|
return updatePlayer(env, obj, stream, numChannels, bitsPerSample, sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNIEnv *env, jobject obj, jstring location)
|
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_save(JNIEnv *env, jobject obj, jstring location)
|
||||||
{
|
{
|
||||||
const int BUFFER_SIZE = 256;
|
const int BUFFER_SIZE = 256;
|
||||||
/*
|
/*
|
||||||
@@ -471,19 +194,19 @@ JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_save(JNI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_play(JNIEnv *env, jobject obj)
|
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_playSong(JNIEnv *env, jobject obj)
|
||||||
{
|
{
|
||||||
printf("Playing\n");
|
printf("Playing\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_Player_pause(JNIEnv *env, jobject obj)
|
JNIEXPORT void JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_pauseSong(JNIEnv *env, jobject obj)
|
||||||
{
|
{
|
||||||
printf("Pausing\n");
|
printf("Pausing\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL Java_edu_regis_universeplayer_localPlayer_Player_isPaused(JNIEnv *env, jobject obj)
|
JNIEXPORT jboolean JNICALL Java_edu_regis_universeplayer_localPlayer_LocalPlayer_isSongPaused(JNIEnv *env, jobject obj)
|
||||||
{
|
{
|
||||||
printf("Is Paused?\n");
|
printf("Is Paused?\n");
|
||||||
return JNI_FALSE;
|
return JNI_FALSE;
|
||||||
|
|||||||
65
player/src/main/c/player.h
Normal file
65
player/src/main/c/player.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
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 +1,112 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2021 William Hubbard. All Rights Reserved.
|
Simpleaudio Python Extension
|
||||||
|
Copyright (C) 2015, Joe Hamilton
|
||||||
|
MIT License (see LICENSE.txt)
|
||||||
*/
|
*/
|
||||||
#include <jni.h>
|
#include "simpleaudio.h"
|
||||||
#include <stdio.h>
|
#include <Windows.h>
|
||||||
|
#include <mmreg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
int updatePlayer(JNIEnv *env, jobject obj, jobject stream, jshort numChannels, jshort bitsPerSample, jint sampleRate)
|
||||||
{
|
{
|
||||||
printf("Updating File\n");
|
printf("Updating File\n");
|
||||||
return 0;
|
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);
|
||||||
|
}
|
||||||
27
player/src/main/c/windows_mutex.c
Normal file
27
player/src/main/c/windows_mutex.c
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user