From 6729f8bbce03e15be74a56269cf66d62fe19d545 Mon Sep 17 00:00:00 2001 From: Denis Andrasec Date: Wed, 10 Jul 2024 14:31:12 +0200 Subject: [PATCH] Implement loadFromHttp --- .../app/src/main/AndroidManifest.xml | 2 + .../esotericsoftware/spine/SimpleAnimation.kt | 11 +++ .../src/main/AndroidManifest.xml | 2 +- .../android/AndroidSkeletonDrawable.java | 6 +- .../spine/android/AndroidTextureAtlas.java | 85 ++++++++++++++----- .../spine/android/SpineView.java | 10 ++- .../android/utils/SkeletonDataUtils.java | 5 +- .../spine/android/utils/SpineHttpUtils.java | 80 +++++++++++++++++ 8 files changed, 171 insertions(+), 30 deletions(-) create mode 100644 spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineHttpUtils.java diff --git a/spine-android/app/src/main/AndroidManifest.xml b/spine-android/app/src/main/AndroidManifest.xml index 8e9e41adf..20ac444d9 100644 --- a/spine-android/app/src/main/AndroidManifest.xml +++ b/spine-android/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + - + \ No newline at end of file diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidSkeletonDrawable.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidSkeletonDrawable.java index 2a53f99bf..3d3138a03 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidSkeletonDrawable.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidSkeletonDrawable.java @@ -80,9 +80,9 @@ public class AndroidSkeletonDrawable { return new AndroidSkeletonDrawable(atlas, skeletonData); } - public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl) { - AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl); - SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl); + public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) { + AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl, targetDirectory); + SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl, targetDirectory); return new AndroidSkeletonDrawable(atlas, skeletonData); } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java index 8979ee179..a9945f243 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/AndroidTextureAtlas.java @@ -35,21 +35,22 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; -import java.util.List; +import java.nio.file.Files; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Null; +import com.esotericsoftware.spine.android.utils.SpineHttpUtils; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; - -import kotlin.NotImplementedError; +import android.os.Build; public class AndroidTextureAtlas { private static interface BitmapLoader { @@ -131,27 +132,15 @@ public class AndroidTextureAtlas { } static public AndroidTextureAtlas fromFile(File atlasFile) { - TextureAtlasData data = new TextureAtlasData(); - + TextureAtlasData data; try { - FileHandle inputFile = new FileHandle() { - @Override - public InputStream read() { - try { - return new FileInputStream(atlasFile); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - }; - data.load(inputFile, new FileHandle(atlasFile).parent(), false); - } catch (Throwable t) { - throw new RuntimeException(t); + data = loadTextureAtlasData(atlasFile); + } catch (Exception e) { + throw new RuntimeException(e); } - return new AndroidTextureAtlas(data, path -> { File imageFile = new File(path); - try (InputStream in = new BufferedInputStream(new FileInputStream(imageFile))) { + try (InputStream in = new BufferedInputStream(inputStream(imageFile))) { return BitmapFactory.decodeStream(in); } catch (Throwable t) { throw new RuntimeException(t); @@ -159,7 +148,59 @@ public class AndroidTextureAtlas { }); } - static public AndroidTextureAtlas fromHttp(URL atlasUrl) { - throw new NotImplementedError("TODO"); + static public AndroidTextureAtlas fromHttp(URL atlasUrl, File targetDirectory) { + File atlasFile = SpineHttpUtils.downloadFrom(atlasUrl, targetDirectory); + TextureAtlasData data; + try { + data = loadTextureAtlasData(atlasFile); + } catch (Exception e) { + throw new RuntimeException(e); + } + return new AndroidTextureAtlas(data, path -> { + String fileName = path.substring(path.lastIndexOf('/') + 1); + + String atlasUrlPath = atlasUrl.getPath(); + int lastSlashIndex = atlasUrlPath.lastIndexOf('/'); + String imagePath = atlasUrlPath.substring(0, lastSlashIndex + 1) + fileName; + + File imageFile; + try { + URL imageUrl = new URL(atlasUrl.getProtocol(), atlasUrl.getHost(), atlasUrl.getPort(), imagePath); + imageFile = SpineHttpUtils.downloadFrom(imageUrl, targetDirectory); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + try (InputStream in = new BufferedInputStream(inputStream(imageFile))) { + return BitmapFactory.decodeStream(in); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + + private static InputStream inputStream(File file) throws Exception { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return Files.newInputStream(file.toPath()); + } else { + //noinspection IOStreamConstructor + return new FileInputStream(file); + } + } + + static private TextureAtlasData loadTextureAtlasData(File atlasFile) { + TextureAtlasData data = new TextureAtlasData(); + FileHandle inputFile = new FileHandle() { + @Override + public InputStream read() { + try { + return new FileInputStream(atlasFile); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + }; + data.load(inputFile, new FileHandle(atlasFile).parent(), false); + return data; } } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java index 5e8091a7a..db159f223 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/SpineView.java @@ -146,6 +146,12 @@ public class SpineView extends View implements Choreographer.FrameCallback { return spineView; } + public static SpineView loadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory, Context context, SpineController controller) { + SpineView spineView = new SpineView(context, controller); + spineView.loadFromHttp(atlasUrl, skeletonUrl, targetDirectory); + return spineView; + } + public static SpineView loadFromDrawable(AndroidSkeletonDrawable drawable, Context context, SpineController controller) { SpineView spineView = new SpineView(context, controller); spineView.loadFromDrawable(drawable); @@ -164,8 +170,8 @@ public class SpineView extends View implements Choreographer.FrameCallback { loadFrom(() -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile)); } - public void loadFromHttp(URL atlasUrl, URL skeletonUrl) { - loadFrom(() -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl)); + public void loadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory) { + loadFrom(() -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl, targetDirectory)); } public void loadFromDrawable(AndroidSkeletonDrawable drawable) { diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SkeletonDataUtils.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SkeletonDataUtils.java index a59b21adc..5d849d274 100644 --- a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SkeletonDataUtils.java +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SkeletonDataUtils.java @@ -55,7 +55,8 @@ public class SkeletonDataUtils { return skeletonLoader.readSkeletonData(new FileHandle(skeletonFile)); } - public static SkeletonData fromHttp(AndroidTextureAtlas atlas, URL skeletonUrl) { - throw new NotImplementedError("TODO"); + public static SkeletonData fromHttp(AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) { + File skeletonFile = SpineHttpUtils.downloadFrom(skeletonUrl, targetDirectory); + return fromFile(atlas, skeletonFile); } } diff --git a/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineHttpUtils.java b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineHttpUtils.java new file mode 100644 index 000000000..0a3eaf99a --- /dev/null +++ b/spine-android/spine-android/src/main/java/com/esotericsoftware/spine/android/utils/SpineHttpUtils.java @@ -0,0 +1,80 @@ +package com.esotericsoftware.spine.android.utils; + +import android.os.Build; + +import com.esotericsoftware.spine.android.AndroidTextureAtlas; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; + +public class SpineHttpUtils { + + public static File downloadFrom(URL url, File targetDirectory) throws RuntimeException { + HttpURLConnection urlConnection = null; + InputStream inputStream = null; + OutputStream outputStream = null; + + try { + urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.connect(); + + if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new RuntimeException("Failed to connect: HTTP response code " + urlConnection.getResponseCode()); + } + + inputStream = new BufferedInputStream(urlConnection.getInputStream()); + + String atlasUrlPath = url.getPath(); + String fileName = atlasUrlPath.substring(atlasUrlPath.lastIndexOf('/') + 1); + File file = new File(targetDirectory, fileName); + + // Create an OutputStream to write to the file + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + outputStream = Files.newOutputStream(file.toPath()); + } else { + //noinspection IOStreamConstructor + outputStream = new FileOutputStream(file); + } + + byte[] buffer = new byte[1024]; + int bytesRead; + + // Write the input stream to the output stream + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return file; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (outputStream != null) { + try { + outputStream.flush(); + outputStream.close(); + } catch (IOException e) { + // Nothing we can do + } + } + + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + // Nothing we can do + } + } + + if (urlConnection != null) { + urlConnection.disconnect(); + } + } + } +} +