Implement loadFromHttp

This commit is contained in:
Denis Andrasec 2024-07-10 14:31:12 +02:00
parent e30dd8956e
commit 6729f8bbce
8 changed files with 171 additions and 30 deletions

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -15,6 +15,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.esotericsoftware.spine.android.SpineController import com.esotericsoftware.spine.android.SpineController
import com.esotericsoftware.spine.android.SpineView import com.esotericsoftware.spine.android.SpineView
import java.io.File
import java.net.URL
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -44,6 +46,15 @@ fun SimpleAnimation(nav: NavHostController) {
it.animationState.setAnimation(0, "walk", true) it.animationState.setAnimation(0, "walk", true)
} }
) )
// SpineView.loadFromHttp(
// URL("https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.2/examples/spineboy/export/spineboy.atlas"),
// URL("https://raw.githubusercontent.com/EsotericSoftware/spine-runtimes/4.2/examples/spineboy/export/spineboy-pro.skel"),
// context.filesDir,
// context,
// SpineController {
// it.animationState.setAnimation(0, "walk", true)
// }
// )
}, },
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) )

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
</manifest> </manifest>

View File

@ -80,9 +80,9 @@ public class AndroidSkeletonDrawable {
return new AndroidSkeletonDrawable(atlas, skeletonData); return new AndroidSkeletonDrawable(atlas, skeletonData);
} }
public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl) { public static AndroidSkeletonDrawable fromHttp (URL atlasUrl, URL skeletonUrl, File targetDirectory) {
AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl); AndroidTextureAtlas atlas = AndroidTextureAtlas.fromHttp(atlasUrl, targetDirectory);
SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl); SkeletonData skeletonData = SkeletonDataUtils.fromHttp(atlas, skeletonUrl, targetDirectory);
return new AndroidSkeletonDrawable(atlas, skeletonData); return new AndroidSkeletonDrawable(atlas, skeletonData);
} }

View File

@ -35,21 +35,22 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.List; import java.nio.file.Files;
import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion; import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData; import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.android.utils.SpineHttpUtils;
import android.content.Context; import android.content.Context;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Build;
import kotlin.NotImplementedError;
public class AndroidTextureAtlas { public class AndroidTextureAtlas {
private static interface BitmapLoader { private static interface BitmapLoader {
@ -131,9 +132,64 @@ public class AndroidTextureAtlas {
} }
static public AndroidTextureAtlas fromFile(File atlasFile) { static public AndroidTextureAtlas fromFile(File atlasFile) {
TextureAtlasData data = new TextureAtlasData(); TextureAtlasData data;
try { try {
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(inputStream(imageFile))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
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() { FileHandle inputFile = new FileHandle() {
@Override @Override
public InputStream read() { public InputStream read() {
@ -145,21 +201,6 @@ public class AndroidTextureAtlas {
} }
}; };
data.load(inputFile, new FileHandle(atlasFile).parent(), false); data.load(inputFile, new FileHandle(atlasFile).parent(), false);
} catch (Throwable t) { return data;
throw new RuntimeException(t);
}
return new AndroidTextureAtlas(data, path -> {
File imageFile = new File(path);
try (InputStream in = new BufferedInputStream(new FileInputStream(imageFile))) {
return BitmapFactory.decodeStream(in);
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
}
static public AndroidTextureAtlas fromHttp(URL atlasUrl) {
throw new NotImplementedError("TODO");
} }
} }

View File

@ -146,6 +146,12 @@ public class SpineView extends View implements Choreographer.FrameCallback {
return spineView; 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) { public static SpineView loadFromDrawable(AndroidSkeletonDrawable drawable, Context context, SpineController controller) {
SpineView spineView = new SpineView(context, controller); SpineView spineView = new SpineView(context, controller);
spineView.loadFromDrawable(drawable); spineView.loadFromDrawable(drawable);
@ -164,8 +170,8 @@ public class SpineView extends View implements Choreographer.FrameCallback {
loadFrom(() -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile)); loadFrom(() -> AndroidSkeletonDrawable.fromFile(atlasFile, skeletonFile));
} }
public void loadFromHttp(URL atlasUrl, URL skeletonUrl) { public void loadFromHttp(URL atlasUrl, URL skeletonUrl, File targetDirectory) {
loadFrom(() -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl)); loadFrom(() -> AndroidSkeletonDrawable.fromHttp(atlasUrl, skeletonUrl, targetDirectory));
} }
public void loadFromDrawable(AndroidSkeletonDrawable drawable) { public void loadFromDrawable(AndroidSkeletonDrawable drawable) {

View File

@ -55,7 +55,8 @@ public class SkeletonDataUtils {
return skeletonLoader.readSkeletonData(new FileHandle(skeletonFile)); return skeletonLoader.readSkeletonData(new FileHandle(skeletonFile));
} }
public static SkeletonData fromHttp(AndroidTextureAtlas atlas, URL skeletonUrl) { public static SkeletonData fromHttp(AndroidTextureAtlas atlas, URL skeletonUrl, File targetDirectory) {
throw new NotImplementedError("TODO"); File skeletonFile = SpineHttpUtils.downloadFrom(skeletonUrl, targetDirectory);
return fromFile(atlas, skeletonFile);
} }
} }

View File

@ -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();
}
}
}
}