From c319fb4a84cf567a9754233b7a1875c3f738cd79 Mon Sep 17 00:00:00 2001 From: AShiningRay Date: Thu, 21 Nov 2024 11:54:14 -0300 Subject: [PATCH] PlatformPlayer: Minor improvements to state handling. We shouldn't be able to retrieve the media's current time if the player isn't at least prefetched (according to the docs, as PREFETCHED is the only state where the exclusive player resources should be fetched). Also, set the player state to PREFETCHED when it reaches an END_OF_MEDIA event before notifying the playerListeners about it. Also, handle allocation, closing and deallocating a bit better in regards to what the should actually do on this player implementation, with deallocate() basically doing nothing, like prefetch(). Fixes #16 (Sonic Jump Siemens version) and probably many others. --- src/org/recompile/mobile/PlatformPlayer.java | 96 ++++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/src/org/recompile/mobile/PlatformPlayer.java b/src/org/recompile/mobile/PlatformPlayer.java index 274d47cd..380d5a4a 100644 --- a/src/org/recompile/mobile/PlatformPlayer.java +++ b/src/org/recompile/mobile/PlatformPlayer.java @@ -158,10 +158,10 @@ public void close() try { - player.stop(); - player.deallocate(); /* Call player's deallocate directly, otherwise we'll realize() again */ - state = Player.CLOSED; + if(player.isRunning()) { stop(); } + player.close(); player = null; + state = Player.CLOSED; notifyListeners(PlayerListener.CLOSED, null); } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Could not close player: " + e.getMessage()); } @@ -217,7 +217,7 @@ public void deallocate() { if(getState() == Player.CLOSED) { throw new IllegalStateException("Cannot deallocate player, it is already CLOSED."); } - stop(); + if(player.isRunning()) { stop(); } player.deallocate(); /* @@ -225,11 +225,7 @@ public void deallocate() * as deallocate can be called during the transition from UNREALIZED to REALIZED, and if that happens, * we can't actually set it as REALIZED, it must be kept as UNREALIZED. */ - if(state > Player.UNREALIZED) - { - player.realize(); - state = Player.REALIZED; - } + if(state > Player.UNREALIZED) { state = Player.REALIZED; } } public String getContentType() @@ -252,6 +248,15 @@ public long getMediaTime() { if(getState() == Player.CLOSED) { throw new IllegalStateException("Cannot call getMediaTime on a CLOSED player."); } + /* + * If the player isn't at least prefetched, there's no way to get media time. + * PlatformPlayer does in fact acquire everything needed to play the media on realize(), + * however, J2ME docs state that the exclusive and scarce resources (such as an actual + * player resources) should only be acquired in prefetch. So let's assume we're working this + * way here for now. + */ + if(getState() == Player.UNREALIZED || getState() == Player.REALIZED) { return Player.TIME_UNKNOWN; } + return player.getMediaTime(); } @@ -336,6 +341,7 @@ public void setLoopCount(int count) { } public long getMediaTime() { return 0; } public boolean isRunning() { return false; } public void deallocate() { } + public void close() { } public void realize() { } public void prefetch() { } public long getDuration() { return Player.TIME_UNKNOWN; } @@ -361,7 +367,6 @@ protected byte[] copyMediaData(InputStream stream) private class midiPlayer extends audioplayer { - private byte[] stream; private Sequencer midi; private Sequence midiSequence; private Synthesizer synthesizer; @@ -373,9 +378,6 @@ public midiPlayer(InputStream stream) { midi = MidiSystem.getSequencer(false); - /* Make a new copy of the media stream, as realize() can be called more than once during the player's lifecycle */ - this.stream = copyMediaData(stream); - if (Manager.useCustomMidi && Manager.hasLoadedCustomMidi) { synthesizer = Manager.customSynth; // Use the custom synthesizer @@ -388,6 +390,7 @@ public midiPlayer(InputStream stream) synthesizer.open(); receiver = synthesizer.getReceiver(); midi.getTransmitter().setReceiver(receiver); + midiSequence = MidiSystem.getSequence(stream); } catch (Exception e) { @@ -400,7 +403,6 @@ public void realize() try { midi.open(); - midiSequence = MidiSystem.getSequence(new ByteArrayInputStream(stream)); midi.setSequence(midiSequence); state = Player.REALIZED; } @@ -431,8 +433,8 @@ public void meta(MetaMessage meta) { if (meta.getType() == 0x2F) // 0x2F = END_OF_MEDIA in Sequencer { - notifyListeners(PlayerListener.END_OF_MEDIA, getMediaTime()); state = Player.PREFETCHED; + notifyListeners(PlayerListener.END_OF_MEDIA, getMediaTime()); } } }); @@ -447,7 +449,15 @@ public void stop() notifyListeners(PlayerListener.STOPPED, getMediaTime()); } - public void deallocate() { midi.close(); } + public void deallocate() { } // Prefetch does "nothing" in each internal player so deallocate must also do nothing + + public void close() + { + midi.close(); + synthesizer.close(); + midiSequence = null; + receiver = null; + } public void setLoopCount(int count) { @@ -502,17 +512,14 @@ private void cleanSequencer() private class wavPlayer extends audioplayer { /* PCM WAV variables */ - private byte[] stream; private AudioInputStream wavStream; private Clip wavClip; - /* IMA ADPCM WAV variables */ - InputStream decodedStream; private int[] wavHeaderData = new int[4]; public wavPlayer(InputStream stream) { /* - * A wav header is generally 44-bytes long (60 for IMA ADPCM), and it is what we need to read in order + * A wav header is generally 44-bytes long (up to 60 for IMA ADPCM), and it is what we need to read in order * to get the stream's format, frame size, bit rate, number of channels, etc. which gives us information * on the kind of codec needed to play or decode the incoming stream. The stream needs to be reset * or else PCM files will be loaded without a header and it might cause issues with playback. @@ -522,31 +529,26 @@ public wavPlayer(InputStream stream) stream.mark(60); wavHeaderData = WavImaAdpcmDecoder.readHeader(stream); stream.reset(); - this.stream = copyMediaData(stream); - } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Could not prepare wav stream:" + e.getMessage());} - } - public void realize() - { - try - { - /* We only check for IMA ADPCM at the moment. */ if(wavHeaderData[0] != 17) /* If it's not IMA ADPCM we don't need to do anything to the stream. */ { - /* Same idea as midiPlayer, operate on a new copy of the media stream */ - wavStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(stream)); - wavClip = AudioSystem.getClip(); - wavClip.open(wavStream); - state = Player.REALIZED; + wavStream = AudioSystem.getAudioInputStream(stream); } else /* But if it is IMA ADPCM, we have to decode it manually. */ { - decodedStream = WavImaAdpcmDecoder.decodeImaAdpcm(new ByteArrayInputStream(stream), wavHeaderData); - wavStream = AudioSystem.getAudioInputStream(decodedStream); - wavClip = AudioSystem.getClip(); - wavClip.open(wavStream); - state = Player.REALIZED; + wavStream = AudioSystem.getAudioInputStream(WavImaAdpcmDecoder.decodeImaAdpcm(stream, wavHeaderData)); } + + } catch (Exception e) { Mobile.log(Mobile.LOG_ERROR, PlatformPlayer.class.getPackage().getName() + "." + PlatformPlayer.class.getSimpleName() + ": " + "Could not prepare wav stream:" + e.getMessage());} + } + + public void realize() + { + try + { + wavClip = AudioSystem.getClip(); + wavClip.open(wavStream); + state = Player.REALIZED; } catch (Exception e) { @@ -587,7 +589,14 @@ public void stop() notifyListeners(PlayerListener.STOPPED, getMediaTime()); } - public void deallocate() { wavClip = null; } + public void deallocate() { } // Prefetch does "nothing" in each internal player so deallocate must also do nothing + + public void close() + { + wavClip = null; + wavStream = null; + wavHeaderData = null; + } public void setLoopCount(int count) { @@ -687,7 +696,14 @@ public void stop() notifyListeners(PlayerListener.STOPPED, getMediaTime()); } - public void deallocate() { mp3Player = null; } + public void deallocate() { } // Prefetch does "nothing" in each internal player so deallocate must also do nothing + + public void close() + { + mp3Player = null; + stream = null; + playerThread = null; + } public void setLoopCount(int count) {