diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index ceb5fadb3..40500fa53 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -90,6 +90,7 @@ import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Frame; @@ -1208,59 +1209,35 @@ public final class SWF implements TreeItem, Timelined { boolean mp3 = true; boolean wave = true; try (ByteArrayOutputStream fos = new ByteArrayOutputStream()) { - - if (t instanceof DefineSoundTag) { - DefineSoundTag st = (DefineSoundTag) t; - if ((st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { + if (t instanceof SoundTag) { + SoundTag st = (SoundTag) t; + if ((st.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { //Does endiannes matter here? - createWavFromPcmData(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); - } else if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { - createWavFromAdpcm(fos, st.soundRate, st.soundSize, st.soundType, st.soundData); - } else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { - fos.write(st.soundData, 2, st.soundData.length - 2); - } else { + createWavFromPcmData(fos, st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()); + } else if ((st.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { + createWavFromAdpcm(fos, st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()); + } else if ((st.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { + fos.write(st.getRawSoundData()); + } else if(st instanceof DefineSoundTag) { FLVOutputStream flv = new FLVOutputStream(fos); flv.writeHeader(true, false); - flv.writeTag(new FLVTAG(0, new AUDIODATA(st.soundFormat, st.soundRate, st.soundSize, st.soundType, st.soundData))); - } - } - if (t instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t; - List blocks = shead.getBlocks(); - if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int b = 0; b < blocks.size(); b++) { - byte[] data = blocks.get(b).getData(); - baos.write(data); - } - createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); - } - if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int b = 0; b < blocks.size(); b++) { - byte[] data = blocks.get(b).getData(); - baos.write(data); - } - createWavFromPcmData(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); - } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { - for (int b = 0; b < blocks.size(); b++) { - byte[] data = blocks.get(b).getData(); - fos.write(data, 4, data.length - 4); - } - } else { + flv.writeTag(new FLVTAG(0, new AUDIODATA(st.getSoundFormat(), st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()))); + } else if (st instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag sh=(SoundStreamHeadTypeTag)st; FLVOutputStream flv = new FLVOutputStream(fos); flv.writeHeader(true, false); + List blocks = sh.getBlocks(); int ms = (int) (1000.0f / ((float) frameRate)); for (int b = 0; b < blocks.size(); b++) { byte[] data = blocks.get(b).getData(); - if (shead.getSoundFormat() == 2) { //MP3 + if (st.getSoundFormat() == 2) { //MP3 data = Arrays.copyOfRange(data, 4, data.length); } - flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(shead.getSoundFormat(), shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), data))); + flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(st.getSoundFormat(), st.getSoundRate(), st.getSoundSize(), st.getSoundType(), data))); } } - } + } return fos.toByteArray(); } } @@ -1328,11 +1305,10 @@ public final class SWF implements TreeItem, Timelined { if (t instanceof DefineSoundTag) { id = ((DefineSoundTag) t).soundId; } - - if (t instanceof DefineSoundTag) { - final DefineSoundTag st = (DefineSoundTag) t; - - if (st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + + if(t instanceof SoundTag){ + final SoundTag st=(SoundTag)t; + if (st.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || st.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { //Does endiannes matter here? final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".wav"); newfile = file; @@ -1340,33 +1316,33 @@ public final class SWF implements TreeItem, Timelined { @Override public void run() throws IOException { try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { - createWavFromPcmData(os, st.soundRate, st.soundSize, st.soundType, st.soundData); + createWavFromPcmData(os, st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()); } } }, handler).run(); - } else if ((st.soundFormat == DefineSoundTag.FORMAT_ADPCM) && wave) { + }else if ((st.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".wav"); newfile = file; new RetryTask(new RunnableIOEx() { @Override public void run() throws IOException { try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { - createWavFromAdpcm(os, st.soundRate, st.soundSize, st.soundType, st.soundData); + createWavFromAdpcm(os, st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()); } } }, handler).run(); - } else if ((st.soundFormat == DefineSoundTag.FORMAT_MP3) && mp3) { + } else if ((st.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".mp3"); newfile = file; new RetryTask(new RunnableIOEx() { @Override public void run() throws IOException { - try (FileOutputStream fos = new FileOutputStream(file)) { - fos.write(st.soundData, 2, st.soundData.length - 2); + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { + os.write(st.getRawSoundData()); } } }, handler).run(); - } else { + } else if(st instanceof DefineSoundTag){ final File file = new File(outdir + File.separator + st.getCharacterExportFileName() + ".flv"); newfile = file; new RetryTask(new RunnableIOEx() { @@ -1375,66 +1351,15 @@ public final class SWF implements TreeItem, Timelined { FileOutputStream fos = new FileOutputStream(file); try (FLVOutputStream flv = new FLVOutputStream(fos)) { flv.writeHeader(true, false); - flv.writeTag(new FLVTAG(0, new AUDIODATA(st.soundFormat, st.soundRate, st.soundSize, st.soundType, st.soundData))); + flv.writeTag(new FLVTAG(0, new AUDIODATA(st.getSoundFormat(), st.getSoundRate(), st.getSoundSize(), st.getSoundType(), st.getRawSoundData()))); } } }, handler).run(); - } - } - if (t instanceof SoundStreamHeadTypeTag) { - final SoundStreamHeadTypeTag shead = (SoundStreamHeadTypeTag) t; - final List blocks = ((SoundStreamHeadTypeTag) t).getBlocks(); - List objs = new ArrayList<>(); - objs.addAll(this.tags); - if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN || shead.getSoundFormat() == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) && wave) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int b = 0; b < blocks.size(); b++) { - byte[] data = blocks.get(b).getData(); - baos.write(data); - } - final File file = new File(outdir + File.separator + id + ".wav"); - newfile = file; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { - createWavFromPcmData(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); - } - } - }, handler).run(); - } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_ADPCM) && wave) { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - for (int b = 0; b < blocks.size(); b++) { - byte data[] = blocks.get(b).getData(); - baos.write(data); - } - final File file = new File(outdir + File.separator + id + ".wav"); - newfile = file; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { - createWavFromAdpcm(fos, shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), baos.toByteArray()); - } - } - }, handler).run(); - } else if ((shead.getSoundFormat() == DefineSoundTag.FORMAT_MP3) && mp3) { - final File file = new File(outdir + File.separator + id + ".mp3"); - newfile = file; - new RetryTask(new RunnableIOEx() { - @Override - public void run() throws IOException { - try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { - for (int b = 0; b < blocks.size(); b++) { - byte data[] = blocks.get(b).getData(); - os.write(data, 2, data.length - 2); - } - } - } - }, handler).run(); - } else { + } else if(st instanceof SoundStreamHeadTypeTag){ final File file = new File(outdir + File.separator + id + ".flv"); newfile = file; + SoundStreamHeadTypeTag sh=(SoundStreamHeadTypeTag)st; + final List blocks=sh.getBlocks(); new RetryTask(new RunnableIOEx() { @Override public void run() throws IOException { @@ -1445,16 +1370,17 @@ public final class SWF implements TreeItem, Timelined { int ms = (int) (1000.0f / ((float) frameRate)); for (int b = 0; b < blocks.size(); b++) { byte data[] = blocks.get(b).getData(); - if (shead.getSoundFormat() == 2) { //MP3 + if (st.getSoundFormat() == 2) { //MP3 data = Arrays.copyOfRange(data, 4, data.length); } - flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(shead.getSoundFormat(), shead.getSoundRate(), shead.getSoundSize(), shead.getSoundType(), data))); + flv.writeTag(new FLVTAG(ms * b, new AUDIODATA(st.getSoundFormat(), st.getSoundRate(), st.getSoundSize(), st.getSoundType(), data))); } } } }, handler).run(); } } + if (newfile != null) { ret.add(newfile); } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index e59bf7fb6..40a24e25a 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -24,6 +24,7 @@ import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.decompiler.flash.timeline.Timeline; import com.jpexs.decompiler.flash.timeline.Timelined; @@ -47,6 +48,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; +import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -71,6 +73,7 @@ public final class ImagePanel extends JPanel implements ActionListener, MediaDis private JLabel debugLabel = new JLabel("-"); private DepthState stateUnderCursor = null; private MouseEvent lastMouseEvent = null; + private List soundPlayers = new ArrayList<>(); @Override public void setBackground(Color bg) { @@ -319,11 +322,23 @@ public final class ImagePanel extends JPanel implements ActionListener, MediaDis timer.cancel(); timer = null; } + stopAllSounds(); + } + + private void stopAllSounds(){ + for (int i = soundPlayers.size() - 1; i >= 0; i--) { + SoundTagPlayer pl = soundPlayers.get(i); + pl.pause(); + } + soundPlayers.clear(); } private void nextFrame() { int newframe = frame == timelined.getTimeline().frames.size() - 1 ? 0 : frame + 1; if (newframe != frame) { + if (newframe == 0) { + stopAllSounds(); + } frame = newframe; updatePos(lastMouseEvent); drawFrame(); @@ -373,6 +388,29 @@ public final class ImagePanel extends JPanel implements ActionListener, MediaDis mat.translateX = swf.displayRect.Xmin; mat.translateY = swf.displayRect.Ymin; ImageIcon icon = new ImageIcon(getFrame(swf, frame, timelined, stateUnderCursor, mouseButton).getBufferedImage()); + List sounds = timelined.getTimeline().getSounds(frame, stateUnderCursor, mouseButton); + for (int sndId : sounds) { + CharacterTag c = swf.characters.get(sndId); + if (c instanceof SoundTag) { + SoundTag st = (SoundTag) c; + if (SoundTagPlayer.tagSupported(st)) { + final SoundTagPlayer sp = new SoundTagPlayer(st, 1); + synchronized (ImagePanel.class) { + soundPlayers.add(sp); + } + sp.addListener(new PlayerListener() { + + @Override + public void playingFinished() { + synchronized (ImagePanel.class) { + soundPlayers.remove(sp); + } + } + }); + sp.play(); + } + } + } label.setIcon(icon); } diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 37a9546dd..35da06385 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -95,6 +95,7 @@ import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; import com.jpexs.decompiler.flash.tags.text.ParseException; @@ -286,142 +287,9 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec private JPanel repPanel; private JPanel repPanel2; - private SoundThread soundThread = null; + private SoundTagPlayer soundThread = null; - private class SoundThread implements MediaDisplay { - - private SoundPlayer player; - - private int mp3SampleCount; - private boolean mp3; - private Thread thr; - private int actualPos = 0; - private boolean playing = false; - private int mp3FrameRate; - private byte data[]; - - private static final int FRAME_DIVISOR = 1024; - - public SoundThread(byte data[]) { - this(data, false, 0, 0); - } - - public SoundThread(byte data[], boolean mp3, int mp3SampleCount, int mp3FrameRate) { - this.data = data; - this.mp3SampleCount = mp3SampleCount; - this.mp3 = mp3; - this.mp3FrameRate = mp3FrameRate; - - } - - @Override - public synchronized int getCurrentFrame() { - if (player == null) { - return 0; - } - - if (!playing) { - return actualPos; - } - - actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); - return actualPos; - } - - @Override - public synchronized int getTotalFrames() { - //System.out.println("getTotalFrames"); - if (player == null) { - return 0; - } - int ret = (int) (player.samplesCount() / FRAME_DIVISOR); - - //System.out.println("/getTotalFrames"); - return ret; - } - - @Override - public synchronized void pause() { - if (!playing) { - return; - } - playing = false; - actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); - player.stop(); - - } - - @Override - public synchronized void play() { - if (player != null) { - player.stop(); - } - - if (mp3) { - try { - player = new MP3Player(new ByteArrayInputStream(data), mp3SampleCount, mp3FrameRate); - } catch (JavaLayerException ex) { - Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } else { - player = new WavPlayer(new ByteArrayInputStream(data)); - } - final int startPos = actualPos * FRAME_DIVISOR; - thr = new Thread() { - - @Override - public void run() { - player.skip(startPos); - player.play(); - if (playing) { - gotoFrame(0); - play(); - } - } - - }; - thr.start(); - playing = true; - } - - @Override - public void rewind() { - actualPos = 0; - } - - @Override - public boolean isPlaying() { - return playing; - } - - @Override - public synchronized void gotoFrame(int frame) { - if (playing) { - playing = false; - player.stop(); - } - actualPos = frame; - } - - @Override - public void setBackground(Color color) { - - } - - @Override - public int getFrameRate() { - if (player == null) { - return 1; - } - return (int) (player.getFrameRate() / FRAME_DIVISOR); - } - - @Override - public boolean isLoaded() { - return true; - } - - } + private static final String ACTION_SELECT_BKCOLOR = "SELECTCOLOR"; private static final String ACTION_REPLACE_IMAGE = "REPLACEIMAGE"; @@ -2758,40 +2626,15 @@ public final class MainPanel extends JPanel implements ActionListener, TreeSelec timelined = ((DefineSpriteTag) fn.getParent()); } previewImagePanel.setTimelined(timelined, swf, fn.getFrame() - 1); - } else if (((tagObj instanceof DefineSoundTag) && mainMenu.isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((DefineSoundTag) tagObj).getExportFormat()))) - || ((tagObj instanceof SoundStreamHeadTypeTag) && mainMenu.isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundStreamHeadTypeTag) tagObj).getExportFormat())))) { + } else if (((tagObj instanceof SoundTag) && mainMenu.isInternalFlashViewerSelected() && (Arrays.asList("mp3", "wav").contains(((SoundTag) tagObj).getExportFormat())))) { showCard(CARDDRAWPREVIEWPANEL); previewImagePanel.setImage(new SerializableImage(1, 1, BufferedImage.TYPE_INT_ARGB)); - String exportFormat = ""; - int soundRate = 0; - long sampleCount = 0; - boolean soundType = false; - if (tagObj instanceof DefineSoundTag) { - DefineSoundTag ds = (DefineSoundTag) tagObj; - exportFormat = ds.getExportFormat(); - soundRate = ds.soundRate; - sampleCount = ds.soundSampleCount; - soundType = ds.soundType; - } - if (tagObj instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag sh = (SoundStreamHeadTypeTag) tagObj; - exportFormat = sh.getExportFormat(); - soundRate = sh.getSoundRate(); - sampleCount = sh.getSoundSampleCount() * sh.getBlocks().size(); - soundType = sh.getSoundType(); - } - try { - byte sounddata[] = tagObj.getSwf().exportSound((Tag) tagObj); - final int soundRates[] = new int[]{5512, 11025, 22050, 44100}; - int frameRate = soundRates[soundRate]; - soundThread = new SoundThread(sounddata, exportFormat.equals("mp3"), (int) (sampleCount * (soundType ? 2 : 1)), frameRate * (soundType ? 2 : 1)); - imagePlayControls.setMedia(soundThread); - soundThread.play(); - - } catch (IOException ex) { - Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, "Cannot export Sound for playback", ex); - } + + + soundThread = new SoundTagPlayer((SoundTag)tagObj,Integer.MAX_VALUE); + imagePlayControls.setMedia(soundThread); + soundThread.play(); } else if (((tagObj instanceof FrameNodeItem) && ((FrameNodeItem) tagObj).isDisplayed()) || ((tagObj instanceof CharacterTag) || (tagObj instanceof FontTag)) && (tagObj instanceof Tag) || (tagObj instanceof SoundStreamHeadTypeTag)) { ((CardLayout) viewerCards.getLayout()).show(viewerCards, FLASH_VIEWER_CARD); diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/PlayerListener.java b/trunk/src/com/jpexs/decompiler/flash/gui/PlayerListener.java new file mode 100644 index 000000000..41eaaa56e --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/gui/PlayerListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.jpexs.decompiler.flash.gui; + +/** + * + * @author JPEXS + */ +public interface PlayerListener { + public void playingFinished(); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java b/trunk/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java new file mode 100644 index 000000000..e8b945d81 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/gui/SoundTagPlayer.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.jpexs.decompiler.flash.gui; + +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; +import com.jpexs.decompiler.flash.tags.DefineSoundTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.helpers.sound.MP3Player; +import com.jpexs.helpers.sound.SoundPlayer; +import com.jpexs.helpers.sound.WavPlayer; +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javazoom.jl.decoder.JavaLayerException; + +/** + * + * @author JPEXS + */ +public class SoundTagPlayer implements MediaDisplay { + + private SoundPlayer player; + + private Thread thr; + private int actualPos = 0; + private boolean playing = false; + private SoundTag tag; + private byte data[]; + private List listeners=new ArrayList<>(); + + public void addListener(PlayerListener l){ + listeners.add(l); + } + + public void removeListener(PlayerListener l){ + listeners.remove(l); + } + + public void fireFinished(){ + for(PlayerListener l:listeners){ + l.playingFinished(); + } + } + + private static final int FRAME_DIVISOR = 1024; + + private int loops; + + public static boolean tagSupported(SoundTag tag){ + if(!Arrays.asList("mp3","wav").contains(tag.getExportFormat())){ + return false; + } + return true; + } + + public SoundTagPlayer(SoundTag tag, int loops) { + if(!tagSupported(tag)){ + throw new IllegalArgumentException("Format not supported"); + } + this.tag = tag; + this.loops = loops; + try { + this.data = ((Tag)tag).getSwf().exportSound((Tag)tag); + } catch (IOException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + public synchronized int getCurrentFrame() { + if (player == null) { + return 0; + } + + if (!playing) { + return actualPos; + } + + actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); + return actualPos; + } + + @Override + public synchronized int getTotalFrames() { + //System.out.println("getTotalFrames"); + if (player == null) { + return 0; + } + int ret = (int) (player.samplesCount() / FRAME_DIVISOR); + + //System.out.println("/getTotalFrames"); + return ret; + } + + @Override + public synchronized void pause() { + if (!playing) { + return; + } + playing = false; + actualPos = (int) (player.getSamplePosition() / FRAME_DIVISOR); + player.stop(); + + } + + public void play(boolean async){ + if (player != null) { + player.stop(); + } + + if (tag.getExportFormat().equals("mp3")) { + final int soundRates[] = new int[]{5512, 11025, 22050, 44100}; + try { + player = new MP3Player(new ByteArrayInputStream(data), (int) (tag.getTotalSoundSampleCount() * (tag.getSoundType() ? 2 : 1)), soundRates[tag.getSoundRate()] * (tag.getSoundType() ? 2 : 1)); + } catch (JavaLayerException ex) { + Logger.getLogger(SoundTagPlayer.class.getName()).log(Level.SEVERE, null, ex); + } + } else if(tag.getExportFormat().equals("wav")){ + player = new WavPlayer(new ByteArrayInputStream(data)); + } + final int startPos = actualPos * FRAME_DIVISOR; + Runnable r = new Runnable() { + + @Override + public void run() { + player.skip(startPos); + player.play(); + fireFinished(); + if(loops>0){ + loops--; + } + if (playing && loops>0) { + gotoFrame(0); + play(); + } + } + }; + playing = true; + if(async){ + thr = new Thread(r); + thr.start(); + }else{ + r.run(); + } + + } + + @Override + public synchronized void play() { + play(true); + } + + @Override + public void rewind() { + actualPos = 0; + } + + @Override + public boolean isPlaying() { + return playing; + } + + @Override + public synchronized void gotoFrame(int frame) { + if (playing) { + playing = false; + player.stop(); + } + actualPos = frame; + } + + @Override + public void setBackground(Color color) { + + } + + @Override + public int getFrameRate() { + if (player == null) { + return 1; + } + return (int) (player.getFrameRate() / FRAME_DIVISOR); + } + + @Override + public boolean isLoaded() { + return true; + } + + } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index afffeebb8..6fcb4f0cc 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; import com.jpexs.decompiler.flash.types.BasicType; import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.sound.MP3FRAME; @@ -32,6 +33,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; @@ -42,7 +45,7 @@ import javax.sound.sampled.UnsupportedAudioFileException; * * @author JPEXS */ -public class DefineSoundTag extends CharacterTag { +public class DefineSoundTag extends CharacterTag implements SoundTag { @SWFType(BasicType.UI16) public int soundId; @@ -119,6 +122,7 @@ public class DefineSoundTag extends CharacterTag { soundData = sis.readBytesEx(sis.available()); } + @Override public String getExportFormat() { if (soundFormat == DefineSoundTag.FORMAT_MP3) { return "mp3"; @@ -182,7 +186,8 @@ public class DefineSoundTag extends CharacterTag { return (size + 10); } - public boolean setSound(InputStream is, int newSoundFormat) throws IOException { + @Override + public boolean setSound(InputStream is, int newSoundFormat) { int newSoundRate = -1; boolean newSoundSize = false; boolean newSoundType = false; @@ -213,7 +218,7 @@ public class DefineSoundTag extends CharacterTag { default: return false; } - } catch (UnsupportedAudioFileException ex) { + } catch (UnsupportedAudioFileException | IOException ex) { return false; } break; @@ -237,37 +242,41 @@ public class DefineSoundTag extends CharacterTag { } } } - MP3SOUNDDATA snd = new MP3SOUNDDATA(new ByteArrayInputStream(mp3data), true); - if (!snd.frames.isEmpty()) { - MP3FRAME fr = snd.frames.get(0); - newSoundRate = fr.getSamplingRate(); - switch (newSoundRate) { - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - return false; - } - newSoundSize = true; - newSoundType = fr.isStereo(); - int len = snd.sampleCount(); - if (fr.isStereo()) { - len = len / 2; - } - newSoundSampleCount = len; + try { + MP3SOUNDDATA snd = new MP3SOUNDDATA(new ByteArrayInputStream(mp3data), true); + if (!snd.frames.isEmpty()) { + MP3FRAME fr = snd.frames.get(0); + newSoundRate = fr.getSamplingRate(); + switch (newSoundRate) { + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + return false; + } + newSoundSize = true; + newSoundType = fr.isStereo(); + int len = snd.sampleCount(); + if (fr.isStereo()) { + len = len / 2; + } + newSoundSampleCount = len; - } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); - sos.writeSI16(0); //Latency - how to calculate it? - sos.write(mp3data); - newSoundData = baos.toByteArray(); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); + sos.writeSI16(0); //Latency - how to calculate it? + sos.write(mp3data); + newSoundData = baos.toByteArray(); + } catch (IOException ex) { + return false; + } break; default: return false; @@ -282,5 +291,46 @@ public class DefineSoundTag extends CharacterTag { return true; } return false; + } + + @Override + public boolean importSupported() { + return true; + } + + @Override + public int getSoundRate() { + return soundRate; + } + + @Override + public boolean getSoundType() { + return soundType; + } + + @Override + public byte[] getRawSoundData() { + if (soundFormat == FORMAT_MP3) { + return Arrays.copyOfRange(soundData, 2, soundData.length - 2); + } + return soundData; + } + + @Override + public int getSoundFormat() { + return soundFormat; + } + + @Override + public long getTotalSoundSampleCount(){ + return soundSampleCount; + } + + @Override + public boolean getSoundSize() { + return soundSize; + } + + } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java index 061ef60aa..ee43cff52 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java @@ -29,6 +29,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -83,6 +84,12 @@ public class SoundStreamHead2Tag extends CharacterIdTag implements SoundStreamHe if (streamSoundCompression == DefineSoundTag.FORMAT_ADPCM) { return "wav"; } + if (streamSoundCompression == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return "wav"; + } + if (streamSoundCompression == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return "wav"; + } return "flv"; } @@ -176,4 +183,37 @@ public class SoundStreamHead2Tag extends CharacterIdTag implements SoundStreamHe return ret; } + + @Override + public boolean importSupported() { + return false; + } + + @Override + public boolean setSound(InputStream is, int newSoundFormat) { + return false; + } + + @Override + public byte[] getRawSoundData() { + List blocks = getBlocks(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + for (SoundStreamBlockTag block : blocks) { + if (streamSoundCompression == DefineSoundTag.FORMAT_MP3) { + baos.write(block.data, 4, block.data.length - 4); + } else { + baos.write(block.data); + } + } + } catch (IOException ex) { + return null; + } + return baos.toByteArray(); + } + + @Override + public long getTotalSoundSampleCount() { + return getBlocks().size()*streamSoundSampleCount; + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java index 004c7a296..9bea81004 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/SoundStreamHeadTag.java @@ -31,9 +31,13 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -71,6 +75,12 @@ public class SoundStreamHeadTag extends CharacterIdTag implements SoundStreamHea if (streamSoundCompression == DefineSoundTag.FORMAT_ADPCM) { return "wav"; } + if (streamSoundCompression == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return "wav"; + } + if (streamSoundCompression == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return "wav"; + } return "flv"; } @@ -191,4 +201,37 @@ public class SoundStreamHeadTag extends CharacterIdTag implements SoundStreamHea return ret; } + + @Override + public boolean importSupported() { + return false; + } + + @Override + public boolean setSound(InputStream is, int newSoundFormat) { + return false; + } + + @Override + public byte[] getRawSoundData() { + List blocks = getBlocks(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + for (SoundStreamBlockTag block : blocks) { + if (streamSoundCompression == DefineSoundTag.FORMAT_MP3) { + baos.write(block.data, 4, block.data.length - 4); + } else { + baos.write(block.data); + } + } + } catch (IOException ex) { + return null; + } + return baos.toByteArray(); + } + + @Override + public long getTotalSoundSampleCount() { + return getBlocks().size()*streamSoundSampleCount; + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java index 20a9a3613..7b421bdfe 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundStreamHeadTypeTag.java @@ -23,23 +23,15 @@ import java.util.List; * * @author JPEXS */ -public interface SoundStreamHeadTypeTag { - - public int getSoundFormat(); - - public int getSoundRate(); +public interface SoundStreamHeadTypeTag extends SoundTag { public boolean getSoundSize(); - public boolean getSoundType(); - public long getSoundSampleCount(); public void setVirtualCharacterId(int ch); public int getCharacterId(); - public String getExportFormat(); - public List getBlocks(); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java new file mode 100644 index 000000000..add190f22 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/SoundTag.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.jpexs.decompiler.flash.tags.base; + +import java.io.InputStream; + +/** + * + * @author JPEXS + */ +public interface SoundTag { + public String getExportFormat(); + public boolean importSupported(); + public boolean setSound(InputStream is, int newSoundFormat); + public int getSoundRate(); + public boolean getSoundType(); + public byte[] getRawSoundData(); + public int getSoundFormat(); + public long getTotalSoundSampleCount(); + public boolean getSoundSize(); + public String getCharacterExportFileName(); +} diff --git a/trunk/src/com/jpexs/decompiler/flash/timeline/Frame.java b/trunk/src/com/jpexs/decompiler/flash/timeline/Frame.java index 495c54472..3582d4808 100644 --- a/trunk/src/com/jpexs/decompiler/flash/timeline/Frame.java +++ b/trunk/src/com/jpexs/decompiler/flash/timeline/Frame.java @@ -42,6 +42,7 @@ public class Frame { public DoActionTag action; public RGB backgroundColor = new RGBA(0, 0, 0, 0); public Timeline timeline; + public List sounds = new ArrayList<>(); public Frame(Timeline timeline) { this.timeline = timeline; @@ -54,6 +55,7 @@ public class Frame { for (int depth : obj.layers.keySet()) { layers.put(depth, new DepthState(obj.layers.get(depth), this, true)); } + //Do not copy sounds } private class Clip { @@ -73,8 +75,7 @@ public class Frame { List keys = new ArrayList<>(layers.keySet()); DepthState maxds = null; Stack clips = new Stack<>(); - for (int d : keys) { //int i=keys.size()-1;i>=0;i--){ - //int d = keys.get(i); + for (int d : keys) { DepthState ds = layers.get(d); Clip currentClip = null; for (int j = clips.size() - 1; j >= 0; j--) { diff --git a/trunk/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/trunk/src/com/jpexs/decompiler/flash/timeline/Timeline.java index fe925d3bb..91fe13f56 100644 --- a/trunk/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/trunk/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -21,8 +21,10 @@ import com.jpexs.decompiler.flash.exporters.Matrix; import com.jpexs.decompiler.flash.tags.DoActionTag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.StartSoundTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.DrawableTag; import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; @@ -79,6 +81,9 @@ public class Timeline { this.frameRate = swf.frameRate; Frame frame = new Frame(this); for (Tag t : tags) { + if (t instanceof StartSoundTag) { + frame.sounds.add(((StartSoundTag) t).soundId); + } if (t instanceof SetBackgroundColorTag) { frame.backgroundColor = ((SetBackgroundColorTag) t).backgroundColor; } @@ -174,6 +179,34 @@ public class Timeline { } + public List getSounds(int frame, DepthState stateUnderCursor, int mouseButton) { + List ret=new ArrayList<>(); + Frame fr = this.frames.get(frame); + ret.addAll(fr.sounds); + for (int d = this.getMaxDepth(); d >= 0; d--) { + DepthState ds = fr.layers.get(d); + if (ds != null) { + CharacterTag c = swf.characters.get(ds.characterId); + if (c instanceof Timelined) { + int dframe = ds.time % ((Timelined)c).getTimeline().frames.size(); + if (c instanceof ButtonTag) { + ButtonTag bt = (ButtonTag) c; + dframe = ButtonTag.FRAME_UP; + if (stateUnderCursor == ds) { + if (mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + ret.addAll(((Timelined)c).getTimeline().getSounds(dframe, stateUnderCursor, mouseButton)); + } + } + } + return ret; + } + public Shape getOutline(int frame, int ratio, DepthState stateUnderCursor, int mouseButton, Matrix transformation) { Frame fr = this.frames.get(frame); Area area = new Area(); diff --git a/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java b/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java index ee448903d..a0673acc7 100644 --- a/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java +++ b/trunk/src/com/jpexs/helpers/sound/SoundPlayer.java @@ -23,7 +23,7 @@ import java.io.InputStream; * @author JPEXS */ public abstract class SoundPlayer { - + protected InputStream is; protected SoundPlayer(InputStream is) { @@ -43,5 +43,7 @@ public abstract class SoundPlayer { public abstract boolean isPlaying(); public abstract long getFrameRate(); + + } diff --git a/trunk/src/com/jpexs/helpers/sound/WavPlayer.java b/trunk/src/com/jpexs/helpers/sound/WavPlayer.java index 5174373f4..027c3a1ee 100644 --- a/trunk/src/com/jpexs/helpers/sound/WavPlayer.java +++ b/trunk/src/com/jpexs/helpers/sound/WavPlayer.java @@ -91,8 +91,7 @@ public class WavPlayer extends SoundPlayer { } } catch (InterruptedException ex) { //Ignore - } - + } } @Override