Skip to content

Commit

Permalink
PlatformPlayer + MPEGPlayer: Improve MPEG playback with looping
Browse files Browse the repository at this point in the history
Looping has been fixed and is now non-blocking as well. Can't
have that be handled by MPEGPlayer internall, it has to be placed
in PlatformPlayer's start() method instead.

Also add a few null checks all around, and change getDuration to
return TIME_UNKNOWN if it can't get the MPEG stream's duration.
  • Loading branch information
AShiningRay committed Nov 21, 2024
1 parent c319fb4 commit 7ad51ad
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 26 deletions.
30 changes: 10 additions & 20 deletions src/javax/microedition/media/javazoom/jl/player/MPEGPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.decoder.SampleBuffer;

import javax.microedition.media.Player;
import org.recompile.mobile.Mobile;

/**
Expand Down Expand Up @@ -187,13 +188,7 @@ public boolean play(int frames) throws JavaLayerException, InterruptedException
synchronized (this)
{
/* Only set complete flag if not entering a new loop */
if(loopCount == 0) { complete = (!closed); }
else
{
loopCount--;
complete = false;
reset();
}
complete = (!closed);
}
}
}
Expand Down Expand Up @@ -232,16 +227,6 @@ public void reset()

synchronized (dataStream) { dataStream.notifyAll(); }
Mobile.log(Mobile.LOG_DEBUG, MPEGPlayer.class.getPackage().getName() + "." + MPEGPlayer.class.getSimpleName() + ": " + "reset done");

/* If looping has been set as more than 0, consume a count, and begin playing again. */
if(loopCount > 0)
{
try
{
loopCount--;
play(Integer.MAX_VALUE);
} catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, MPEGPlayer.class.getPackage().getName() + "." + MPEGPlayer.class.getSimpleName() + ": " + "MPEGPlayer: Failed to loop player:"+e.getMessage()); }
}
}

public int getBitrate() {
Expand Down Expand Up @@ -430,13 +415,13 @@ public long getDuration()
{
double duration = 0;

try { duration = (double) ((data.length * 8 * 1000D) / getBitrate()); }
catch (Exception e){ Mobile.log(Mobile.LOG_ERROR, MPEGPlayer.class.getPackage().getName() + "." + MPEGPlayer.class.getSimpleName() + ": " + "Couldn't get duration:" + e.getMessage()); return 0;}
try { duration = (double) ((data.length * 8 * 1_000D) / getBitrate()); }
catch (Exception e){ Mobile.log(Mobile.LOG_ERROR, MPEGPlayer.class.getPackage().getName() + "." + MPEGPlayer.class.getSimpleName() + ": " + "Couldn't get duration:" + e.getMessage()); return Player.TIME_UNKNOWN;}

return (long) duration;
}

public void loop(int count)
public void setLoopCount(int count)
{
if(count == -1) { loopCount = Integer.MAX_VALUE; } // Loop "indefinitely"
else { loopCount = count; }
Expand All @@ -447,4 +432,9 @@ public boolean isRunning()
if(!paused && !closed && !reset) { return true; }
else { return false; }
}

// Looping is handled by PlatformPlayer
public int getLoopCount() { return loopCount; }

public void decreaseLoopCount() { loopCount--; }
}
34 changes: 28 additions & 6 deletions src/org/recompile/mobile/PlatformPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -659,19 +659,41 @@ public void realize()
public void prefetch() { state = Player.PREFETCHED; }

public void start()
{
{
/*
* The fact these null checks have to be littered on MP3Player gives me a bad feeling...
* Maybe it's because it doesn't at all work like wav and midi players, which are
* integrated into Java Sound and don't need a thread object to play non-blocking.
*/
if(mp3Player == null) { return; }

try
{
playerThread = new Thread(() ->
{
try
{
if(getMediaTime() >= getDuration()) { setMediaTime(0); }
mp3Player.play();
mp3Player.play(); // This is thread-blocking, so the code below only executes after this has finished.

/*
* Check if mp3Player is still valid and exit early, since this thread can be
* interrupted and the player can also be closed abruptly.
*/
if (mp3Player == null) { return; }

//mp3Player.play(); is thread-blocking, so having this directly after it is safe.
if (!Thread.currentThread().isInterrupted())
{
if(mp3Player.getLoopCount() > 0)
{
while(mp3Player.getLoopCount() > 0)
{
mp3Player.decreaseLoopCount();
mp3Player.reset();
mp3Player.play();
}
}
// After all loops (if any) are done, notify END_OF_MEDIA
notifyListeners(PlayerListener.END_OF_MEDIA, getMediaTime());
state = Player.PREFETCHED;
}
Expand All @@ -684,11 +706,11 @@ public void start()
state = Player.STARTED;
notifyListeners(PlayerListener.STARTED, getMediaTime());
} catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Couldn't start mpeg player:" + e.getMessage()); }

}

public void stop()
{
if(mp3Player == null) { return; }
mp3Player.stop();
if (playerThread != null && playerThread.isAlive()) { playerThread.interrupt(); }

Expand All @@ -714,8 +736,8 @@ public void setLoopCount(int count)
* it appears that count = 1 means no loop at all, at least based
* on Gameloft games that set effects and some music with count = 1
*/
if(count == Clip.LOOP_CONTINUOUSLY) { mp3Player.loop(count); }
else { mp3Player.loop(count-1); }
if(count == Clip.LOOP_CONTINUOUSLY) { mp3Player.setLoopCount(count); }
else { mp3Player.setLoopCount(count-1); }
}

public long setMediaTime(long now)
Expand Down

0 comments on commit 7ad51ad

Please sign in to comment.