diff --git a/CHANGES.md b/CHANGES.md index 38033d44..87859d5e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,10 @@ [1.0.0-SNAPSHOT] - Update teavm to 0.10.0 - Pixmap now use Gdx2DPixmap -- Add useNewFileHandle set to true as default. Gdx.files.internal/classpath use new class to hold files and add IndexedDB support for Gdx.files.local +- New Gdx.files.internal, classpath and local implementation. Local files uses IndexedDB. - Call dispose when browser closes - Improve clipboard text copy/paste. +- Change default sound/music api to howler.js [1.0.0-b9] - add TeaClassFilter printAllowedClasses() and printExcludedClasses() diff --git a/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java b/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java index 5cbe48f6..a076c726 100644 --- a/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java +++ b/backends/backend-teavm/emu/com/badlogic/gdx/graphics/PixmapEmu.java @@ -89,7 +89,7 @@ public boolean onSuccess(String url, Blob result) { return false; } }; - AssetDownloader.getInstance().load(true, url, AssetType.Image, null, listener); + AssetDownloader.getInstance().load(true, url, AssetType.Binary, null, listener); } public PixmapEmu(FileHandle file) { @@ -97,23 +97,11 @@ public PixmapEmu(FileHandle file) { String path = webFileHandler.path(); TeaApplicationConfiguration config = ((TeaApplication)Gdx.app).getConfig(); - byte[] bytes = null; - if(config.useNewFileHandle) { - if(!file.exists()) { - // Add a way to debug when assets was not loaded in preloader. - throw new GdxRuntimeException("File is null, it does not exist: " + path); - } - bytes = file.readBytes(); - } - else { - Blob object = webFileHandler.preloader.images.get(path); - if(object == null) { - // Add a way to debug when assets was not loaded in preloader. - throw new GdxRuntimeException("File is null, it does not exist: " + path); - } - Int8ArrayWrapper response = object.getData(); - bytes = TypedArrays.toByteArray(response); + if(!file.exists()) { + // Add a way to debug when assets was not loaded in preloader. + throw new GdxRuntimeException("File is null, it does not exist: " + path); } + byte[] bytes = file.readBytes(); nativePixmap = new Gdx2DPixmapEmu(bytes, 0, bytes.length, 0); initPixmapEmu(); } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/DefaultTeaAudio.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/DefaultTeaAudio.java index 3090b9b2..e1ce77d2 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/DefaultTeaAudio.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/DefaultTeaAudio.java @@ -6,17 +6,17 @@ import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; -import com.github.xpenatan.gdx.backends.teavm.webaudio.WebAudioAPIManager; +import com.github.xpenatan.gdx.backends.teavm.webaudio.howler.HowlerAudioManager; /** * @author xpenatan * Port from GWT gdx 1.12.0 */ public class DefaultTeaAudio implements TeaAudio { - private WebAudioAPIManager webAudioAPIManager = null; + private HowlerAudioManager webAudioAPIManager = null; public DefaultTeaAudio() { - webAudioAPIManager = new WebAudioAPIManager(); + webAudioAPIManager = new HowlerAudioManager(); } @Override diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java index b1db2ee1..b831445b 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplication.java @@ -120,12 +120,13 @@ else if(agentInfo.isLinux()) AssetLoaderListener assetListener = new AssetLoaderListener(); input = new TeaInput(this, graphics.canvas); - files = new TeaFiles(config, this, preloader); + files = new TeaFiles(config, this); net = new TeaNet(); logger = new TeaApplicationLogger(); clipboard = new TeaClipboard(); initGdx(); + initSound(); Gdx.app = this; Gdx.graphics = graphics; @@ -456,10 +457,6 @@ public void removeLifecycleListener(LifecycleListener listener) { } } - public String getAssetUrl() { - return preloader.getAssetUrl(); - } - /** @return {@code true} if application runs on a mobile device */ public static boolean isMobileDevice () { // RegEx pattern from detectmobilebrowsers.com (public domain) @@ -489,4 +486,13 @@ public boolean onSuccess(String url, Object result) { } }); } + + private void initSound() { + preloader.loadScript(true, "howler.js", new AssetLoaderListener() { + @Override + public boolean onSuccess(String url, Object result) { + return true; + } + }); + } } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java index 813bbc2d..d4cb5119 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaApplicationConfiguration.java @@ -20,12 +20,6 @@ public class TeaApplicationConfiguration { */ public boolean preloadAssets = true; - /** - * Experimental new Gdx.files.internal and Gdx.files.local implementation - */ - @Deprecated - public boolean useNewFileHandle = true; - /** * The prefix for the browser storage. If you have multiple apps on the same server and want to keep the * data separate for those applications, you will need to set unique prefixes. This is useful if you are @@ -35,7 +29,7 @@ public class TeaApplicationConfiguration { * browser is not shared between the applications. If you leave the storage prefix at "", all the data * and files stored will be shared between the applications. */ - public String storagePrefix = ""; + public String storagePrefix = "db/assets"; /** * Show download logs. diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFileHandle.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFileHandle.java index 70ba055a..cbe4b7f4 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFileHandle.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFileHandle.java @@ -4,13 +4,12 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.StreamUtils; -import com.github.xpenatan.gdx.backends.teavm.filesystem.FileDB; -import com.github.xpenatan.gdx.backends.teavm.preloader.Preloader; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileFilter; +import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; @@ -25,37 +24,33 @@ * @author xpenatan */ public class TeaFileHandle extends FileHandle { - public final Preloader preloader; private final String file; private final FileType type; - private FileDB fileDB; + private TeaFiles teaFiles; - public TeaFileHandle(Preloader preloader, FileDB fileDB, String fileName, FileType type) { + public TeaFileHandle(TeaFiles teaFiles, String fileName, FileType type) { if((type != FileType.Internal) && (type != FileType.Classpath) && (type != FileType.Local)) { throw new GdxRuntimeException("FileType '" + type + "' Not supported in web backend"); } - this.preloader = preloader; this.file = fixSlashes(fileName); this.type = type; - this.fileDB = fileDB; - } - - /** @return The full url to an asset, e.g. http://localhost:8080/assets/data/shotgun.ogg */ - public String getAssetUrl () { - return preloader.getAssetUrl() + preloader.assetNames.get(file, file); + this.teaFiles = teaFiles; } + @Override public String path() { return file; } + @Override public String name() { int index = file.lastIndexOf('/'); if(index < 0) return file; return file.substring(index + 1); } + @Override public String extension() { String name = name(); int dotIndex = name.lastIndexOf('.'); @@ -63,6 +58,7 @@ public String extension() { return name.substring(dotIndex + 1); } + @Override public String nameWithoutExtension() { String name = name(); int dotIndex = name.lastIndexOf('.'); @@ -73,6 +69,7 @@ public String nameWithoutExtension() { /** * @return the path and filename without the extension, e.g. dir/dir2/file.png -> dir/dir2/file */ + @Override public String pathWithoutExtension() { String path = file; int dotIndex = path.lastIndexOf('.'); @@ -80,6 +77,7 @@ public String pathWithoutExtension() { return path.substring(0, dotIndex); } + @Override public FileType type() { return type; } @@ -88,6 +86,7 @@ public FileType type() { * Returns a java.io.File that represents this file handle. Note the returned file will only be usable for * {@link FileType#Absolute} and {@link FileType#External} file handles. */ + @Override public File file() { throw new GdxRuntimeException("Not supported in web backend"); } @@ -97,18 +96,15 @@ public File file() { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public InputStream read() { - if(fileDB != null) { - return fileDB.read(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().read(this); - } - else { - InputStream in = preloader.read(file); - if(in == null) throw new GdxRuntimeException(file + " does not exist"); - return in; + boolean exists = teaFiles.getFileDB(type).exists(this); + if (type == FileType.Classpath || (type == FileType.Internal && !exists) || (type == FileType.Local && !exists)) { + InputStream input = teaFiles.getFileDB(FileType.Classpath).read(this); + if (input == null) throw new GdxRuntimeException("File not found: " + file + " (" + type + ")"); + return input; } + return teaFiles.getFileDB(type).read(this); } /** @@ -116,6 +112,7 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public BufferedInputStream read(int bufferSize) { return new BufferedInputStream(read(), bufferSize); } @@ -125,6 +122,7 @@ public BufferedInputStream read(int bufferSize) { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public Reader reader() { return new InputStreamReader(read()); } @@ -134,6 +132,7 @@ public Reader reader() { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public Reader reader(String charset) { try { return new InputStreamReader(read(), charset); @@ -148,6 +147,7 @@ public Reader reader(String charset) { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public BufferedReader reader(int bufferSize) { return new BufferedReader(reader(), bufferSize); } @@ -157,6 +157,7 @@ public BufferedReader reader(int bufferSize) { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public BufferedReader reader(int bufferSize, String charset) { return new BufferedReader(reader(charset), bufferSize); } @@ -166,6 +167,7 @@ public BufferedReader reader(int bufferSize, String charset) { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public String readString() { return readString(null); } @@ -175,70 +177,9 @@ public String readString() { * * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. */ + @Override public String readString(String charset) { - if(fileDB != null) { - return super.readString(charset); - } - else if(type == FileType.Local) { - // obtain via reader - return super.readString(charset); - } - else { - if(preloader.isText(file)) return preloader.texts.get(file); - try { - return new String(readBytes(), "UTF-8"); - } - catch(UnsupportedEncodingException e) { - return null; - } - } - } - - /** - * Reads the entire file into a byte array. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public byte[] readBytes() { - if(fileDB != null) { - return fileDB.readBytes(this); - } - - int length = (int)length(); - if(length == 0) length = 512; - byte[] buffer = new byte[length]; - int position = 0; - InputStream input = read(); - try { - while(true) { - int count = input.read(buffer, position, buffer.length - position); - if(count == -1) break; - position += count; - if(position == buffer.length) { - // Grow buffer. - byte[] newBuffer = new byte[buffer.length * 2]; - System.arraycopy(buffer, 0, newBuffer, 0, position); - buffer = newBuffer; - } - } - } - catch(IOException ex) { - throw new GdxRuntimeException("Error reading file: " + this, ex); - } - finally { - try { - if(input != null) input.close(); - } - catch(IOException ignored) { - } - } - if(position < buffer.length) { - // Shrink buffer. - byte[] newBuffer = new byte[position]; - System.arraycopy(buffer, 0, newBuffer, 0, position); - buffer = newBuffer; - } - return buffer; + return super.readString(charset); } /** @@ -249,6 +190,7 @@ public byte[] readBytes() { * @param size the number of bytes to read, see {@link #length()} * @return the number of read bytes */ + @Override public int readBytes(byte[] bytes, int offset, int size) { InputStream input = read(); int position = 0; @@ -279,6 +221,7 @@ public int readBytes(byte[] bytes, int offset, int size) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public OutputStream write(boolean append) { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot write to a classpath file: " + file); if(type == FileType.Internal) throw new GdxRuntimeException("Cannot write to an internal file: " + file); @@ -293,16 +236,9 @@ public OutputStream write(boolean append) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public OutputStream write(boolean append, int bufferSize) { - if(fileDB != null) { - return fileDB.write(this, append, bufferSize); - } - else if(type == FileType.Local) { - return FileDB.getInstance().write(this, append, bufferSize); - } - else { - throw new GdxRuntimeException("Cannot write to the given file."); - } + return teaFiles.getFileDB(type).write(this, append, bufferSize); } /** @@ -313,6 +249,7 @@ else if(type == FileType.Local) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public void write(InputStream input, boolean append) { OutputStream output = null; try { @@ -336,6 +273,7 @@ public void write(InputStream input, boolean append) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public Writer writer(boolean append) { return writer(append, null); } @@ -348,6 +286,7 @@ public Writer writer(boolean append) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public Writer writer(boolean append, String charset) { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot write to a classpath file: " + file); if(type == FileType.Internal) throw new GdxRuntimeException("Cannot write to an internal file: " + file); @@ -366,6 +305,7 @@ public Writer writer(boolean append, String charset) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public void writeString(String string, boolean append) { writeString(string, append, null); } @@ -378,6 +318,7 @@ public void writeString(String string, boolean append) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public void writeString(String string, boolean append, String charset) { Writer writer = null; try { @@ -399,6 +340,7 @@ public void writeString(String string, boolean append, String charset) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public void writeBytes(byte[] bytes, boolean append) { OutputStream output = write(append); try { @@ -419,6 +361,7 @@ public void writeBytes(byte[] bytes, boolean append) { * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or * {@link FileType#Internal} file, or if it could not be written. */ + @Override public void writeBytes(byte[] bytes, int offset, int length, boolean append) { OutputStream output = write(append); try { @@ -439,17 +382,10 @@ public void writeBytes(byte[] bytes, int offset, int length, boolean append) { * * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ + @Override public FileHandle[] list() { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - if(fileDB != null) { - return fileDB.list(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().list(this); - } - else { - return preloader.list(file); - } + return teaFiles.getFileDB(type).list(this); } /** @@ -459,17 +395,10 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ + @Override public FileHandle[] list(FileFilter filter) { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - if(fileDB != null) { - return fileDB.list(this, filter); - } - else if(type == FileType.Local) { - return FileDB.getInstance().list(this, filter); - } - else { - return preloader.list(file); - } + return teaFiles.getFileDB(type).list(this, filter); } /** @@ -479,17 +408,10 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ + @Override public FileHandle[] list(FilenameFilter filter) { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - if(fileDB != null) { - return fileDB.list(this, filter); - } - else if(type == FileType.Local) { - return FileDB.getInstance().list(this, filter); - } - else { - return preloader.list(file, filter); - } + return teaFiles.getFileDB(type).list(this, filter); } /** @@ -499,17 +421,10 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. */ + @Override public FileHandle[] list(String suffix) { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - if(fileDB != null) { - return fileDB.list(this, suffix); - } - else if(type == FileType.Local) { - return FileDB.getInstance().list(this, suffix); - } - else { - return preloader.list(file, suffix); - } + return teaFiles.getFileDB(type).list(this, suffix); } /** @@ -517,17 +432,10 @@ else if(type == FileType.Local) { * {@link FileType#Internal} handle to an empty directory will return false. On the desktop, an {@link FileType#Internal} * handle to a directory on the classpath will return false. */ + @Override public boolean isDirectory() { if (type == FileType.Classpath) return false; - if(fileDB != null) { - return fileDB.isDirectory(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().isDirectory(this); - } - else { - return preloader.isDirectory(file); - } + return teaFiles.getFileDB(type).isDirectory(this); } /** @@ -536,18 +444,21 @@ else if(type == FileType.Local) { * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} and the child * doesn't exist. */ + @Override public FileHandle child(String name) { - return new TeaFileHandle(preloader, fileDB, (file.isEmpty() ? "" : (file + (file.endsWith("/") ? "" : "/"))) + name, + return new TeaFileHandle(teaFiles, (file.isEmpty() ? "" : (file + (file.endsWith("/") ? "" : "/"))) + name, type); } + @Override public FileHandle parent() { int index = file.lastIndexOf("/"); String dir = ""; if(index > 0) dir = file.substring(0, index); - return new TeaFileHandle(preloader, fileDB, dir, type); + return new TeaFileHandle(teaFiles, dir, type); } + @Override public FileHandle sibling(String name) { return parent().child(fixSlashes(name)); } @@ -555,38 +466,32 @@ public FileHandle sibling(String name) { /** * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ + @Override public void mkdirs() { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot mkdirs with a classpath file: " + file); if(type == FileType.Internal) throw new GdxRuntimeException("Cannot mkdirs with an internal file: " + file); - if(fileDB != null) { - fileDB.mkdirs(this); - } - else if(type == FileType.Local) { - FileDB.getInstance().mkdirs(this); - } - else { - throw new GdxRuntimeException("Cannot mkdirs for non-local file: " + file); - } + teaFiles.getFileDB(type).mkdirs(this); } public void mkdirsInternal() { - fileDB.mkdirs(this); + teaFiles.getFileDB(type).mkdirs(this); } /** * Returns true if the file exists. On Android, a {@link FileType#Classpath} or {@link FileType#Internal} handle to a * directory will always return false. */ + @Override public boolean exists() { - if(fileDB != null) { - return fileDB.exists(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().exists(this); - } - else { - return preloader.contains(file); + boolean exists = teaFiles.getFileDB(type).exists(this); + switch (type) { + case Internal: + if (exists) return true; + // Fall through. + case Classpath: + return teaFiles.getFileDB(FileType.Classpath).exists(this); } + return exists; } /** @@ -594,18 +499,11 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ + @Override public boolean delete() { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot delete a classpath file: " + file); if(type == FileType.Internal) throw new GdxRuntimeException("Cannot delete an internal file: " + file); - if(fileDB != null) { - return fileDB.delete(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().delete(this); - } - else { - throw new GdxRuntimeException("Cannot delete a non-local file: " + file); - } + return teaFiles.getFileDB(type).delete(this); } /** @@ -613,16 +511,11 @@ else if(type == FileType.Local) { * * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. */ + @Override public boolean deleteDirectory() { if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot delete a classpath file: " + file); if(type == FileType.Internal) throw new GdxRuntimeException("Cannot delete an internal file: " + file); - if(fileDB != null) { - return fileDB.deleteDirectory(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().deleteDirectory(this); - } - throw new GdxRuntimeException("Cannot delete directory (missing implementation): " + file); + return teaFiles.getFileDB(type).deleteDirectory(this); } /** @@ -636,6 +529,7 @@ else if(type == FileType.Local) { * @throws GdxRuntimeException if the destination file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file, * or copying failed. */ + @Override public void copyTo(FileHandle dest) { if(!isDirectory()) { if(dest.isDirectory()) dest = dest.child(name()); @@ -658,6 +552,7 @@ public void copyTo(FileHandle dest) { * @throws GdxRuntimeException if the source or destination file handle is a {@link FileType#Classpath} or * {@link FileType#Internal} file. */ + @Override public void moveTo(FileHandle dest) { switch(type) { case Classpath: { @@ -667,12 +562,12 @@ public void moveTo(FileHandle dest) { throw new GdxRuntimeException("Cannot move an internal file: " + file); } } - if(fileDB != null && !dest.exists()) { + if(!dest.exists()) { FileHandle destParent = dest.parent(); FileHandle thisParent = parent(); if(thisParent.equals(destParent)) { // Rename if same parent. Similar to absolute from desktop - fileDB.rename(this, (TeaFileHandle)dest); + teaFiles.getFileDB(type).rename(this, (TeaFileHandle)dest); return; } } @@ -686,16 +581,9 @@ public void moveTo(FileHandle dest) { * Returns the length in bytes of this file, or 0 if this file is a directory, does not exist, or the size cannot otherwise be * determined. */ + @Override public long length() { - if(fileDB != null) { - return fileDB.length(this); - } - else if(type == FileType.Local) { - return FileDB.getInstance().length(this); - } - else { - return preloader.length(file); - } + return teaFiles.getFileDB(type).length(this); } /** @@ -703,10 +591,12 @@ else if(type == FileType.Local) { * for {@link FileType#Classpath} files. On Android, zero is returned for {@link FileType#Internal} files. On the desktop, zero * is returned for {@link FileType#Internal} files on the classpath. */ + @Override public long lastModified() { return 0; } + @Override public String toString() { return file; } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java index a21813d2..1563dca2 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/TeaFiles.java @@ -2,27 +2,41 @@ import com.badlogic.gdx.Files; import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.GdxRuntimeException; import com.github.xpenatan.gdx.backends.teavm.filesystem.FileDB; -import com.github.xpenatan.gdx.backends.teavm.filesystem.types.InternalDBStorage; +import com.github.xpenatan.gdx.backends.teavm.filesystem.MemoryFileStorage; +import com.github.xpenatan.gdx.backends.teavm.filesystem.types.ClasspathStorage; +import com.github.xpenatan.gdx.backends.teavm.filesystem.types.InternalStorage; import com.github.xpenatan.gdx.backends.teavm.filesystem.types.LocalDBStorage; -import com.github.xpenatan.gdx.backends.teavm.preloader.Preloader; /** * @author xpenatan */ public class TeaFiles implements Files { - final Preloader preloader; + public MemoryFileStorage internalStorage; + public MemoryFileStorage classpathStorage; + public MemoryFileStorage localStorage; + public String storagePath; - public InternalDBStorage internalStorage; - public LocalDBStorage localStorage; + public TeaFiles(TeaApplicationConfiguration config, TeaApplication teaApplication) { + this.internalStorage = new InternalStorage(); + this.classpathStorage = new ClasspathStorage(); + this.localStorage = new LocalDBStorage(teaApplication); + storagePath = config.storagePrefix; + } - public TeaFiles(TeaApplicationConfiguration config, TeaApplication teaApplication, Preloader preloader) { - this.preloader = preloader; - if(config.useNewFileHandle) { - this.internalStorage = new InternalDBStorage(); - this.localStorage = new LocalDBStorage(teaApplication); + public FileDB getFileDB(FileType type) { + if(type == FileType.Internal) { + return internalStorage; + } + else if(type == FileType.Classpath) { + return classpathStorage; } + else if(type == FileType.Local) { + return localStorage; + } + return null; } @Override @@ -37,32 +51,32 @@ else if(type == FileType.Classpath) { else if(type == FileType.Local) { return local(path); } - return new TeaFileHandle(preloader, null, path, type); + throw new GdxRuntimeException("Type " + type + " is not supported"); } @Override public FileHandle classpath(String path) { - return new TeaFileHandle(preloader, internalStorage, path, FileType.Classpath); + return new TeaFileHandle(this, path, FileType.Classpath); } @Override public FileHandle internal(String path) { - return new TeaFileHandle(preloader, internalStorage, path, FileType.Internal); + return new TeaFileHandle(this, path, FileType.Internal); } @Override - public FileHandle external(String path) { - return new TeaFileHandle(preloader, null, path, FileType.External); + public FileHandle local(String path) { + return new TeaFileHandle(this, path, FileType.Local); } @Override - public FileHandle absolute(String path) { - return new TeaFileHandle(preloader, null, path, FileType.Absolute); + public FileHandle external(String path) { + throw new GdxRuntimeException("Type external is not supported"); } @Override - public FileHandle local(String path) { - return new TeaFileHandle(preloader, localStorage, path, FileType.Local); + public FileHandle absolute(String path) { + throw new GdxRuntimeException("Type absolute is not supported"); } @Override @@ -77,7 +91,7 @@ public boolean isExternalStorageAvailable() { @Override public String getLocalStoragePath() { - return FileDB.getInstance().getPath(); + return storagePath; } @Override diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetFileHandle.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetFileHandle.java new file mode 100644 index 00000000..4217e0a6 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetFileHandle.java @@ -0,0 +1,42 @@ +package com.github.xpenatan.gdx.backends.teavm.config; + +import com.badlogic.gdx.Files.FileType; +import com.badlogic.gdx.files.FileHandle; +import java.io.File; + +public class AssetFileHandle extends FileHandle { + + FileType copyType; + + public static AssetFileHandle createHandle(String fileName, FileType type) { + return new AssetFileHandle(fileName, type, type); + } + + public static AssetFileHandle createCopyHandle(File file, FileType type) { + return new AssetFileHandle(file, FileType.Absolute, type); + } + + public AssetFileHandle(String fileName) { + this(new File(fileName), FileType.Absolute, FileType.Internal); + } + + private AssetFileHandle(String fileName, FileType type, FileType copyType) { + this(new File(fileName), type, copyType); + } + + private AssetFileHandle(File file, FileType type, FileType copyType) { + this.type = type; + this.copyType = copyType; + this.file = file; + } + + public FileHandle child (String name) { + if (file.getPath().isEmpty()) return new AssetFileHandle(new File(name), super.type(), copyType); + return new AssetFileHandle(new File(file, name), super.type(), copyType); + } + + @Override + public FileType type() { + return copyType; + } +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java index 106cb723..287ce847 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/AssetsCopy.java @@ -1,18 +1,16 @@ package com.github.xpenatan.gdx.backends.teavm.config; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; import com.github.xpenatan.gdx.backends.teavm.TeaClassLoader; import com.github.xpenatan.gdx.backends.teavm.preloader.AssetFilter; import com.github.xpenatan.gdx.backends.teavm.preloader.AssetType; import com.github.xpenatan.gdx.backends.teavm.preloader.DefaultAssetFilter; -import com.github.xpenatan.gdx.backends.teavm.preloader.FileWrapper; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLConnection; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; @@ -26,35 +24,41 @@ import java.util.List; import java.util.Map.Entry; import java.util.stream.Stream; +import com.badlogic.gdx.Files.FileType; /** * @author xpenatan */ public class AssetsCopy { + private static class Asset { - FileWrapper file; + FileHandle file; AssetType type; - public Asset(FileWrapper file, AssetType type) { + public Asset(FileHandle file, AssetType type) { this.file = file; this.type = type; } } - public static void copy(ArrayList assetsPaths, AssetFilter filter, String assetsOutputPath, boolean generateTextFile) { - copy(null, null, assetsPaths, null, assetsOutputPath, generateTextFile, false); + public static void copyResources(TeaClassLoader classLoader, List classPathAssetsFiles, String assetsOutputPath, boolean generateTextFile, boolean append) { + copy(classLoader, classPathAssetsFiles, true, null, null, assetsOutputPath, generateTextFile, append); + } + + public static void copy(TeaClassLoader classLoader, List classPathAssetsFiles, ArrayList assetsPaths, AssetFilter filter, String assetsOutputPath, boolean generateTextFile, boolean append) { + copy(classLoader, classPathAssetsFiles, false, assetsPaths, filter, assetsOutputPath, generateTextFile, append); } - public static void copy(TeaClassLoader classloader, List classPathAssetsFiles, ArrayList assetsPaths, AssetFilter filter, String assetsOutputPath, boolean generateTextFile, boolean append) { - assetsOutputPath = assetsOutputPath.replace("\\", "/"); - FileWrapper target = new FileWrapper(assetsOutputPath); + private static void copy(TeaClassLoader classloader, List classPathAssetsFiles, boolean isResources, ArrayList assetsPaths, AssetFilter filter, String assetsOutputPath, boolean generateTextFile, boolean append) { + FileHandle target = new FileHandle(assetsOutputPath); + assetsOutputPath = target.path(); ArrayList assets = new ArrayList(); AssetFilter defaultAssetFilter = filter != null ? filter : new DefaultAssetFilter(); if(assetsPaths != null && assetsPaths.size() > 0) { TeaBuilder.log("Copying assets from:"); for(int i = 0; i < assetsPaths.size(); i++) { - String path = assetsPaths.get(i).getAbsolutePath(); - FileWrapper source = new FileWrapper(path); + FileHandle source = assetsPaths.get(i); + String path = source.path(); TeaBuilder.log(path); copyDirectory(source, target, defaultAssetFilter, assets); } @@ -82,11 +86,12 @@ public static void copy(TeaClassLoader classloader, List classPathAssets TeaBuilder.log(classpathFile); InputStream is = classloader.getResourceAsStream(classpathFile); if(is != null) { - FileWrapper dest = target.child(classpathFile); + FileHandle dest = target.child(classpathFile); dest.write(is, false); String destPath = dest.path(); if(!destPath.endsWith(".js") && !destPath.endsWith(".wasm")) { - assets.add(new Asset(dest, AssetFilter.getType(destPath))); + AssetFileHandle dest2 = AssetFileHandle.createCopyHandle(dest.file(), FileType.Classpath); + assets.add(new Asset(dest2, AssetFilter.getType(destPath))); } is.close(); } @@ -119,23 +124,35 @@ public static void copy(TeaClassLoader classloader, List classPathAssets for(Entry> bundle : bundles.entrySet()) { StringBuffer buffer = new StringBuffer(); for(Asset asset : bundle.getValue()) { - String path = asset.file.path().replace('\\', '/'); - path = path.replace(assetsOutputPath, ""); - if(path.startsWith("/")) path = path.substring(1); - buffer.append(asset.type.code); - buffer.append(":"); - buffer.append(path); - buffer.append(":"); - buffer.append(asset.file.isDirectory() ? 0 : asset.file.length()); - buffer.append(":"); - String mimetype = URLConnection.guessContentTypeFromName(asset.file.name()); - buffer.append(mimetype == null ? "application/unknown" : mimetype); - buffer.append("\n"); + setupPreloadAssetFileFormat(asset, buffer, assetsOutputPath); } target.child(bundle.getKey() + ".txt").writeString(buffer.toString(), append); } } + private static void setupPreloadAssetFileFormat(Asset asset, StringBuffer buffer, String assetsOutputPath) { + FileHandle fileHandle = asset.file; + FileType type = fileHandle.type(); + String path = fileHandle.path(); + path = path.replace(assetsOutputPath, ""); + String fileTypeStr = "i"; + if(type == FileType.Local) { + fileTypeStr = "l"; + } + else if(type == FileType.Classpath) { + fileTypeStr = "c"; + } + + buffer.append(fileTypeStr); + buffer.append(":"); + buffer.append(asset.type.code); + buffer.append(":"); + buffer.append(path); + buffer.append(":"); + buffer.append(asset.file.isDirectory() ? 0 : asset.file.length()); + buffer.append("\n"); + } + private static void addDirectoryClassPathFiles(List classPathFiles) { ArrayList folderFilePaths = new ArrayList<>(); for(int k = 0; k < classPathFiles.size(); k++) { @@ -229,7 +246,7 @@ private static void addDirectoryClassPathFiles(List classPathFiles) { classPathFiles.addAll(set); } - private static void copyFile(FileWrapper source, FileWrapper dest, AssetFilter filter, ArrayList assets) { + private static void copyFile(FileHandle source, FileHandle dest, AssetFilter filter, ArrayList assets) { if(!filter.accept(dest.path(), false)) return; try { assets.add(new Asset(dest, AssetFilter.getType(dest.path()))); @@ -243,16 +260,20 @@ private static void copyFile(FileWrapper source, FileWrapper dest, AssetFilter f } } - private static void copyDirectory(FileWrapper sourceDir, FileWrapper destDir, AssetFilter filter, ArrayList assets) { - if(!filter.accept(destDir.path(), true)) return; - assets.add(new Asset(destDir, AssetType.Directory)); + private static void copyDirectory(FileHandle sourceDir, FileHandle destDir, AssetFilter filter, ArrayList assets) { + String destPath = destDir.path(); + if(!filter.accept(destPath, true)) return; destDir.mkdirs(); - FileWrapper[] files = sourceDir.list(); + FileHandle[] files = sourceDir.list(); for(int i = 0, n = files.length; i < n; i++) { - FileWrapper srcFile = files[i]; - FileWrapper destFile = destDir.child(srcFile.name()); - if(srcFile.isDirectory()) + FileHandle srcFile = files[i]; + FileHandle destFile1 = destDir.child(srcFile.name()); + // Destination type is copied from source type + FileHandle destFile = AssetFileHandle.createCopyHandle(destFile1.file(), srcFile.type()); + if(srcFile.isDirectory()) { + assets.add(new Asset(destFile, AssetType.Directory)); copyDirectory(srcFile, destFile, filter, assets); + } else copyFile(srcFile, destFile, filter, assets); } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuildConfiguration.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuildConfiguration.java index dcb292f3..e05a31e3 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuildConfiguration.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuildConfiguration.java @@ -1,9 +1,9 @@ package com.github.xpenatan.gdx.backends.teavm.config; import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.files.FileHandle; import com.github.xpenatan.gdx.backends.teavm.TeaLauncher; import com.github.xpenatan.gdx.backends.teavm.preloader.AssetFilter; -import java.io.File; import java.net.URL; import java.util.ArrayList; @@ -13,7 +13,7 @@ public class TeaBuildConfiguration { public AssetFilter assetFilter = null; - public ArrayList assetsPath = new ArrayList<>(); + public ArrayList assetsPath = new ArrayList<>(); public ArrayList additionalAssetsClasspathFiles = new ArrayList<>(); private String mainApplicationClass; @@ -89,7 +89,7 @@ public String getWebAppPath() { return webappPath; } - public boolean assetsPath(ArrayList paths) { + public boolean assetsPath(ArrayList paths) { paths.addAll(assetsPath); return true; } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuilder.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuilder.java index 82c758e6..0a0815d1 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuilder.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaBuilder.java @@ -346,41 +346,6 @@ private static void configClasspath(TeaBuildConfiguration configuration, ArrayLi } } - private static void assetsDefaultClasspath(ArrayList filePath) { - //TODO remove if its working correctly without these assets -// filePath.add("com/badlogic/gdx/graphics/g3d/particles/"); -// filePath.add("com/badlogic/gdx/graphics/g3d/shaders/"); -// filePath.add("com/badlogic/gdx/utils/arial-15.fnt"); // Cannot be utils folder for now because its trying to copy from emu folder and not core gdx classpath -// filePath.add("com/badlogic/gdx/utils/arial-15.png"); -// -// filePath.add("com/badlogic/gdx/utils/lsans-15.fnt"); -// filePath.add("com/badlogic/gdx/utils/lsans-15.png"); - -// filePath.add("net/mgsx/gltf/shaders/brdfLUT.png"); -// filePath.add("net/mgsx/gltf/shaders/default.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/default.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/gdx-pbr.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/gdx-pbr.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/depth.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/depth.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/emissive-only.fs"); -// filePath.add("net/mgsx/gltf/shaders/ibl-sun.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/ibl-sun.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/skybox.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/skybox.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/compat.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/compat.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/env.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/functions.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/ibl.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/iridescence.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/lights.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/material.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/pbr.fs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/pbr.vs.glsl"); -// filePath.add("net/mgsx/gltf/shaders/pbr/shadows.glsl"); - } - private static ACCEPT_STATE acceptPath(String path) { ACCEPT_STATE isValid = ACCEPT_STATE.NO_MATCH; if(path.contains("junit")) @@ -524,47 +489,20 @@ public TeaVMProgressFeedback progressReached(int i) { } public static void configAssets(TeaClassLoader classLoader, TeaBuildConfiguration configuration, String webappDirectory, String webappName, ArrayList acceptedURL) { - ArrayList webappAssetsFiles = new ArrayList<>(); - webappAssetsFiles.add(webappName); TeaBuilder.logHeader("COPYING ASSETS"); - boolean shouldUseDefaultHtmlIndex = configuration.shouldUseDefaultHtmlIndex(); - if(shouldUseDefaultHtmlIndex) { - AssetsCopy.copy(classLoader, webappAssetsFiles, new ArrayList<>(), null, webappDirectory, false, false); - TeaBuilder.log(""); - } - - String scriptsOutputPath = webappDirectory + File.separator + webappName; - String assetsOutputPath = scriptsOutputPath + File.separator + "assets"; + String webappOutputPath = webappDirectory + File.separator + webappName; + String assetsOutputPath = webappOutputPath + File.separator + "assets"; - AssetFilter filter = configuration.assetFilter(); - ArrayList assetsPaths = new ArrayList<>(); + ArrayList assetsPaths = new ArrayList<>(); ArrayList classPathAssetsFiles = new ArrayList<>(); - assetsDefaultClasspath(classPathAssetsFiles); + AssetFilter filter = configuration.assetFilter(); + boolean shouldUseDefaultHtmlIndex = configuration.shouldUseDefaultHtmlIndex(); if(shouldUseDefaultHtmlIndex) { - File indexFile = new File(scriptsOutputPath + File.separator + "index.html"); - FileHandle handler = new FileHandle(indexFile); - String indexHtmlStr = handler.readString(); - - String logo = configuration.getLogoPath(); - String htmlLogo = "assets/" + logo; - boolean showLoadingLogo = configuration.isShowLoadingLogo(); - - indexHtmlStr = indexHtmlStr.replace("%TITLE%", configuration.getHtmlTitle()); - indexHtmlStr = indexHtmlStr.replace("%WIDTH%", configuration.getHtmlWidth()); - indexHtmlStr = indexHtmlStr.replace("%HEIGHT%", configuration.getHtmlHeight()); - indexHtmlStr = indexHtmlStr.replace("%ARGS%", configuration.getMainClassArgs()); - indexHtmlStr = indexHtmlStr.replace( - "%LOGO%", showLoadingLogo ? "" : "" - ); - - handler.writeString(indexHtmlStr, false); - - if(showLoadingLogo) { - classPathAssetsFiles.add(logo); - } + useDefaultHTMLIndexFile(classLoader, configuration, webappDirectory, webappName, webappOutputPath); } + ArrayList additionalAssetClasspath = configuration.getAdditionalAssetClasspath(); classPathAssetsFiles.addAll(additionalAssetClasspath); boolean generateAssetPaths = configuration.assetsPath(assetsPaths); @@ -572,7 +510,39 @@ public static void configAssets(TeaClassLoader classLoader, TeaBuildConfiguratio // Copy assets from resources List resources = TeaVMResourceProperties.getResources(acceptedURL); - AssetsCopy.copy(classLoader, resources, null, null, assetsOutputPath, true, true); + AssetsCopy.copyResources(classLoader, resources, assetsOutputPath, true, true); + TeaBuilder.log(""); + } + + private static void useDefaultHTMLIndexFile(TeaClassLoader classLoader, TeaBuildConfiguration configuration, String webappDirectory, String webappName, String webappOutputPath) { + ArrayList webappAssetsFiles = new ArrayList<>(); + webappAssetsFiles.add(webappName); + // Copy webapp folder from resources to destination + AssetsCopy.copyResources(classLoader, webappAssetsFiles, webappDirectory, false, false); TeaBuilder.log(""); + + File indexFile = new File(webappOutputPath + File.separator + "index.html"); + FileHandle handler = new FileHandle(indexFile); + String indexHtmlStr = handler.readString(); + + String logo = configuration.getLogoPath(); + String htmlLogo = logo; + boolean showLoadingLogo = configuration.isShowLoadingLogo(); + + indexHtmlStr = indexHtmlStr.replace("%TITLE%", configuration.getHtmlTitle()); + indexHtmlStr = indexHtmlStr.replace("%WIDTH%", configuration.getHtmlWidth()); + indexHtmlStr = indexHtmlStr.replace("%HEIGHT%", configuration.getHtmlHeight()); + indexHtmlStr = indexHtmlStr.replace("%ARGS%", configuration.getMainClassArgs()); + indexHtmlStr = indexHtmlStr.replace( + "%LOGO%", showLoadingLogo ? "" : "" + ); + + handler.writeString(indexHtmlStr, false); + + if(showLoadingLogo) { + ArrayList logoAsset = new ArrayList<>(); + logoAsset.add(logo); + AssetsCopy.copyResources(classLoader, logoAsset, webappOutputPath, false, false); + } } } diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaVMResourceProperties.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaVMResourceProperties.java index 2f179325..05c09655 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaVMResourceProperties.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/config/TeaVMResourceProperties.java @@ -1,5 +1,6 @@ package com.github.xpenatan.gdx.backends.teavm.config; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -20,6 +21,7 @@ import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import com.badlogic.gdx.Files.FileType; public class TeaVMResourceProperties { private static final String OPTION_ADDITIONAL_RESOURCES = "resources"; diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/dom/typedarray/TypedArrays.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/dom/typedarray/TypedArrays.java index 22ca5044..105d90d3 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/dom/typedarray/TypedArrays.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/dom/typedarray/TypedArrays.java @@ -205,7 +205,7 @@ public static ArrayBufferViewWrapper getTypedArray(FloatBuffer buffer) { @JSBody(params = {"buffer"}, script = "" + "return buffer;") - private static native ArrayBufferViewWrapper getTypedArray(@JSByRef() byte[] buffer); + public static native ArrayBufferViewWrapper getTypedArray(@JSByRef() byte[] buffer); @JSBody(params = {"buffer"}, script = "" + "return buffer;") diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDB.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDB.java index fe34a9e8..a2f94c00 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDB.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDB.java @@ -1,12 +1,9 @@ package com.github.xpenatan.gdx.backends.teavm.filesystem; -import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; -import com.github.xpenatan.gdx.backends.teavm.TeaApplication; import com.github.xpenatan.gdx.backends.teavm.TeaFileHandle; -import com.github.xpenatan.gdx.backends.teavm.preloader.Preloader; import java.io.ByteArrayOutputStream; import java.io.FileFilter; import java.io.FilenameFilter; @@ -23,22 +20,6 @@ */ public abstract class FileDB { - /** - * The singleton instance as we only have one file system DB. - */ - private static FileDB INSTANCE = null; - - protected FileDB() { - // hiding the constructor - } - - public static FileDB getInstance() { - if(INSTANCE == null) { - INSTANCE = new FileDBManager(); - } - return INSTANCE; - } - public abstract InputStream read(TeaFileHandle file); public abstract byte[] readBytes(TeaFileHandle file); @@ -83,18 +64,13 @@ public final FileHandle[] list(TeaFileHandle file) { String[] paths = paths(file); FileHandle[] files = new TeaFileHandle[paths.length]; - Preloader preloader = null; - if(Gdx.app != null) { - if(Gdx.app instanceof TeaApplication) { - preloader = ((TeaApplication)Gdx.app).getPreloader(); - } - } for(int i = 0; i < paths.length; i++) { String path = paths[i]; if((path.length() > 0) && (path.charAt(path.length() - 1) == '/')) { path = path.substring(0, path.length() - 1); } - files[i] = new TeaFileHandle(preloader, this, path, file.type()); + + files[i] = Gdx.files.getFileHandle(path, file.type()); } return files; } @@ -138,6 +114,4 @@ public final FileHandle[] list(TeaFileHandle file, String suffix) { public abstract long length(TeaFileHandle file); public abstract void rename(TeaFileHandle source, TeaFileHandle target); - - public abstract String getPath(); -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBManager.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBManager.java deleted file mode 100644 index 2fa33e26..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBManager.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.filesystem; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.GdxRuntimeException; -import com.github.xpenatan.gdx.backends.teavm.TeaApplication; -import com.github.xpenatan.gdx.backends.teavm.TeaApplicationConfiguration; -import com.github.xpenatan.gdx.backends.teavm.TeaFileHandle; - -import java.io.InputStream; - -/** - * Stores data either in persistent storage or memory. - * - * @author noblemaster - */ -public final class FileDBManager extends FileDB { - - /** - * Persistent local store for smaller files. - */ - private final FileDBStorage localStorage; - /** - * Memory store for larger files. Will be wiped when the page is newly loaded. - */ - private final FileDBStorage memory; - - FileDBManager() { - TeaApplicationConfiguration config = ((TeaApplication)Gdx.app).getConfig(); - String storagePrefix = config.storagePrefix; - localStorage = new FileDBStorage(new StoreLocal(storagePrefix)); - memory = new FileDBStorage(new StoreMemory()); - } - - @Override - public InputStream read(TeaFileHandle file) { - if(memory.exists(file)) { - return memory.read(file); - } - else { - return localStorage.read(file); - } - } - - @Override - public byte[] readBytes(TeaFileHandle file) { - return new byte[0]; - } - - @Override - protected void writeInternal(TeaFileHandle file, byte[] data, boolean append, int expectedLength) { - // write larger files into memory: up to 16.384kb into local storage (permanent) - int localStorageMax = 16384; - if((data.length >= localStorageMax) || (append && (expectedLength >= localStorageMax))) { - // write to memory... - memory.writeInternal(file, data, append, expectedLength); - localStorage.delete(file); - } - else { - localStorage.writeInternal(file, data, append, expectedLength); - memory.delete(file); - } - } - - @Override - protected String[] paths(TeaFileHandle file) { - // combine & return the paths of memory & local storage - String[] pathsMemory = memory.paths(file); - String[] pathsLocalStorage = localStorage.paths(file); - String[] paths = new String[pathsMemory.length + pathsLocalStorage.length]; - System.arraycopy(pathsMemory, 0, paths, 0, pathsMemory.length); - System.arraycopy(pathsLocalStorage, 0, paths, pathsMemory.length, pathsLocalStorage.length); - return paths; - } - - @Override - public boolean isDirectory(TeaFileHandle file) { - if(memory.exists(file)) { - return memory.isDirectory(file); - } - else { - return localStorage.isDirectory(file); - } - } - - @Override - public void mkdirs(TeaFileHandle file) { - localStorage.mkdirs(file); - } - - @Override - public boolean exists(TeaFileHandle file) { - return memory.exists(file) || localStorage.exists(file); - } - - @Override - public boolean delete(TeaFileHandle file) { - if(memory.exists(file)) { - return memory.delete(file); - } - else { - return localStorage.delete(file); - } - } - - @Override - public boolean deleteDirectory(TeaFileHandle file) { - throw new GdxRuntimeException("Cannot delete directory (missing implementation): " + file); - } - - @Override - public long length(TeaFileHandle file) { - if(memory.exists(file)) { - return memory.length(file); - } - else { - return localStorage.length(file); - } - } - - @Override - public void rename(TeaFileHandle source, TeaFileHandle target) { - if(memory.exists(source)) { - memory.rename(source, target); - } - else { - localStorage.rename(source, target); - } - } - - @Override - public String getPath() { - return null; - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBStorage.java deleted file mode 100644 index 6a5479eb..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/FileDBStorage.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.filesystem; - -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.GdxRuntimeException; -import com.github.xpenatan.gdx.backends.teavm.TeaFileHandle; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -/** - * Local storage based file system. Stays persistent but is limited to about 2.5-5MB in general. - * - * @author noblemaster - */ -final class FileDBStorage extends FileDB { - - /** - * Our files stored and encoded as Base64. Directories with "d:..." and regular files stored as "f:...". - */ - private final Store store; - - private static final String ID_FOR_FILE = "file-f:"; - private static final String ID_FOR_DIR = "file-d:"; - /** - * The ID length should match both for files and directories. - */ - private static final int ID_LENGTH = ID_FOR_FILE.length(); - - FileDBStorage(Store store) { - this.store = store; - } - - @Override - public InputStream read(TeaFileHandle file) { - // we obtain the stored byte array - String data = store.getItem(ID_FOR_FILE + file.path()); - try { - return new ByteArrayInputStream(HEXCoder.decode(data)); - } - catch(RuntimeException e) { - // something corrupted: we remove it & re-throw the error - store.removeItem(ID_FOR_FILE + file.path()); - throw e; - } - } - - @Override - public byte[] readBytes(TeaFileHandle file) { - return new byte[0]; - } - - @Override - public void writeInternal(TeaFileHandle file, byte[] data, boolean append, int expectedLength) { - String value = HEXCoder.encode(data); - - // append to existing as needed - if(append) { - String dataCurrent = store.getItem(ID_FOR_FILE + file.path()); - if(dataCurrent != null) { - value = dataCurrent + value; - } - } - store.setItem(ID_FOR_FILE + file.path(), value); - } - - @Override - public String[] paths(TeaFileHandle file) { - String dir = file.path() + "/"; - int length = store.getLength(); - Array paths = new Array(length); - for(int i = 0; i < length; i++) { - // cut the identifier for files and directories and add to path list - String key = store.key(i); - if ((key != null) && ((key.startsWith(ID_FOR_DIR) || key.startsWith(ID_FOR_FILE)))) { - String path = key.substring(ID_LENGTH); - if(path.startsWith(dir)) { - paths.add(path); - } - } - } - return paths.toArray(String.class); - } - - @Override - public boolean isDirectory(TeaFileHandle file) { - return store.getItem(ID_FOR_DIR + file.path()) != null; - } - - @Override - public void mkdirs(TeaFileHandle file) { - // add for current path if not already a file! - if(store.getItem(ID_FOR_FILE + file.path()) == null) { - store.setItem(ID_FOR_DIR + file.path(), ""); - } - - // do for parent directories also - file = (TeaFileHandle)file.parent(); - while(file.path().length() > 0) { - store.setItem(ID_FOR_DIR + file.path(), ""); - file = (TeaFileHandle)file.parent(); - } - } - - @Override - public boolean exists(TeaFileHandle file) { - // check if either a file or directory entry exists - return (store.getItem(ID_FOR_DIR + file.path()) != null) || - (store.getItem(ID_FOR_FILE + file.path()) != null); - } - - @Override - public boolean delete(TeaFileHandle file) { - store.removeItem(ID_FOR_DIR + file.path()); - store.removeItem(ID_FOR_FILE + file.path()); - return true; - } - - @Override - public boolean deleteDirectory(TeaFileHandle file) { - throw new GdxRuntimeException("Cannot delete directory (missing implementation): " + file); - } - - @Override - public long length(TeaFileHandle file) { - // this is somewhat slow - String data = store.getItem(ID_FOR_FILE + file.path()); - if (data != null) { - // 2 HEX characters == 1 byte - return data.length() / 2; - } - else { - // no data available - return 0L; - } - } - - @Override - public void rename(TeaFileHandle source, TeaFileHandle target) { - // assuming file (not directory) - String data = store.getItem(ID_FOR_FILE + source.path()); - store.removeItem(ID_FOR_FILE + source.path()); - store.setItem(ID_FOR_FILE + target.path(), data); - } - - @Override - public String getPath() { - return null; - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/MemoryFileStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/MemoryFileStorage.java index 29afab07..32bd36bc 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/MemoryFileStorage.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/MemoryFileStorage.java @@ -204,11 +204,6 @@ public void rename(TeaFileHandle source, TeaFileHandle target) { } } - @Override - public String getPath() { - return ""; - } - public String debugAllFiles() { String text = ""; text += println("####### START DEBUG FILE: " + fileMap.size + "\n"); diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/Store.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/Store.java deleted file mode 100644 index be8d89cb..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/Store.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.filesystem; - -/** - * Definition for a key-based file store. - * - * @author noblemaster - */ -public interface Store { - - int getLength(); - - /** The key or 'null' if it's not related to this application. */ - String key(int i); - - String getItem(String key); - - void setItem(String key, String item); - - void removeItem(String key); - - void clear(); -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreLocal.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreLocal.java deleted file mode 100644 index ca716388..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreLocal.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.filesystem; - -import org.teavm.jso.browser.Storage; - -/** - * Storage for data in browser's local storage (limited to a max. size of ~2.5MB). - * - * @author noblemaster - */ -class StoreLocal implements Store { - - /** - * Contains all the data. - */ - private final Storage storage; - - /** - * Prefix that makes sure multiple applications on the same server don't use the same paths. - */ - private final String prefix; - - StoreLocal(String prefix) { - storage = Storage.getLocalStorage(); - this.prefix = prefix; - } - - @Override - public int getLength() { - return storage.getLength(); - } - - @Override - public String key(int i) { - String key = storage.key(i); - if (key.startsWith(prefix)) { - return key.substring(prefix.length()); - } - else { - return null; - } - } - - @Override - public String getItem(String key) { - return storage.getItem(prefix + key); - } - - @Override - public void setItem(String key, String item) { - storage.setItem(prefix + key, item); - } - - @Override - public void removeItem(String key) { - storage.removeItem(prefix + key); - } - - @Override - public void clear() { - storage.clear(); - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreMemory.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreMemory.java deleted file mode 100644 index 077d417e..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/StoreMemory.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.filesystem; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.ObjectMap; - -/** - * Storage for data in memory (RAM). The limit is on how much memory the browser will allow the JavaScript - * application. - * - * @author noblemaster - */ -class StoreMemory implements Store { - - /** - * Contains all the data. - */ - private final Array keys; - private final ObjectMap map; - - StoreMemory() { - keys = new Array(16); - map = new ObjectMap(16); - } - - @Override - public int getLength() { - return keys.size; - } - - @Override - public String key(int i) { - return keys.get(i); - } - - @Override - public String getItem(String key) { - return map.get(key); - } - - @Override - public void setItem(String key, String item) { - if(!map.containsKey(key)) { - keys.add(key); - } - map.put(key, item); - } - - @Override - public void removeItem(String key) { - keys.removeValue(key, false); - map.remove(key); - } - - @Override - public void clear() { - keys.clear(); - map.clear(); - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalDBStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/ClasspathStorage.java similarity index 70% rename from backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalDBStorage.java rename to backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/ClasspathStorage.java index 771b469f..5fd26aa4 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalDBStorage.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/ClasspathStorage.java @@ -2,5 +2,5 @@ import com.github.xpenatan.gdx.backends.teavm.filesystem.MemoryFileStorage; -public class InternalDBStorage extends MemoryFileStorage { +public class ClasspathStorage extends MemoryFileStorage { } \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalStorage.java new file mode 100644 index 00000000..462a8713 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/InternalStorage.java @@ -0,0 +1,6 @@ +package com.github.xpenatan.gdx.backends.teavm.filesystem.types; + +import com.github.xpenatan.gdx.backends.teavm.filesystem.MemoryFileStorage; + +public class InternalStorage extends MemoryFileStorage { +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java index cbe42c54..a25c0d01 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/filesystem/types/LocalDBStorage.java @@ -24,11 +24,8 @@ public class LocalDBStorage extends MemoryFileStorage { private final static String KEY_OBJECT_STORE = "FILE_DATA"; - private final static String ROOT_PATH = "db/assets"; private IDBDatabase dataBase = null; - private String databaseName; - public LocalDBStorage(TeaApplication teaApplication) { setupIndexedDB(teaApplication); } @@ -41,7 +38,7 @@ private void setupIndexedDB(TeaApplication teaApplication) { teaApplication.delayInitCount++; IDBFactory instance = IDBFactory.getInstance(); - databaseName = getDatabaseName(config); + String databaseName = config.storagePrefix; IDBOpenDBRequest request = instance.open(databaseName, 1); request.setOnUpgradeNeeded(evt -> { @@ -61,28 +58,6 @@ private void setupIndexedDB(TeaApplication teaApplication) { }); } - private String getDatabaseName(TeaApplicationConfiguration config) { - String path = ROOT_PATH; - String storagePrefix = config.storagePrefix.trim(); - if(storagePrefix.endsWith("/")) { - path = storagePrefix + ROOT_PATH; - } - else { - if(storagePrefix.isEmpty()) { - path = ROOT_PATH; - } - else { - path = storagePrefix + "/" + ROOT_PATH; - } - } - return path; - } - - @Override - public String getPath() { - return databaseName; - } - @Override protected FileHandle getFilePath(String path) { return Gdx.files.local(path); diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/FileWrapper.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/FileWrapper.java deleted file mode 100644 index 731a0f44..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/FileWrapper.java +++ /dev/null @@ -1,708 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.preloader; - -import com.badlogic.gdx.Files; -import com.badlogic.gdx.Files.FileType; -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.utils.GdxRuntimeException; -import com.badlogic.gdx.utils.StreamUtils; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.io.Writer; - -/** - * @author xpenatan - */ -public class FileWrapper { - protected File file; - protected FileType type; - - protected FileWrapper() { - } - - /** - * Creates a new absolute FileHandle for the file name. Use this for tools on the desktop that don't need any of the backends. - * Do not use this constructor in case you write something cross-platform. Use the {@link Files} interface instead. - * - * @param fileName the filename. - */ - public FileWrapper(String fileName) { - this.file = new File(fileName); - this.type = FileType.Absolute; - } - - /** - * Creates a new absolute FileHandle for the {@link File}. Use this for tools on the desktop that don't need any of the - * backends. Do not use this constructor in case you write something cross-platform. Use the {@link Files} interface instead. - * - * @param file the file. - */ - public FileWrapper(File file) { - this.file = file; - this.type = FileType.Absolute; - } - - protected FileWrapper(String fileName, FileType type) { - this.type = type; - file = new File(fileName); - } - - protected FileWrapper(File file, FileType type) { - this.file = file; - this.type = type; - } - - public String path() { - return file.getPath(); - } - - public String name() { - return file.getName(); - } - - public String extension() { - String name = file.getName(); - int dotIndex = name.lastIndexOf('.'); - if(dotIndex == -1) return ""; - return name.substring(dotIndex + 1); - } - - public String nameWithoutExtension() { - String name = file.getName(); - int dotIndex = name.lastIndexOf('.'); - if(dotIndex == -1) return name; - return name.substring(0, dotIndex); - } - - public FileType type() { - return type; - } - - /** - * Returns a java.io.File that represents this file handle. Note the returned file will only be usable for - * {@link FileType#Absolute} and {@link FileType#External} file handles. - */ - public File file() { - if(type == FileType.External) return new File(Gdx.files.getExternalStoragePath(), file.getPath()); - return file; - } - - /** - * Returns a stream for reading this file as bytes. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public InputStream read() { - if(type == FileType.Classpath || (type == FileType.Internal && !file.exists()) - || (type == FileType.Local && !file.exists())) { - InputStream input = FileWrapper.class.getResourceAsStream("/" + file.getPath().replace('\\', '/')); - if(input == null) throw new GdxRuntimeException("File not found: " + file + " (" + type + ")"); - return input; - } - try { - return new FileInputStream(file()); - } - catch(Exception ex) { - if(file().isDirectory()) - throw new GdxRuntimeException("Cannot open a stream to a directory: " + file + " (" + type + ")", ex); - throw new GdxRuntimeException("Error reading file: " + file + " (" + type + ")", ex); - } - } - - /** - * Returns a buffered stream for reading this file as bytes. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public BufferedInputStream read(int bufferSize) { - return new BufferedInputStream(read(), bufferSize); - } - - /** - * Returns a reader for reading this file as characters. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public Reader reader() { - return new InputStreamReader(read()); - } - - /** - * Returns a reader for reading this file as characters. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public Reader reader(String charset) { - try { - return new InputStreamReader(read(), charset); - } - catch(UnsupportedEncodingException ex) { - throw new GdxRuntimeException("Error reading file: " + this, ex); - } - } - - /** - * Returns a buffered reader for reading this file as characters. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public BufferedReader reader(int bufferSize) { - return new BufferedReader(new InputStreamReader(read()), bufferSize); - } - - /** - * Returns a buffered reader for reading this file as characters. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public BufferedReader reader(int bufferSize, String charset) { - try { - return new BufferedReader(new InputStreamReader(read(), charset), bufferSize); - } - catch(UnsupportedEncodingException ex) { - throw new GdxRuntimeException("Error reading file: " + this, ex); - } - } - - /** - * Reads the entire file into a string using the platform's default charset. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public String readString() { - return readString(null); - } - - /** - * Reads the entire file into a string using the specified charset. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public String readString(String charset) { - int fileLength = (int)length(); - if(fileLength == 0) fileLength = 512; - StringBuilder output = new StringBuilder(fileLength); - InputStreamReader reader = null; - try { - if(charset == null) - reader = new InputStreamReader(read()); - else - reader = new InputStreamReader(read(), charset); - char[] buffer = new char[256]; - while(true) { - int length = reader.read(buffer); - if(length == -1) break; - output.append(buffer, 0, length); - } - } - catch(IOException ex) { - throw new GdxRuntimeException("Error reading layout file: " + this, ex); - } - finally { - StreamUtils.closeQuietly(reader); - } - return output.toString(); - } - - /** - * Reads the entire file into a byte array. - * - * @throws GdxRuntimeException if the file handle represents a directory, doesn't exist, or could not be read. - */ - public byte[] readBytes() { - int length = (int)length(); - if(length == 0) length = 512; - byte[] buffer = new byte[length]; - int position = 0; - InputStream input = read(); - try { - while(true) { - int count = input.read(buffer, position, buffer.length - position); - if(count == -1) break; - position += count; - if(position == buffer.length) { - // Grow buffer. - byte[] newBuffer = new byte[buffer.length * 2]; - System.arraycopy(buffer, 0, newBuffer, 0, position); - buffer = newBuffer; - } - } - } - catch(IOException ex) { - throw new GdxRuntimeException("Error reading file: " + this, ex); - } - finally { - try { - if(input != null) input.close(); - } - catch(IOException ignored) { - } - } - if(position < buffer.length) { - // Shrink buffer. - byte[] newBuffer = new byte[position]; - System.arraycopy(buffer, 0, newBuffer, 0, position); - buffer = newBuffer; - } - return buffer; - } - - /** - * Reads the entire file into the byte array. The byte array must be big enough to hold the file's data. - * - * @param bytes the array to load the file into - * @param offset the offset to start writing bytes - * @param size the number of bytes to read, see {@link #length()} - * @return the number of read bytes - */ - public int readBytes(byte[] bytes, int offset, int size) { - InputStream input = read(); - int position = 0; - try { - while(true) { - int count = input.read(bytes, offset + position, size - position); - if(count <= 0) break; - position += count; - } - } - catch(IOException ex) { - throw new GdxRuntimeException("Error reading file: " + this, ex); - } - finally { - try { - if(input != null) input.close(); - } - catch(IOException ignored) { - } - } - return position - offset; - } - - /** - * Returns a stream for writing to this file. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public OutputStream write(boolean append) { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot write to a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot write to an internal file: " + file); - parent().mkdirs(); - try { - return new FileOutputStream(file(), append); - } - catch(Exception ex) { - if(file().isDirectory()) - throw new GdxRuntimeException("Cannot open a stream to a directory: " + file + " (" + type + ")", ex); - throw new GdxRuntimeException("Error writing file: " + file + " (" + type + ")", ex); - } - } - - /** - * Reads the remaining bytes from the specified stream and writes them to this file. The stream is closed. Parent directories - * will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public void write(InputStream input, boolean append) { - OutputStream output = null; - try { - output = write(append); - byte[] buffer = new byte[4096]; - while(true) { - int length = input.read(buffer); - if(length == -1) break; - output.write(buffer, 0, length); - } - } - catch(Exception ex) { - throw new GdxRuntimeException("Error stream writing to file: " + file + " (" + type + ")", ex); - } - finally { - try { - if(input != null) input.close(); - } - catch(Exception ignored) { - } - try { - if(output != null) output.close(); - } - catch(Exception ignored) { - } - } - } - - /** - * Returns a writer for writing to this file using the default charset. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public Writer writer(boolean append) { - return writer(append, null); - } - - /** - * Returns a writer for writing to this file. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @param charset May be null to use the default charset. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public Writer writer(boolean append, String charset) { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot write to a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot write to an internal file: " + file); - parent().mkdirs(); - try { - FileOutputStream output = new FileOutputStream(file(), append); - if(charset == null) - return new OutputStreamWriter(output); - else - return new OutputStreamWriter(output, charset); - } - catch(IOException ex) { - if(file().isDirectory()) - throw new GdxRuntimeException("Cannot open a stream to a directory: " + file + " (" + type + ")", ex); - throw new GdxRuntimeException("Error writing file: " + file + " (" + type + ")", ex); - } - } - - /** - * Writes the specified string to the file using the default charset. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public void writeString(String string, boolean append) { - writeString(string, append, null); - } - - /** - * Writes the specified string to the file as UTF-8. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @param charset May be null to use the default charset. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public void writeString(String string, boolean append, String charset) { - Writer writer = null; - try { - writer = writer(append, charset); - writer.write(string); - } - catch(Exception ex) { - throw new GdxRuntimeException("Error writing file: " + file + " (" + type + ")", ex); - } - finally { - StreamUtils.closeQuietly(writer); - } - } - - /** - * Writes the specified bytes to the file. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public void writeBytes(byte[] bytes, boolean append) { - OutputStream output = write(append); - try { - output.write(bytes); - } - catch(IOException ex) { - throw new GdxRuntimeException("Error writing file: " + file + " (" + type + ")", ex); - } - finally { - try { - output.close(); - } - catch(IOException ignored) { - } - } - } - - /** - * Writes the specified bytes to the file. Parent directories will be created if necessary. - * - * @param append If false, this file will be overwritten if it exists, otherwise it will be appended. - * @throws GdxRuntimeException if this file handle represents a directory, if it is a {@link FileType#Classpath} or - * {@link FileType#Internal} file, or if it could not be written. - */ - public void writeBytes(byte[] bytes, int offset, int length, boolean append) { - OutputStream output = write(append); - try { - output.write(bytes, offset, length); - } - catch(IOException ex) { - throw new GdxRuntimeException("Error writing file: " + file + " (" + type + ")", ex); - } - finally { - try { - output.close(); - } - catch(IOException ignored) { - } - } - } - - /** - * Returns the paths to the children of this directory. Returns an empty list if this file handle represents a file and not a - * directory. On the desktop, an {@link FileType#Internal} handle to a directory on the classpath will return a zero length - * array. - * - * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. - */ - public FileWrapper[] list() { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - String[] relativePaths = file().list(); - if(relativePaths == null) return new FileWrapper[0]; - FileWrapper[] handles = new FileWrapper[relativePaths.length]; - for(int i = 0, n = relativePaths.length; i < n; i++) - handles[i] = child(relativePaths[i]); - return handles; - } - - /** - * Returns the paths to the children of this directory with the specified suffix. Returns an empty list if this file handle - * represents a file and not a directory. On the desktop, an {@link FileType#Internal} handle to a directory on the classpath - * will return a zero length array. - * - * @throws GdxRuntimeException if this file is an {@link FileType#Classpath} file. - */ - public FileWrapper[] list(String suffix) { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot list a classpath directory: " + file); - String[] relativePaths = file().list(); - if(relativePaths == null) return new FileWrapper[0]; - FileWrapper[] handles = new FileWrapper[relativePaths.length]; - int count = 0; - for(int i = 0, n = relativePaths.length; i < n; i++) { - String path = relativePaths[i]; - if(!path.endsWith(suffix)) continue; - handles[count] = child(path); - count++; - } - if(count < relativePaths.length) { - FileWrapper[] newHandles = new FileWrapper[count]; - System.arraycopy(handles, 0, newHandles, 0, count); - handles = newHandles; - } - return handles; - } - - /** - * Returns true if this file is a directory. Always returns false for classpath files. On Android, an {@link FileType#Internal} - * handle to an empty directory will return false. On the desktop, an {@link FileType#Internal} handle to a directory on the - * classpath will return false. - */ - public boolean isDirectory() { - if(type == FileType.Classpath) return false; - return file().isDirectory(); - } - - /** - * Returns a handle to the child with the specified name. - * - * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} and the child - * doesn't exist. - */ - public FileWrapper child(String name) { - if(file.getPath().length() == 0) return new FileWrapper(new File(name), type); - return new FileWrapper(new File(file, name), type); - } - - public FileWrapper parent() { - File parent = file.getParentFile(); - if(parent == null) { - if(type == FileType.Absolute) - parent = new File("/"); - else - parent = new File(""); - } - return new FileWrapper(parent, type); - } - - /** - * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. - */ - public boolean mkdirs() { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot mkdirs with a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot mkdirs with an internal file: " + file); - return file().mkdirs(); - } - - /** - * Returns true if the file exists. On Android, a {@link FileType#Classpath} or {@link FileType#Internal} handle to a directory - * will always return false. - */ - public boolean exists() { - switch(type) { - case Internal: - if(file.exists()) return true; - // Fall through. - case Classpath: - return FileWrapper.class.getResource("/" + file.getPath().replace('\\', '/')) != null; - } - return file().exists(); - } - - /** - * Deletes this file or empty directory and returns success. Will not delete a directory that has children. - * - * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. - */ - public boolean delete() { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot delete a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot delete an internal file: " + file); - return file().delete(); - } - - /** - * Deletes this file or directory and all children, recursively. - * - * @throws GdxRuntimeException if this file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file. - */ - public boolean deleteDirectory() { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot delete a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot delete an internal file: " + file); - return deleteDirectory(file()); - } - - /** - * Copies this file or directory to the specified file or directory. If this handle is a file, then 1) if the destination is a - * file, it is overwritten, or 2) if the destination is a directory, this file is copied into it, or 3) if the destination - * doesn't exist, {@link #mkdirs()} is called on the destination's parent and this file is copied into it with a new name. If - * this handle is a directory, then 1) if the destination is a file, GdxRuntimeException is thrown, or 2) if the destination is - * a directory, this directory is copied into it recursively, overwriting existing files, or 3) if the destination doesn't - * exist, {@link #mkdirs()} is called on the destination and this directory is copied into it recursively. - * - * @throws GdxRuntimeException if the destination file handle is a {@link FileType#Classpath} or {@link FileType#Internal} file, - * or copying failed. - */ - public void copyTo(FileWrapper dest) { - boolean sourceDir = isDirectory(); - if(!sourceDir) { - if(dest.isDirectory()) dest = dest.child(name()); - copyFile(this, dest); - return; - } - if(dest.exists()) { - if(!dest.isDirectory()) throw new GdxRuntimeException("Destination exists but is not a directory: " + dest); - } - else { - dest.mkdirs(); - if(!dest.isDirectory()) throw new GdxRuntimeException("Destination directory cannot be created: " + dest); - } - if(!sourceDir) dest = dest.child(name()); - copyDirectory(this, dest); - } - - /** - * Moves this file to the specified file, overwriting the file if it already exists. - * - * @throws GdxRuntimeException if the source or destination file handle is a {@link FileType#Classpath} or - * {@link FileType#Internal} file. - */ - public void moveTo(FileWrapper dest) { - if(type == FileType.Classpath) throw new GdxRuntimeException("Cannot move a classpath file: " + file); - if(type == FileType.Internal) throw new GdxRuntimeException("Cannot move an internal file: " + file); - copyTo(dest); - delete(); - } - - /** - * Returns the length in bytes of this file, or 0 if this file is a directory, does not exist, or the size cannot otherwise be - * determined. - */ - public long length() { - return file().length(); - } - - /** - * Returns the last modified time in milliseconds for this file. Zero is returned if the file doesn't exist. Zero is returned - * for {@link FileType#Classpath} files. On Android, zero is returned for {@link FileType#Internal} files. On the desktop, zero - * is returned for {@link FileType#Internal} files on the classpath. - */ - public long lastModified() { - return file().lastModified(); - } - - public String toString() { - return file.getPath(); - } - - static public FileWrapper tempFile(String prefix) { - try { - return new FileWrapper(File.createTempFile(prefix, null)); - } - catch(IOException ex) { - throw new GdxRuntimeException("Unable to create temp file.", ex); - } - } - - static public FileWrapper tempDirectory(String prefix) { - try { - File file = File.createTempFile(prefix, null); - if(!file.delete()) throw new IOException("Unable to delete temp file: " + file); - if(!file.mkdir()) throw new IOException("Unable to create temp directory: " + file); - return new FileWrapper(file); - } - catch(IOException ex) { - throw new GdxRuntimeException("Unable to create temp file.", ex); - } - } - - static private boolean deleteDirectory(File file) { - if(file.exists()) { - File[] files = file.listFiles(); - if(files != null) { - for(int i = 0, n = files.length; i < n; i++) { - if(files[i].isDirectory()) - deleteDirectory(files[i]); - else - files[i].delete(); - } - } - } - return file.delete(); - } - - static private void copyFile(FileWrapper source, FileWrapper dest) { - try { - dest.write(source.read(), false); - } - catch(Exception ex) { - throw new GdxRuntimeException("Error copying source file: " + source.file + " (" + source.type + ")\n" // - + "To destination: " + dest.file + " (" + dest.type + ")", ex); - } - } - - static private void copyDirectory(FileWrapper sourceDir, FileWrapper destDir) { - destDir.mkdirs(); - FileWrapper[] files = sourceDir.list(); - for(int i = 0, n = files.length; i < n; i++) { - FileWrapper srcFile = files[i]; - FileWrapper destFile = destDir.child(srcFile.name()); - if(srcFile.isDirectory()) - copyDirectory(srcFile, destFile); - else - copyFile(srcFile, destFile); - } - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java index 103b342a..761a9bf5 100644 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/preloader/Preloader.java @@ -5,9 +5,6 @@ import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectMap.Entries; -import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.StreamUtils; import com.github.xpenatan.gdx.backends.teavm.AssetLoaderListener; import com.github.xpenatan.gdx.backends.teavm.TeaApplication; @@ -27,15 +24,8 @@ import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayWrapper; import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.TypedArrays; import com.github.xpenatan.gdx.backends.teavm.filesystem.FileData; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FilenameFilter; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.StandardCharsets; import org.teavm.jso.core.JSArray; import org.teavm.jso.core.JSArrayReader; import org.teavm.jso.core.JSPromise; @@ -44,35 +34,10 @@ * @author xpenatan */ public class Preloader { - public ObjectMap directories = new ObjectMap<>(); - public ObjectMap images = new ObjectMap<>(); - public ObjectMap audio = new ObjectMap<>(); - public ObjectMap texts = new ObjectMap<>(); - public ObjectMap binaries = new ObjectMap<>(); - public ObjectMap assetNames = new ObjectMap<>(); - public Array assets = new Array<>(); public int assetTotal = -1; private static final String ASSET_FOLDER = "assets/"; - public static class Asset { - public Asset(String url, AssetType type, long size, String mimeType) { - this.url = url; - this.type = type; - this.size = size; - this.mimeType = mimeType; - } - - public boolean succeed; - public boolean failed; - public boolean isLoading; - public long loaded; - public final String url; - public final AssetType type; - public final long size; - public final String mimeType; - } - public final String baseUrl; public Preloader(String newBaseURL, HTMLCanvasElementWrapper canvas, TeaApplication teaApplication) { @@ -166,7 +131,7 @@ private void downloadDroppedFile(TeaApplicationConfiguration config, FileListWra } } - public String getAssetUrl() { + private String getAssetUrl() { return baseUrl + ASSET_FOLDER; } @@ -185,57 +150,7 @@ public void onFailure(String url) { public boolean onSuccess(String url, String result) { String[] lines = result.split("\n"); - if(config.useNewFileHandle) { - assetTotal = lines.length; - - for(String line : lines) { - String[] tokens = line.split(":"); - String assetUrl = tokens[1].trim(); - if(assetUrl.trim().isEmpty()) { - continue; - } - - TeaFiles files = (TeaFiles)Gdx.files; - - AssetDownloader.getInstance().load(true, getAssetUrl() + assetUrl, AssetType.Binary, null, new AssetLoaderListener() { - @Override - public void onProgress(double amount) { - } - - @Override - public void onFailure(String urll) { - } - - @Override - public boolean onSuccess(String urll, Object result) { - Blob blob = (Blob)result; - String fileType = tokens[0]; - boolean isDirectory = fileType.equals("d"); - if(isDirectory) { - TeaFileHandle internalFile = (TeaFileHandle)Gdx.files.internal(assetUrl); - internalFile.mkdirsInternal(); - } - else { - Int8ArrayWrapper data = blob.getData(); - byte[] byteArray = TypedArrays.toByteArray(data); - FileHandle internalFile = Gdx.files.internal(assetUrl); - OutputStream output = internalFile.write(false, 4096); - try { - output.write(byteArray); - } - catch(IOException ex) { - throw new GdxRuntimeException("Error writing file: " + internalFile + " (" + internalFile.type() + ")", ex); - } - finally { - StreamUtils.closeQuietly(output); - } - } - return false; - } - }); - } - return false; - } + assetTotal = lines.length; for(String line : lines) { String[] tokens = line.split(":"); @@ -243,112 +158,71 @@ public boolean onSuccess(String urll, Object result) { throw new GdxRuntimeException("Invalid assets description file."); } - String fileType = tokens[0]; - String assetUrl = tokens[1].trim(); - long size = Long.parseLong(tokens[2]); - String mimeType = tokens[3]; - - AssetType type = AssetType.Text; - if(fileType.equals("i")) type = AssetType.Image; - if(fileType.equals("b")) type = AssetType.Binary; - if(fileType.equals("a")) type = AssetType.Audio; - if(fileType.equals("d")) type = AssetType.Directory; - - if(type == AssetType.Audio && !AssetDownloader.getInstance().isUseBrowserCache()) { - size = 0; + String assetUrl = tokens[2].trim(); + assetUrl = assetUrl.trim(); + if(assetUrl.isEmpty()) { + continue; } - Asset asset = new Asset(assetUrl, type, size, mimeType); - assetNames.put(asset.url, asset.url); - assets.add(asset); - } - assetTotal = assets.size; + String fileTypeStr = tokens[0]; + String assetTypeStr = tokens[1]; - if(config.preloadAssets) { - for(int i = 0; i < assets.size; i++) { - final Asset asset = assets.get(i); - loadSingleAsset(asset); + FileType fileType = FileType.Internal; + if(fileTypeStr.equals("c")) { + fileType = FileType.Classpath; } + else if(fileTypeStr.equals("l")) { + fileType = FileType.Local; + } + AssetType assetType = AssetType.Binary; + if(assetTypeStr.equals("d")) assetType = AssetType.Directory; + + loadAsset(assetType, fileType, assetUrl); } return false; } }); } - public int loadAssets() { - int assetsToDownload = 0; - for(int i = 0; i < assets.size; i++) { - final Asset asset = assets.get(i); - if(loadSingleAsset(asset)) { - assetsToDownload++; - } - } - return assetsToDownload; - } - - public boolean loadSingleAsset(Asset asset) { - if(contains(asset.url)) { - asset.loaded = asset.size; - asset.succeed = true; - asset.failed = false; - asset.isLoading = false; - return false; - } - - if(!asset.isLoading) { - asset.failed = false; - asset.succeed = false; - asset.isLoading = true; - loadAsset(true, asset.url, asset.type, asset.mimeType, new AssetLoaderListener() { - @Override - public void onProgress(double amount) { - asset.loaded = (long)amount; - } - - @Override - public void onFailure(String url) { - asset.failed = true; - asset.isLoading = false; - } - - @Override - public boolean onSuccess(String url, Object result) { - asset.succeed = true; - asset.isLoading = false; - return false; - } - }); - return true; + public void loadAsset(AssetType assetType, FileType fileType, String path1) { + path1 = path1.trim().replace("\\", "/"); + if(path1.startsWith("/")) { + path1 = path1.substring(1); } - return false; - } - - public Asset getAsset(String path) { - for(int i = 0; i < assets.size; i++) { - final Asset asset = assets.get(i); - String url = asset.url; - if(path.equals(url)) { - return asset; - } + if(path1.isEmpty()) { + return; } - return null; - } - - private void loadAsset(boolean async, final String url, final AssetType type, final String mimeType, final AssetLoaderListener listener) { - AssetDownloader.getInstance().load(async, getAssetUrl() + url, type, mimeType, new AssetLoaderListener() { + String path = path1; + AssetDownloader.getInstance().load(true, getAssetUrl() + path, AssetType.Binary, null, new AssetLoaderListener<>() { @Override public void onProgress(double amount) { - listener.onProgress(amount); } @Override - public void onFailure(String urll) { - listener.onFailure(urll); + public void onFailure(String url) { } @Override - public boolean onSuccess(String urll, Object result) { - putAssetInMap(type, url, result); - listener.onSuccess(urll, result); + public boolean onSuccess(String url, Object result) { + Blob blob = (Blob)result; + AssetType type = AssetType.Binary; + TeaFileHandle internalFile = (TeaFileHandle)Gdx.files.getFileHandle(path, fileType); + if(assetType == AssetType.Directory) { + internalFile.mkdirsInternal(); + } + else { + Int8ArrayWrapper data = blob.getData(); + byte[] byteArray = TypedArrays.toByteArray(data); + OutputStream output = internalFile.write(false, 4096); + try { + output.write(byteArray); + } + catch(IOException ex) { + throw new GdxRuntimeException("Error writing file: " + internalFile + " (" + internalFile.type() + ")", ex); + } + finally { + StreamUtils.closeQuietly(output); + } + } return false; } }); @@ -358,179 +232,7 @@ public void loadScript(boolean async, final String url, final AssetLoaderListene AssetDownloader.getInstance().loadScript(async, getAssetUrl() + url, listener); } - protected void putAssetInMap(AssetType type, String url, Object result) { - switch(type) { - case Text: - texts.put(url, (String)result); - break; - case Image: - images.put(url, (Blob)result); - break; - case Binary: - binaries.put(url, (Blob)result); - break; - case Audio: - audio.put(url, (Blob)result); - break; - case Directory: - directories.put(url, null); - break; - } - } - - public InputStream read(String url) { - if(texts.containsKey(url)) { - try { - return new ByteArrayInputStream(texts.get(url).getBytes("UTF-8")); - } - catch(UnsupportedEncodingException e) { - return null; - } - } - if(images.containsKey(url)) { - return new ByteArrayInputStream(new byte[1]); // FIXME, sensible? - } - if(binaries.containsKey(url)) { - return binaries.get(url).read(); - } - if(audio.containsKey(url)) { - return new ByteArrayInputStream(new byte[1]); // FIXME, sensible? - } - return null; - } - - public boolean contains(String file) { - return texts.containsKey(file) || images.containsKey(file) || binaries.containsKey(file) || audio.containsKey(file) - || directories.containsKey(file); - } - - public boolean isText(String url) { - return texts.containsKey(url); - } - - public boolean isImage(String url) { - return images.containsKey(url); - } - - public boolean isBinary(String url) { - return binaries.containsKey(url); - } - - public boolean isAudio(String url) { - return audio.containsKey(url); - } - - public boolean isDirectory(String url) { - return directories.containsKey(url); - } - - private boolean isChild(String filePath, String directory) { - return filePath.startsWith(directory + "/"); - } - - public FileHandle[] list(final String file) { - return getMatchedAssetFiles(new FilePathFilter() { - @Override - public boolean accept(String path) { - return isChild(path, file); - } - }); - } - - public FileHandle[] list(final String file, final FileFilter filter) { - return getMatchedAssetFiles(new FilePathFilter() { - @Override - public boolean accept(String path) { - return isChild(path, file) && filter.accept(new File(path)); - } - }); - } - - public FileHandle[] list(final String file, final FilenameFilter filter) { - return getMatchedAssetFiles(new FilePathFilter() { - @Override - public boolean accept(String path) { - return isChild(path, file) && filter.accept(new File(file), path.substring(file.length() + 1)); - } - }); - } - - public FileHandle[] list(final String file, final String suffix) { - return getMatchedAssetFiles(new FilePathFilter() { - @Override - public boolean accept(String path) { - return isChild(path, file) && path.endsWith(suffix); - } - }); - } - - public long length(String file) { - if(texts.containsKey(file)) { - return texts.get(file).getBytes(StandardCharsets.UTF_8).length; - } - if(images.containsKey(file)) { - return 1; // FIXME, sensible? - } - if(binaries.containsKey(file)) { - return binaries.get(file).length(); - } - if(audio.containsKey(file)) { - return audio.get(file).length(); - } - return 0; - } - - private interface FilePathFilter { - boolean accept(String path); - } - - private FileHandle[] getMatchedAssetFiles(FilePathFilter filter) { - Array files = new Array<>(); - for(String file : texts.keys()) { - if(filter.accept(file)) { - files.add(new TeaFileHandle(this, null, file, FileType.Internal)); - } - } - for(String file : images.keys()) { - if(filter.accept(file)) { - files.add(new TeaFileHandle(this, null, file, FileType.Internal)); - } - } - for(String file : binaries.keys()) { - if(filter.accept(file)) { - files.add(new TeaFileHandle(this, null, file, FileType.Internal)); - } - } - for(String file : audio.keys()) { - if(filter.accept(file)) { - files.add(new TeaFileHandle(this, null, file, FileType.Internal)); - } - } - FileHandle[] filesArray = new FileHandle[files.size]; - System.arraycopy(files.items, 0, filesArray, 0, filesArray.length); - return filesArray; - } - public int getQueue() { return AssetDownloader.getInstance().getQueue(); } - - public void printLoadedAssets() { - System.out.println("### Text Assets: "); - printKeys(texts); - System.out.println("##########"); - System.out.println("### Image Assets: "); - printKeys(images); - System.out.println("##########"); - } - - private void printKeys(ObjectMap map) { - Entries iterator = map.iterator(); - - while(iterator.hasNext) { - Entry next = iterator.next(); - String key = next.key; - System.out.println("Key: " + key); - } - } -} +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraph.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraph.java deleted file mode 100644 index 2b59a44d..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraph.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.webaudio; - - -import org.teavm.jso.JSBody; -import org.teavm.jso.JSObject; - -/** - * Port from GWT gdx 1.12.0 - * - * @author xpenatan - */ -public class AudioControlGraph { - private final JSObject audioContext; - private JSObject destinationNode; - - private JSObject gainNode; - private JSObject panNode; - - public AudioControlGraph(JSObject audioContext, JSObject destinationNode) { - this.audioContext = audioContext; - this.destinationNode = destinationNode; - - panNode = setupPanNode(audioContext); - gainNode = setupAudioGraph(audioContext, panNode, destinationNode); - } - - @JSBody(params = { "audioContext" }, script = "" + - "var panNode = null;" + - "if (audioContext.createStereoPanner) {\n" + - " panNode = audioContext.createStereoPanner();\n" + - " panNode.pan.value = 0;\n" + - "}" + - "return panNode;" - ) - public static native JSObject setupPanNode(JSObject audioContext); - - @JSBody(params = { "audioContext", "panNode", "destinationNode" }, script = "" + - "var gainNode = null;" + - "if(audioContext.createGain)" + - " gainNode = audioContext.createGain();" + - "else" + - " gainNode = audioContext.createGainNode();" + - "gainNode.gain.value = 1;" + - "if(panNode) {" + - " panNode.connect(gainNode)" + - "}" + - "gainNode.connect(destinationNode);" + - "return gainNode;" - ) - public static native JSObject setupAudioGraph(JSObject audioContext, JSObject panNode, JSObject destinationNode); - - @JSBody(params = { "sourceNode", "panNode", "gainNode" }, script = "" + - "if (panNode) {" + - " sourceNode.connect(panNode);" + - "} else {" + - " sourceNode.connect(gainNode);" + - "}" - ) - public static native void setSourceJSNI(JSObject sourceNode, JSObject panNode, JSObject gainNode); - - public void setVolume(float volume) { - setVolumeJSNI(volume, gainNode); - } - - public float getVolume() { - return getVolumeJSNI(gainNode); - } - - @JSBody(params = { "volume", "gainNode" }, script = "" + - "gainNode.gain.value = volume;" - ) - public static native void setVolumeJSNI(float volume, JSObject gainNode); - - @JSBody(params = { "gainNode" }, script = "" + - "return gainNode.gain.value;" - ) - public static native float getVolumeJSNI(JSObject gainNode); - - public void setPan(float pan) { - setPanJSNI(pan, panNode); - } - - public float getPan() { - return getPanJSNI(panNode); - } - - @JSBody(params = { "pan", "panNode" }, script = "" + - "if(panNode)" + - " panNode.pan.value = pan" - ) - public static native void setPanJSNI(float pan, JSObject panNode); - - @JSBody(params = { "panNode" }, script = "" + - "if(panNode)" + - " return panNode.pan.value;" + - "return 0;" - ) - public static native float getPanJSNI(JSObject panNode); - - public void setSource(JSObject sourceNode) { - setSourceJSNI(sourceNode, panNode, gainNode); - } -} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraphPool.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraphPool.java deleted file mode 100644 index 68e13156..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/AudioControlGraphPool.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.webaudio; - -import com.badlogic.gdx.utils.Pool; -import org.teavm.jso.JSObject; - -/** - * Port from GWT gdx 1.12.0 - * - * @author xpenatan - */ -public class AudioControlGraphPool extends Pool { - public JSObject audioContext; - public JSObject destinationNode; - - public AudioControlGraphPool(JSObject audioContext, JSObject destinationNode) { - this.audioContext = audioContext; - this.destinationNode = destinationNode; - } - - @Override - protected AudioControlGraph newObject () { - return new AudioControlGraph(audioContext, destinationNode); - } -} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIManager.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIManager.java deleted file mode 100644 index 1ff912b9..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIManager.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.webaudio; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.LifecycleListener; -import com.badlogic.gdx.audio.Music; -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.files.FileHandle; -import com.github.xpenatan.gdx.backends.teavm.TeaFileHandle; -import com.github.xpenatan.gdx.backends.teavm.dom.audio.Audio; -import com.github.xpenatan.gdx.backends.teavm.dom.audio.AudioContextWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.audio.HTMLAudioElementWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.ArrayBufferWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayWrapper; -import org.teavm.jso.JSBody; -import org.teavm.jso.JSFunctor; -import org.teavm.jso.JSObject; -import org.teavm.jso.ajax.ReadyStateChangeHandler; -import org.teavm.jso.ajax.XMLHttpRequest; -import org.teavm.jso.typedarrays.ArrayBuffer; -import org.teavm.jso.typedarrays.Int8Array; - -/** - * Port from GWT gdx 1.12.0 - * - * @author xpenatan - */ -public class WebAudioAPIManager implements LifecycleListener { - private final AudioContextWrapper audioContext; - private final JSObject globalVolumeNode; - private final AudioControlGraphPool audioControlGraphPool; - private static boolean soundUnlocked; - - public WebAudioAPIManager() { - this.audioContext = createAudioContextJSNI(); - this.globalVolumeNode = createGlobalVolumeNodeJSNI(audioContext); - this.audioControlGraphPool = new AudioControlGraphPool(audioContext, globalVolumeNode); - - // for automatically muting/unmuting on pause/resume - Gdx.app.addLifecycleListener(this); - - /* - * The Web Audio API is blocked on many platforms until the developer triggers the first sound playback using the API. But - * it MUST happen as a direct result of a few specific input events. This is a major point of confusion for developers new - * to the platform. Here we attach event listeners to the graphics canvas in order to unlock the sound system on the first - * input event. On the event, we play a silent sample, which should unlock the sound - on platforms where it is not - * necessary the effect should not be noticeable (i.e. we play silence). As soon as the attempt to unlock has been - * performed, we remove all the event listeners. - */ - if(isAudioContextLocked(audioContext)) { - UnlockFunction unlockFunction = new UnlockFunction() { - @Override - public void unlockFunction() { - setUnlocked(); - } - }; - hookUpSoundUnlockers(audioContext, unlockFunction); - } - else - setUnlocked(); - } - - @JSFunctor - public interface UnlockFunction extends JSObject { - void unlockFunction(); - } - - @JSBody(params = { "audioContext", "unlockFunction" }, script = "" + - "var userInputEventNames = [" + - " 'click', 'contextmenu', 'auxclick', 'dblclick', 'mousedown'," + - " 'mouseup', 'pointerup', 'touchend', 'keydown', 'keyup', 'touchstart'" + - "];" + - "var unlock = function(e) {" + - " audioContext.resume();" + - " unlockFunction();" + - " userInputEventNames.forEach(function (eventName) {" + - " document.removeEventListener(eventName, unlock);" + - " });" + - "};" + - "userInputEventNames.forEach(function (eventName) {" + - " document.addEventListener(eventName, unlock);" + - "});" - ) - public static native void hookUpSoundUnlockers(JSObject audioContext, UnlockFunction unlockFunction); - - public void setUnlocked() { - if(!soundUnlocked) { - Gdx.app.log("Webaudio", "Audiocontext unlocked"); - } - soundUnlocked = true; - } - - public static boolean isSoundUnlocked() { - return soundUnlocked; - } - - @JSBody(params = { "audioContext" }, script = "" + - "return audioContext.state !== 'running';" - ) - static native boolean isAudioContextLocked(JSObject audioContext); - - /** - * Older browsers do not support the Web Audio API. This is where we find out. - * - * @return is the WebAudioAPI available in this browser? - */ - - @JSBody(script = "" + - "return typeof (window.AudioContext || window.webkitAudioContext) != \"undefined\";" - ) - public static native boolean isSupported(); - - @JSBody(script = "" + - "var AudioContext = window.AudioContext || window.webkitAudioContext;" + - "if(AudioContext) {" + - " var audioContext = new AudioContext();" + - " return audioContext;" + - "}" + - "return null;" - ) - private static native AudioContextWrapper createAudioContextJSNI(); - - @JSBody(params = { "audioContext" }, script = "" + - "var gainNode = null;" + - "if (audioContext.createGain)" + - " gainNode = audioContext.createGain();" + - "else" + - " gainNode = audioContext.createGainNode();" + - "gainNode.gain.value = 1.0;" + - "gainNode.connect(audioContext.destination);" + - "return gainNode;" - ) - private static native JSObject createGlobalVolumeNodeJSNI(JSObject audioContext); - - @JSBody(params = { "audioContext", "gainNode" }, script = "" + - "gainNode.disconnect(audioContext.destination);" - ) - private static native void disconnectJSNI(JSObject audioContext, JSObject gainNode); - - @JSBody(params = { "audioContext", "gainNode" }, script = "" + - "gainNode.connect(audioContext.destination);" - ) - private static native void connectJSNI(JSObject audioContext, JSObject gainNode); - - public JSObject getAudioContext() { - return audioContext; - } - - public Sound createSound(FileHandle fileHandle) { - final WebAudioAPISound newSound = new WebAudioAPISound(audioContext, globalVolumeNode, audioControlGraphPool); - - String url = ((TeaFileHandle)fileHandle).getAssetUrl(); - - XMLHttpRequest request = XMLHttpRequest.create(); - request.setOnReadyStateChange(new ReadyStateChangeHandler() { - @Override - public void stateChanged() { - if(request.getReadyState() == XMLHttpRequest.DONE) { - if(request.getStatus() != 200) { - } - else { - Int8ArrayWrapper data = (Int8ArrayWrapper)Int8Array.create((ArrayBuffer)request.getResponse()); - - /* - * Start decoding the sound data. This is an asynchronous process, which is a bad fit for the libGDX API, which - * expects sound creation to be synchronous. The result is that sound won't actually start playing until the - * decoding is done. - */ - - DecodeAudioFunction audioFunction = new DecodeAudioFunction() { - @Override - public void decodeAudioFunction(JSObject jsObject) { - newSound.setAudioBuffer(jsObject); - } - }; - decodeAudioData(getAudioContext(), data.getBuffer(), audioFunction); - } - } - } - }); - request.open("GET", url); - request.setResponseType("arraybuffer"); - request.send(); - return newSound; - } - - public Music createMusic(FileHandle fileHandle) { - String url = ((TeaFileHandle)fileHandle).getAssetUrl(); - - HTMLAudioElementWrapper audio = Audio.createIfSupported(); - audio.setSrc(url); - - WebAudioAPIMusic music = new WebAudioAPIMusic(audioContext, audio, audioControlGraphPool); - - return music; - } - - @JSFunctor - public interface DecodeAudioFunction extends JSObject { - void decodeAudioFunction(JSObject jsObject); - } - - @JSBody(params = { "audioContextIn", "audioData", "decodeFunction" }, script = "" + - "audioContextIn.decodeAudioData(" + - " audioData, " + - " decodeFunction," + - " function() {" + - " console.log(\"Error: decodeAudioData\");" + - " }" + - ");" - ) - public static native void decodeAudioData(JSObject audioContextIn, ArrayBufferWrapper audioData, DecodeAudioFunction decodeFunction); - - @Override - public void pause() { - // As the web application looses focus, we mute the sound - disconnectJSNI(audioContext, globalVolumeNode); - } - - @Override - public void resume() { - // As the web application regains focus, we unmute the sound - connectJSNI(audioContext, globalVolumeNode); - } - - public void setGlobalVolume(float volume) { - setGlobalVolumeJSNI(volume, globalVolumeNode); - } - - @JSBody(params = { "volume", "gainNode" }, script = "" + - "gainNode.gain.value = volume;" - ) - public static native void setGlobalVolumeJSNI(float volume, JSObject gainNode); - - @Override - public void dispose() { - } -} diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIMusic.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIMusic.java deleted file mode 100644 index b4663298..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPIMusic.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.webaudio; - -import com.badlogic.gdx.audio.Music; -import com.github.xpenatan.gdx.backends.teavm.dom.audio.AudioContextWrapper; -import com.github.xpenatan.gdx.backends.teavm.dom.audio.HTMLAudioElementWrapper; -import org.teavm.jso.JSBody; -import org.teavm.jso.JSFunctor; -import org.teavm.jso.JSObject; - -/** - * Port from GWT gdx 1.12.0 - * - * @author xpenatan - */ -public class WebAudioAPIMusic implements Music { - // The Audio element to be streamed - private final HTMLAudioElementWrapper audio; - - // Pool from which we will draw, and eventually return to, our AudioControlGraph from - private final AudioControlGraphPool audioControlGraphPool; - - // The audio graph used to control pan and volume for this piece of music - private final AudioControlGraph audioControlGraph; - - private OnCompletionListener onCompletionListener; - - public WebAudioAPIMusic(AudioContextWrapper audioContext, HTMLAudioElementWrapper audio, AudioControlGraphPool audioControlGraphPool) { - this.audio = audio; - this.audioControlGraphPool = audioControlGraphPool; - - EndedFunction endedFunction = new EndedFunction() { - @Override - public void endedFunc() { - ended(); - } - }; - - // Create AudioSourceNode from Audio element - JSObject audioSourceNode = createMediaElementAudioSourceNode(audioContext, audio, endedFunction); - - // Setup the sound graph to control pan and volume - audioControlGraph = audioControlGraphPool.obtain(); - audioControlGraph.setSource(audioSourceNode); - } - - public void ended() { - if(this.onCompletionListener != null) this.onCompletionListener.onCompletion(this); - } - - @JSFunctor - public interface EndedFunction extends JSObject { - void endedFunc(); - } - - @JSBody(params = { "audioContext", "audioElement", "endedFunction" }, script = "" + - "var source = audioContext.createMediaElementSource(audioElement);" + - "audioElement.addEventListener('ended', endedFunction);" + - "return source;" - ) - public static native JSObject createMediaElementAudioSourceNode(JSObject audioContext, JSObject audioElement, EndedFunction endedFunction); - - @Override - public void play() { - audio.play(); - } - - @Override - public void pause() { - audio.pause(); - } - - @Override - public void stop() { - pause(); - audio.setCurrentTime(0); - } - - @Override - public boolean isPlaying() { - return !audio.isPaused(); - } - - @Override - public void setLooping(boolean isLooping) { - audio.setLoop(isLooping); - } - - @Override - public boolean isLooping() { - return audio.isLoop(); - } - - @Override - public void setVolume(float volume) { - // Volume can be controlled on the Audio element, or as part of the audio graph. We do it as part of the graph to ensure we - // use as much common - // code as possible with the sound effect code. - audioControlGraph.setVolume(volume); - } - - @Override - public float getVolume() { - return audioControlGraph.getVolume(); - } - - @Override - public void setPan(float pan, float volume) { - audioControlGraph.setPan(pan); - audioControlGraph.setVolume(volume); - } - - @Override - public void setPosition(float position) { - audio.setCurrentTime(position); - } - - @Override - public float getPosition() { - return (float)audio.getCurrentTime(); - } - - @Override - public void dispose() { - // Stop the music - pause(); - - // Tear down the audio graph - audioControlGraphPool.free(audioControlGraph); - } - - @Override - public void setOnCompletionListener(OnCompletionListener listener) { - this.onCompletionListener = listener; - } -} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPISound.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPISound.java deleted file mode 100644 index 4773da30..00000000 --- a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/WebAudioAPISound.java +++ /dev/null @@ -1,336 +0,0 @@ -package com.github.xpenatan.gdx.backends.teavm.webaudio; - -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.utils.IntMap; -import com.badlogic.gdx.utils.IntMap.Keys; -import org.teavm.jso.JSBody; -import org.teavm.jso.JSFunctor; -import org.teavm.jso.JSObject; - -/** - * Port from GWT gdx 1.12.0 - * - * @author xpenatan - */ -public class WebAudioAPISound implements Sound { - // JavaScript object that is the base object of the Web Audio API - private final JSObject audioContext; - - // JavaScript AudioNode representing the final destination of the sound. Typically the speakers of whatever device we are - // running on. - private final JSObject destinationNode; - - // Maps from integer keys to active sounds. Both the AudioBufferSourceNode and the associated AudioControlGraph are stored for - // quick access - private final IntMap activeSounds; - private final IntMap activeAudioControlGraphs; - - // The raw sound data of this sound, which will be fed into the audio nodes - private JSObject audioBuffer; - - // Key generator for sound objects. - private int nextKey = 0; - - // We use a pool of AudioControlGraphs in order to minimize object creation - private AudioControlGraphPool audioGraphPool; - - /** - * @param audioContext The JavaScript AudioContext object that servers as the base object of the Web Audio API - * @param destinationNode The JavaScript AudioNode to route all the sound output to - * @param audioGraphPool A Pool that allows us to create AudioControlGraphs efficiently - */ - public WebAudioAPISound(JSObject audioContext, JSObject destinationNode, AudioControlGraphPool audioGraphPool) { - this.audioContext = audioContext; - this.destinationNode = destinationNode; - this.audioGraphPool = audioGraphPool; - this.activeSounds = new IntMap(); - this.activeAudioControlGraphs = new IntMap(); - } - - /** - * Set the buffer containing the actual sound data - * - * @param audioBuffer - */ - public void setAudioBuffer(JSObject audioBuffer) { - this.audioBuffer = audioBuffer; - - // If play-back of sounds have been requested before we were ready, do a pause/resume to get sound flowing - Keys keys = activeSounds.keys(); - while(keys.hasNext) { - int key = keys.next(); - pause(key); - resume(key, 0f); - } - } - - protected long play(float volume, float pitch, float pan, boolean loop) { - // if the sound system is not yet unlocked, skip playing the sound. - // otherwise, it is played when the user makes his first input - if(!WebAudioAPIManager.isSoundUnlocked() && WebAudioAPIManager.isAudioContextLocked(audioContext)) return -1; - - // Get ourselves a fresh audio graph - AudioControlGraph audioControlGraph = audioGraphPool.obtain(); - - // Create the source node that will be feeding the audio graph - JSObject audioBufferSourceNode = createBufferSourceNode(loop, pitch, audioContext, audioBuffer); - - // Configure the audio graph - audioControlGraph.setSource(audioBufferSourceNode); - audioControlGraph.setPan(pan); - audioControlGraph.setVolume(volume); - - int myKey = nextKey++; - - // Start the playback - - EndedFunction endedFunction = new EndedFunction() { - @Override - public void endedFunc() { - soundDone(myKey); - } - }; - playJSNI(audioBufferSourceNode, myKey, 0f, audioContext, endedFunction); - - // Remember that we are playing - activeSounds.put(myKey, audioBufferSourceNode); - activeAudioControlGraphs.put(myKey, audioControlGraph); - - return myKey; - } - - private void soundDone(int key) { - // The sound might have been removed by an explicit stop, before the sound reached its end - if(activeSounds.containsKey(key)) { - activeSounds.remove(key); - audioGraphPool.free(activeAudioControlGraphs.remove(key)); - } - } - - @JSBody(params = { "loop", "pitch", "audioContext", "audioBuffer" }, script = "" + - "if (audioBuffer == null) {" + - " audioBuffer = audioContext.createBuffer(2, 22050, 44100);" + - "}" + - "var source = audioContext.createBufferSource();" + - "source.buffer = audioBuffer;" + - "source.loop = loop;" + - "if (pitch !== 1.0) {" + - " source.playbackRate.value = pitch;" + - "}" + - "return source;" - ) - public static native JSObject createBufferSourceNode(boolean loop, float pitch, JSObject audioContext, JSObject audioBuffer); - - - @JSFunctor - public interface EndedFunction extends JSObject { - void endedFunc(); - } - - @JSBody(params = { "source", "key", "startOffset", "audioContext", "endedFunction" }, script = "" + - "source.startTime = audioContext.currentTime;" + - "source.onended = endedFunction;" + - "if(typeof (source.start) !== \"undefined\")" + - " source.start(audioContext.currentTime, startOffset);" + - "else" + - " source.noteOn(audioContext.currentTime, startOffset);" + - "return source;" - ) - public static native JSObject playJSNI(JSObject source, int key, float startOffset, JSObject audioContext, EndedFunction endedFunction); - - @JSBody(params = { "audioBufferSourceNode" }, script = "" + - "if(typeof (audioBufferSourceNode.stop) !== 'undefined')" + - " audioBufferSourceNode.stop();" + - "else" + - " audioBufferSourceNode.noteOff();" - ) - public static native void stopJSNI(JSObject audioBufferSourceNode); - - @Override - public long play() { - return play(1f); - } - - @Override - public long play(float volume) { - return play(volume, 1f, 0f); - } - - @Override - public long play(float volume, float pitch, float pan) { - return play(volume, pitch, pan, false); - } - - @Override - public long loop() { - return loop(1f); - } - - @Override - public long loop(float volume) { - return loop(volume, 1f, 0f); - } - - @Override - public long loop(float volume, float pitch, float pan) { - return play(volume, pitch, pan, true); - } - - @Override - public void stop() { - Keys keys = activeSounds.keys(); - while(keys.hasNext) { - int next = keys.next(); - stop(next); - } - } - - @Override - public void pause() { - Keys keys = activeSounds.keys(); - while(keys.hasNext) { - pause(keys.next()); - } - } - - @Override - public void resume() { - Keys keys = activeSounds.keys(); - while(keys.hasNext) { - resume(keys.next()); - } - } - - @Override - public void dispose() { - stop(); - activeSounds.clear(); - } - - @Override - public void stop(long soundId) { - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - JSObject audioBufferSourceNode = activeSounds.remove(soundKey); - stopJSNI(audioBufferSourceNode); - - audioGraphPool.free(activeAudioControlGraphs.remove(soundKey)); - } - } - - @Override - public void pause(long soundId) { - // Record our current position, and then stop - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - JSObject audioBufferSourceNode = activeSounds.get(soundKey); - - // The API has no concept of pause/resume, so we do it by recording a pause time stamp, and then stopping the sound. On - // resume we play the - // sound again, starting from a calculated offset. - pauseJSNI(audioBufferSourceNode, audioContext); - stopJSNI(audioBufferSourceNode); - } - } - - @Override - public void resume(long soundId) { - resume(soundId, null); - } - - private void resume(long soundId, Float from) { - // Start from previous paused position - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - JSObject audioBufferSourceNode = activeSounds.remove(soundKey); - AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey); - - boolean loop = getLoopingJSNI(audioBufferSourceNode); - float pitch = getPitchJSNI(audioBufferSourceNode); - float resumeOffset = getResumeOffsetJSNI(audioBufferSourceNode); - - if(from != null) resumeOffset = from; - - // These things can not be re-used. One play only, as dictated by the Web Audio API - JSObject newAudioBufferSourceNode = createBufferSourceNode(loop, pitch, audioContext, audioBuffer); - audioControlGraph.setSource(newAudioBufferSourceNode); - activeSounds.put(soundKey, newAudioBufferSourceNode); - - - EndedFunction endedFunction = new EndedFunction() { - @Override - public void endedFunc() { - soundDone(soundKey); - } - }; - playJSNI(newAudioBufferSourceNode, soundKey, resumeOffset, audioContext, endedFunction); - } - } - - @Override - public void setLooping(long soundId, boolean looping) { - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - JSObject sound = activeSounds.get(soundKey); - setLoopingJSNI(sound, looping); - } - } - - @Override - public void setPitch(long soundId, float pitch) { - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - JSObject sound = activeSounds.get(soundKey); - setPitchJSNI(sound, pitch); - } - } - - @Override - public void setVolume(long soundId, float volume) { - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey); - audioControlGraph.setVolume(volume); - } - } - - @Override - public void setPan(long soundId, float pan, float volume) { - int soundKey = (int)soundId; - if(activeSounds.containsKey(soundKey)) { - AudioControlGraph audioControlGraph = activeAudioControlGraphs.get(soundKey); - audioControlGraph.setPan(pan); - audioControlGraph.setVolume(volume); - } - } - - @JSBody(params = { "audioBufferSourceNode", "pitch" }, script = "" + - "audioBufferSourceNode.playbackRate.value = pitch;" - ) - public static native void setPitchJSNI(JSObject audioBufferSourceNode, float pitch); - - @JSBody(params = { "audioBufferSourceNode" }, script = "" + - "return audioBufferSourceNode.playbackRate.value;" - ) - public static native float getPitchJSNI(JSObject audioBufferSourceNode); - - @JSBody(params = { "audioBufferSourceNode", "looping" }, script = "" + - "audioBufferSourceNode.loop = looping;" - ) - public static native void setLoopingJSNI(JSObject audioBufferSourceNode, boolean looping); - - @JSBody(params = { "audioBufferSourceNode" }, script = "" + - "return audioBufferSourceNode.loop;" - ) - public static native boolean getLoopingJSNI(JSObject audioBufferSourceNode); - - @JSBody(params = { "audioBufferSourceNode", "audioContext" }, script = "" + - "audioBufferSourceNode.pauseTime = audioContext.currentTime;" - ) - public static native void pauseJSNI(JSObject audioBufferSourceNode, JSObject audioContext); - - @JSBody(params = { "audioBufferSourceNode" }, script = "" + - "return audioBufferSourceNode.pauseTime - audioBufferSourceNode.startTime;" - ) - public static native float getResumeOffsetJSNI(JSObject audioBufferSourceNode); -} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/Howl.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/Howl.java new file mode 100644 index 00000000..a8351076 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/Howl.java @@ -0,0 +1,68 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.ArrayBufferViewWrapper; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSClass; +import org.teavm.jso.JSMethod; +import org.teavm.jso.JSObject; + +@JSClass +public class Howl implements JSObject { + + @JSBody(params = { "arrayBufferView"}, script = "" + + "var blob = new Blob( [ arrayBufferView ]);" + + "var howlSource = URL.createObjectURL(blob);" + + "return new Howl({ src: [howlSource], format: ['ogg', 'webm', 'mp3', 'wav']});") + public static native Howl create(ArrayBufferViewWrapper arrayBufferView); + + public native int play(); + public native int play(int soundId); + public native void stop(int soundId); + public native void pause(int soundId); + @JSMethod("rate") + public native void setRate(float rate, int soundId); + @JSMethod("rate") + public native float getRate(int soundId); + @JSMethod("volume") + public native void setVolume(float volume, int soundId); + @JSMethod("volume") + public native float getVolume(int soundId); + @JSMethod("mute") + public native void setMute(boolean mute, int soundId); + @JSMethod("mute") + public native boolean getMute(int soundId); + @JSMethod("seek") + public native void setSeek(float seek, int soundId); + @JSMethod("seek") + public native float getSeek(int soundId); + @JSMethod("duration") + public native int getDuration(int spriteId); + @JSMethod("duration") + public native float getDuration(); + @JSMethod("loop") + public native void setLoop (boolean loop , int soundId); + @JSMethod("loop") + public native boolean getLoop(int soundId); + @JSMethod("playing") + public native boolean isPlaying(int soundId); + + @JSMethod("pannerAttr") + public native void setPannerAttr(HowlPannerAttr pannerAttr, int soundId); + @JSMethod("pannerAttr") + public native HowlPannerAttr getPannerAttr(int soundId); + @JSMethod("stereo") + public native void setStereo(float pan, int soundId); + @JSMethod("stereo") + public native float getStereo(int soundId); + + // Globals + public native void stop(); + public native void pause(); + @JSMethod("volume") + public native void setVolume(float volume); + @JSMethod("volume") + public native float getVolume(); + public native float on(String event, HowlEventFunction function, int soundId); + @JSMethod("stereo") + public native void setStereo(float pan); +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlEventFunction.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlEventFunction.java new file mode 100644 index 00000000..a4e077f1 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlEventFunction.java @@ -0,0 +1,9 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; + +@JSFunctor +public interface HowlEventFunction extends JSObject { + void onEvent(); +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlMusic.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlMusic.java new file mode 100644 index 00000000..63dbe125 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlMusic.java @@ -0,0 +1,92 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.files.FileHandle; +import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.ArrayBufferViewWrapper; +import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.TypedArrays; + +public class HowlMusic implements Music { + + private int soundId; + private Howl howl; + + public HowlMusic(FileHandle fileHandle) { + byte[] bytes = fileHandle.readBytes(); + ArrayBufferViewWrapper data = TypedArrays.getTypedArray(bytes); + howl = Howl.create(data); + } + + @Override + public void play() { + if(soundId != -1) { + soundId = howl.play(soundId); + } + else { + soundId = howl.play(); + } + } + + @Override + public void pause() { + howl.pause(soundId); + } + + @Override + public void stop() { + howl.stop(); + soundId = -1; + } + + @Override + public boolean isPlaying() { + return howl.isPlaying(soundId); + } + + @Override + public void setLooping(boolean isLooping) { + howl.setLoop(isLooping, soundId); + } + + @Override + public boolean isLooping() { + return howl.getLoop(soundId); + } + + @Override + public void setVolume(float volume) { + howl.setVolume(volume, soundId); + } + + @Override + public float getVolume() { + return howl.getVolume(soundId); + } + + @Override + public void setPan(float pan, float volume) { + howl.setStereo(pan, soundId); + howl.setVolume(volume, soundId); + } + + @Override + public void setPosition(float position) { + howl.setSeek(position, soundId); + } + + @Override + public float getPosition() { + return howl.getSeek(soundId); + } + + @Override + public void dispose() { + howl.stop(); + soundId = -1; + howl = null; + } + + @Override + public void setOnCompletionListener(OnCompletionListener listener) { + howl.on("end", () -> listener.onCompletion(HowlMusic.this), soundId); + } +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlPannerAttr.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlPannerAttr.java new file mode 100644 index 00000000..862b996e --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlPannerAttr.java @@ -0,0 +1,12 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import org.teavm.jso.JSClass; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +@JSClass +public class HowlPannerAttr implements JSObject { + + @JSProperty() + public native void setConeInnerAngle (int angle); +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlSound.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlSound.java new file mode 100644 index 00000000..8965ea0a --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlSound.java @@ -0,0 +1,120 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.files.FileHandle; +import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.ArrayBufferViewWrapper; +import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.TypedArrays; + +public class HowlSound implements Sound { + + private Howl howl; + + public HowlSound(FileHandle fileHandle) { + byte[] bytes = fileHandle.readBytes(); + ArrayBufferViewWrapper data = TypedArrays.getTypedArray(bytes); + howl = Howl.create(data); + } + + @Override + public long play() { + return howl.play(); + } + + @Override + public long play(float volume) { + int soundId = howl.play(); + howl.setVolume(volume, soundId); + return soundId; + } + + @Override + public long play(float volume, float pitch, float pan) { + int soundId = howl.play(); + howl.setVolume(volume, soundId); + howl.setRate(pitch, soundId); + howl.setStereo(pan, soundId); + return soundId; + } + + @Override + public long loop() { + int soundId = howl.play(); + howl.setLoop(true, soundId); + return soundId; + } + + @Override + public long loop(float volume) { + int soundId = howl.play(); + howl.setLoop(true, soundId); + howl.setVolume(volume, soundId); + return soundId; + } + + @Override + public long loop(float volume, float pitch, float pan) { + int soundId = howl.play(); + howl.setLoop(true, soundId); + howl.setVolume(volume, soundId); + howl.setStereo(volume, soundId); + return soundId; + } + + @Override + public void stop() { + howl.stop(); + } + + @Override + public void pause() { + howl.pause(); + } + + @Override + public void resume() { + howl.play(); + } + + @Override + public void dispose() { + howl.stop(); + howl = null; + } + + @Override + public void stop(long soundId) { + howl.stop((int)soundId); + } + + @Override + public void pause(long soundId) { + howl.pause((int)soundId); + } + + @Override + public void resume(long soundId) { + howl.play((int)soundId); + } + + @Override + public void setLooping(long soundId, boolean looping) { + howl.setLoop(looping, (int)soundId); + } + + @Override + public void setPitch(long soundId, float pitch) { + howl.setRate(pitch, (int)soundId); + } + + @Override + public void setVolume(long soundId, float volume) { + howl.setVolume(volume, (int)soundId); + } + + @Override + public void setPan(long soundId, float pan, float volume) { + int soundIdd = (int)soundId; + howl.setStereo(pan, soundIdd); + howl.setVolume(volume, soundIdd); + } +} \ No newline at end of file diff --git a/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlerAudioManager.java b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlerAudioManager.java new file mode 100644 index 00000000..ad082860 --- /dev/null +++ b/backends/backend-teavm/src/main/java/com/github/xpenatan/gdx/backends/teavm/webaudio/howler/HowlerAudioManager.java @@ -0,0 +1,31 @@ +package com.github.xpenatan.gdx.backends.teavm.webaudio.howler; + +import com.badlogic.gdx.LifecycleListener; +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.files.FileHandle; + +public class HowlerAudioManager implements LifecycleListener { + + public Sound createSound(FileHandle fileHandle) { + return new HowlSound(fileHandle); + } + + public Music createMusic(FileHandle fileHandle) { + return new HowlMusic(fileHandle); + } + @Override + public void pause() { + + } + + @Override + public void resume() { + + } + + @Override + public void dispose() { + + } +} diff --git a/backends/backend-teavm/src/main/resources/howler.js b/backends/backend-teavm/src/main/resources/howler.js new file mode 100644 index 00000000..40f9a2fb --- /dev/null +++ b/backends/backend-teavm/src/main/resources/howler.js @@ -0,0 +1,4 @@ +/*! howler.js v2.2.4 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */ +!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/(\d+)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(on&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t=0&&n._howls.splice(a,1);var u=!0;for(t=0;t=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t=0;if(!e.bufferSource)return o;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}(); +/*! Spatial Plugin */ +!function(){"use strict";HowlerGlobal.prototype._pos=[0,0,0],HowlerGlobal.prototype._orientation=[0,0,-1,0,1,0],HowlerGlobal.prototype.stereo=function(e){var n=this;if(!n.ctx||!n.ctx.listener)return n;for(var t=n._howls.length-1;t>=0;t--)n._howls[t].stereo(e);return n},HowlerGlobal.prototype.pos=function(e,n,t){var r=this;return r.ctx&&r.ctx.listener?(n="number"!=typeof n?r._pos[1]:n,t="number"!=typeof t?r._pos[2]:t,"number"!=typeof e?r._pos:(r._pos=[e,n,t],void 0!==r.ctx.listener.positionX?(r.ctx.listener.positionX.setTargetAtTime(r._pos[0],Howler.ctx.currentTime,.1),r.ctx.listener.positionY.setTargetAtTime(r._pos[1],Howler.ctx.currentTime,.1),r.ctx.listener.positionZ.setTargetAtTime(r._pos[2],Howler.ctx.currentTime,.1)):r.ctx.listener.setPosition(r._pos[0],r._pos[1],r._pos[2]),r)):r},HowlerGlobal.prototype.orientation=function(e,n,t,r,o,i){var a=this;if(!a.ctx||!a.ctx.listener)return a;var p=a._orientation;return n="number"!=typeof n?p[1]:n,t="number"!=typeof t?p[2]:t,r="number"!=typeof r?p[3]:r,o="number"!=typeof o?p[4]:o,i="number"!=typeof i?p[5]:i,"number"!=typeof e?p:(a._orientation=[e,n,t,r,o,i],void 0!==a.ctx.listener.forwardX?(a.ctx.listener.forwardX.setTargetAtTime(e,Howler.ctx.currentTime,.1),a.ctx.listener.forwardY.setTargetAtTime(n,Howler.ctx.currentTime,.1),a.ctx.listener.forwardZ.setTargetAtTime(t,Howler.ctx.currentTime,.1),a.ctx.listener.upX.setTargetAtTime(r,Howler.ctx.currentTime,.1),a.ctx.listener.upY.setTargetAtTime(o,Howler.ctx.currentTime,.1),a.ctx.listener.upZ.setTargetAtTime(i,Howler.ctx.currentTime,.1)):a.ctx.listener.setOrientation(e,n,t,r,o,i),a)},Howl.prototype.init=function(e){return function(n){var t=this;return t._orientation=n.orientation||[1,0,0],t._stereo=n.stereo||null,t._pos=n.pos||null,t._pannerAttr={coneInnerAngle:void 0!==n.coneInnerAngle?n.coneInnerAngle:360,coneOuterAngle:void 0!==n.coneOuterAngle?n.coneOuterAngle:360,coneOuterGain:void 0!==n.coneOuterGain?n.coneOuterGain:0,distanceModel:void 0!==n.distanceModel?n.distanceModel:"inverse",maxDistance:void 0!==n.maxDistance?n.maxDistance:1e4,panningModel:void 0!==n.panningModel?n.panningModel:"HRTF",refDistance:void 0!==n.refDistance?n.refDistance:1,rolloffFactor:void 0!==n.rolloffFactor?n.rolloffFactor:1},t._onstereo=n.onstereo?[{fn:n.onstereo}]:[],t._onpos=n.onpos?[{fn:n.onpos}]:[],t._onorientation=n.onorientation?[{fn:n.onorientation}]:[],e.call(this,n)}}(Howl.prototype.init),Howl.prototype.stereo=function(n,t){var r=this;if(!r._webAudio)return r;if("loaded"!==r._state)return r._queue.push({event:"stereo",action:function(){r.stereo(n,t)}}),r;var o=void 0===Howler.ctx.createStereoPanner?"spatial":"stereo";if(void 0===t){if("number"!=typeof n)return r._stereo;r._stereo=n,r._pos=[n,0,0]}for(var i=r._getSoundIds(t),a=0;a