diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1576bc4b..ec605344 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -3,7 +3,7 @@ name: Build launcher
on:
workflow_dispatch:
push:
- branches: ["main"]
+ branches: '*'
jobs:
build:
diff --git a/.gitignore b/.gitignore
index b1dd2417..134c5889 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,7 @@
*.iml
.gradle
/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
+/.idea/*
.DS_Store
/build
/captures
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d33521..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 89f76d0b..00000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-launcher
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 7643783a..00000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123c..00000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b589d56e..00000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index ae388c2a..00000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 875a1122..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
deleted file mode 100644
index 217e5c51..00000000
--- a/.idea/kotlinc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 4b7c2b30..00000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7f..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index bb597a0b..08e093b0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "com.geode.launcher"
minSdk 23
- targetSdk 33
+ targetSdk 34
versionCode 1
versionName "0.6.0-alpha.1"
@@ -21,8 +21,8 @@ android {
arguments "-DDOBBY_DEBUG=OFF"
}
}
- // support only armv7 as GD only supports armv7
- ndkConfig.abiFilters "armeabi-v7a"
+
+ ndkConfig.abiFilters "arm64-v8a", "armeabi-v7a"
}
buildTypes {
@@ -62,14 +62,13 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation "androidx.compose.ui:ui:$compose_version"
- implementation 'androidx.compose.material3:material3:1.1.1'
+ implementation 'androidx.compose.material3:material3:1.1.2'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
- implementation 'androidx.activity:activity-compose:1.7.2'
- implementation 'androidx.activity:activity-ktx:1.7.2'
+ implementation 'androidx.activity:activity-compose:1.8.2'
+ implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.9.0'
- implementation files('../libs/fmod.jar')
+ implementation 'com.google.android.material:material:1.11.0'
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
\ No newline at end of file
diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
index 6292525d..5bf8e297 100644
--- a/app/src/main/cpp/CMakeLists.txt
+++ b/app/src/main/cpp/CMakeLists.txt
@@ -22,4 +22,6 @@ if(NOT dobby_POPULATED)
add_subdirectory("${dobby_SOURCE_DIR}" ${dobby_BINARY_DIR} EXCLUDE_FROM_ALL)
target_include_directories(launcherfix PRIVATE ${dobby_SOURCE_DIR}/include)
target_link_libraries(launcherfix dobby)
-endif()
\ No newline at end of file
+endif()
+
+target_link_libraries(launcherfix log)
\ No newline at end of file
diff --git a/app/src/main/cpp/launcher-fix.cpp b/app/src/main/cpp/launcher-fix.cpp
index 0b41ee50..bdaca145 100644
--- a/app/src/main/cpp/launcher-fix.cpp
+++ b/app/src/main/cpp/launcher-fix.cpp
@@ -1,7 +1,13 @@
+#include
+#include
#include
+#include
#include
+#include
+
+#ifndef DISABLE_LAUNCHER_FIX
#include
-#include
+#endif
class DataPaths {
public:
@@ -27,6 +33,8 @@ JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setDataPath(
auto data_path_str = env->GetStringUTFChars(data_path, &is_copy);
DataPaths::get_instance().data_path = std::string(data_path_str);
+
+ env->ReleaseStringUTFChars(data_path, data_path_str);
}
extern "C"
@@ -39,6 +47,38 @@ JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setOriginalDataPath(
auto data_path_str = env->GetStringUTFChars(data_path, &is_copy);
DataPaths::get_instance().original_data_path = std::string(data_path_str);
+
+ env->ReleaseStringUTFChars(data_path, data_path_str);
+}
+
+extern "C"
+JNIEXPORT jboolean JNICALL Java_com_geode_launcher_LauncherFix_loadLibraryFromOffset(JNIEnv *env, jobject, jstring library_name, jint fd, jlong offset) {
+ // loads a given library at an offset and file descriptor
+ // assumes we have ownership of the file passed in fd
+
+ auto is_copy = jboolean();
+ auto library_cname = env->GetStringUTFChars(library_name, &is_copy);
+
+ android_dlextinfo ext_info{};
+ ext_info.flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET;
+ ext_info.library_fd = fd;
+ ext_info.library_fd_offset = offset;
+
+ auto handle = android_dlopen_ext(library_cname, RTLD_NOW | RTLD_GLOBAL, &ext_info);
+ env->ReleaseStringUTFChars(library_name, library_cname);
+ close(fd);
+
+ if (handle == nullptr) {
+ auto error = dlerror();
+ __android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-Fix", "dlopen_ext failed. given: %s\n", error);
+
+ return false;
+ }
+
+ // we don't need the library anymore
+ dlclose(handle);
+
+ return true;
}
FILE* (*fopen_original)(const char *pathname, const char *mode);
@@ -60,6 +100,7 @@ FILE* fopen_hook(const char* pathname, const char* mode) {
}
[[gnu::constructor]] [[gnu::used]] void setup_hooks() {
+ #ifndef DISABLE_LAUNCHER_FIX
auto fopen_addr = dlsym(RTLD_NEXT, "fopen");
DobbyHook(
@@ -67,4 +108,5 @@ FILE* fopen_hook(const char* pathname, const char* mode) {
reinterpret_cast(&fopen_hook),
reinterpret_cast(&fopen_original)
);
+ #endif
}
diff --git a/app/src/main/java/com/customRobTop/BaseRobTopActivity.kt b/app/src/main/java/com/customRobTop/BaseRobTopActivity.kt
index 2ab4cd7d..f9a1a2bb 100644
--- a/app/src/main/java/com/customRobTop/BaseRobTopActivity.kt
+++ b/app/src/main/java/com/customRobTop/BaseRobTopActivity.kt
@@ -9,6 +9,7 @@ import android.net.NetworkCapabilities
import android.net.Uri
import android.provider.Settings
import android.util.Log
+import android.view.WindowManager
import android.widget.Toast
import com.customRobTop.JniToCpp.resumeSound
import org.cocos2dx.lib.Cocos2dxGLSurfaceView.Companion.closeIMEKeyboard
@@ -127,6 +128,11 @@ object BaseRobTopActivity {
isLoaded = true
}
+ @JvmStatic
+ fun getDeviceRefreshRate(): Float {
+ return (me.get()?.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.refreshRate
+ }
+
// Everyplay doesn't even exist anymore lol
@JvmStatic
fun setupEveryplay() {}
diff --git a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt
index 453ad857..bf82607a 100644
--- a/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt
+++ b/app/src/main/java/com/geode/launcher/GeometryDashActivity.kt
@@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
+import android.content.pm.PackageInfo
import android.os.Build
import android.os.Bundle
import android.os.Environment
@@ -44,7 +45,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
private var mHasWindowFocus = false
private var mReceiver: BroadcastReceiver? = null
- @SuppressLint("UnsafeDynamicallyLoadedCode")
override fun onCreate(savedInstanceState: Bundle?) {
setupUIState()
@@ -57,7 +57,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
- val gdNativeLibraryPath = "${gdPackageInfo.applicationInfo.nativeLibraryDir}/"
try {
LaunchUtils.addAssetsFromPackage(assets, gdPackageInfo)
@@ -86,8 +85,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
Cocos2dxHelper.init(this, this)
GeodeUtils.setContext(this)
- System.load("$gdNativeLibraryPath/lib${Constants.FMOD_LIB_NAME}.so")
- System.load("$gdNativeLibraryPath/lib${Constants.COCOS_LIB_NAME}.so")
+ tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
+ tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
if (getLoadTesting()) {
loadTestingLibraries()
@@ -114,6 +113,44 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
}
}
+ @SuppressLint("UnsafeDynamicallyLoadedCode")
+ private fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
+ try {
+ val nativeDir = packageInfo.applicationInfo.nativeLibraryDir
+ System.load("$nativeDir/lib$libraryName.so")
+ } catch (ule: UnsatisfiedLinkError) {
+ loadLibraryFromAssets(libraryName)
+ }
+ }
+
+ private fun loadLibraryFromAssets(libraryName: String) {
+ // loads a library loaded in assets (which points to the apk + an offset)
+ // these libraries are available to the application after merging
+
+ // find the first instance of the library in preferred abi order
+ val libraryFd = Build.SUPPORTED_ABIS.asSequence()
+ .mapNotNull {
+ try {
+ assets.openNonAssetFd("lib/$it/lib$libraryName.so")
+ } catch (_: Exception) {
+ null
+ }
+ }
+ .firstOrNull() ?: throw UnsatisfiedLinkError("Could not find library lib$libraryName.so")
+
+ val fdOffset = libraryFd.startOffset
+ val fdDescriptor = libraryFd.parcelFileDescriptor.detachFd()
+
+ if (!LauncherFix.loadLibraryFromOffset("lib$libraryName.so", fdDescriptor, fdOffset)) {
+ throw UnsatisfiedLinkError("Failed to load asset lib$libraryName.so")
+ }
+
+ // after the library is opened in native code, it should be available for loading
+ // you can read more about the behavior:
+ // https://android.googlesource.com/platform/libcore/+/7f3eb2e0ac87bdea471bc577380cf50025aebde5/ojluni/src/main/java/java/lang/Runtime.java#1060
+ System.loadLibrary(libraryName)
+ }
+
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadGeodeLibrary(): Boolean {
// Load Geode if exists
diff --git a/app/src/main/java/com/geode/launcher/LauncherFix.kt b/app/src/main/java/com/geode/launcher/LauncherFix.kt
index b117ebd8..2394a649 100644
--- a/app/src/main/java/com/geode/launcher/LauncherFix.kt
+++ b/app/src/main/java/com/geode/launcher/LauncherFix.kt
@@ -10,4 +10,6 @@ object LauncherFix {
external fun setDataPath(dataPath: String)
external fun setOriginalDataPath(dataPath: String)
+
+ external fun loadLibraryFromOffset(libraryName: String, fd: Int, offset: Long): Boolean
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt b/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt
index 48929847..8701e6a7 100644
--- a/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt
+++ b/app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt
@@ -4,7 +4,6 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.AssetManager
import android.content.Context
-import com.geode.launcher.utils.Constants
import java.io.File
object LaunchUtils {
@@ -45,6 +44,10 @@ object LaunchUtils {
// (the source recommends replacing with AssetManager.setApkAssets(ApkAssets[], boolean) lol)
val clazz = assetManager.javaClass
val aspMethod = clazz.getDeclaredMethod("addAssetPath", String::class.java)
+
aspMethod.invoke(assetManager, packageInfo.applicationInfo.sourceDir)
+ packageInfo.applicationInfo.splitSourceDirs?.forEach {
+ aspMethod.invoke(assetManager, it)
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/fmod/AudioDevice.kt b/app/src/main/java/org/fmod/AudioDevice.kt
new file mode 100644
index 00000000..a67b975e
--- /dev/null
+++ b/app/src/main/java/org/fmod/AudioDevice.kt
@@ -0,0 +1,66 @@
+package org.fmod
+
+import android.media.AudioTrack
+import android.util.Log
+
+class AudioDevice {
+ private var mTrack: AudioTrack? = null
+ private fun fetchChannelConfigFromCount(i: Int): Int {
+ if (i == 1) {
+ return 2
+ }
+ if (i == 2) {
+ return 3
+ }
+ if (i == 6) {
+ return 252
+ }
+ return if (i == 8) 6396 else 0
+ }
+
+ fun init(i: Int, i2: Int, i3: Int, i4: Int): Boolean {
+ val fetchChannelConfigFromCount = fetchChannelConfigFromCount(i)
+ val minBufferSize = AudioTrack.getMinBufferSize(i2, fetchChannelConfigFromCount, 2)
+ if (minBufferSize < 0) {
+ Log.w(
+ "fmod",
+ "AudioDevice::init : Couldn't query minimum buffer size, possibly unsupported sample rate or channel count"
+ )
+ } else {
+ Log.i("fmod", "AudioDevice::init : Min buffer size: $minBufferSize bytes")
+ }
+ val i5 = i3 * i4 * i * 2
+ val i6 = if (i5 > minBufferSize) i5 else minBufferSize
+ Log.i("fmod", "AudioDevice::init : Actual buffer size: $i6 bytes")
+ return try {
+ val audioTrack = AudioTrack(3, i2, fetchChannelConfigFromCount, 2, i6, 1)
+ mTrack = audioTrack
+ try {
+ audioTrack.play()
+ true
+ } catch (unused: IllegalStateException) {
+ Log.e("fmod", "AudioDevice::init : AudioTrack play caused IllegalStateException")
+ mTrack?.release()
+ mTrack = null
+ false
+ }
+ } catch (unused2: IllegalArgumentException) {
+ Log.e("fmod", "AudioDevice::init : AudioTrack creation caused IllegalArgumentException")
+ false
+ }
+ }
+
+ fun close() {
+ try {
+ mTrack?.stop()
+ } catch (unused: IllegalStateException) {
+ Log.e("fmod", "AudioDevice::init : AudioTrack stop caused IllegalStateException")
+ }
+ mTrack?.release()
+ mTrack = null
+ }
+
+ fun write(sArr: ShortArray?, i: Int) {
+ mTrack!!.write(sArr!!, 0, i)
+ }
+}
diff --git a/app/src/main/java/org/fmod/FMOD.kt b/app/src/main/java/org/fmod/FMOD.kt
new file mode 100644
index 00000000..f9837460
--- /dev/null
+++ b/app/src/main/java/org/fmod/FMOD.kt
@@ -0,0 +1,142 @@
+package org.fmod
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.AssetManager
+import android.media.AudioManager
+import android.net.Uri
+import android.os.Build
+import android.util.Log
+import java.io.FileNotFoundException
+
+// i never agreed to your licenses! take that
+@SuppressLint("StaticFieldLeak")
+object FMOD {
+ private var gContext: Context? = null
+ private val gPluginBroadcastReceiver = PluginBroadcastReceiver()
+
+ external fun OutputAAudioHeadphonesChanged()
+
+ @JvmStatic
+ fun init(context: Context?) {
+ gContext = context
+ if (context != null) {
+ gContext!!.registerReceiver(
+ gPluginBroadcastReceiver,
+ IntentFilter("android.intent.action.HEADSET_PLUG")
+ )
+ }
+ }
+
+ @JvmStatic
+ fun close() {
+ val context = gContext
+ context?.unregisterReceiver(gPluginBroadcastReceiver)
+ gContext = null
+ }
+
+ @JvmStatic
+ fun checkInit(): Boolean {
+ return gContext != null
+ }
+
+ @JvmStatic
+ fun getAssetManager(): AssetManager? {
+ val context = gContext
+ return context?.assets
+ }
+
+ @JvmStatic
+ fun supportsLowLatency(): Boolean {
+ val outputBlockSize = getOutputBlockSize()
+ val lowLatencyFlag = lowLatencyFlag()
+ val proAudioFlag = proAudioFlag()
+ val z = outputBlockSize in 1..1024
+ val isBluetoothOn = isBluetoothOn()
+ Log.i(
+ "fmod",
+ "FMOD::supportsLowLatency : Low latency = $lowLatencyFlag, Pro Audio = $proAudioFlag, Bluetooth On = $isBluetoothOn, Acceptable Block Size = $z ($outputBlockSize)"
+ )
+ return z && lowLatencyFlag && !isBluetoothOn
+ }
+
+ @JvmStatic
+ fun lowLatencyFlag(): Boolean {
+ if (Build.VERSION.SDK_INT < 5) {
+ return false
+ }
+
+ val context = gContext ?: return false
+ return context.packageManager.hasSystemFeature("android.hardware.audio.low_latency")
+ }
+
+ @JvmStatic
+ fun proAudioFlag(): Boolean {
+ if (Build.VERSION.SDK_INT < 5) {
+ return false
+ }
+
+ val context = gContext ?: return false
+ return context.packageManager.hasSystemFeature("android.hardware.audio.pro")
+ }
+
+ @JvmStatic
+ fun supportsAAudio(): Boolean {
+ return Build.VERSION.SDK_INT >= 27
+ }
+
+ @JvmStatic
+ fun getOutputSampleRate(): Int {
+ if (Build.VERSION.SDK_INT < 17) {
+ return 0
+ }
+
+ val context = gContext ?: return 0
+ val audioService = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ val property = audioService.getProperty("android.media.property.OUTPUT_SAMPLE_RATE")
+
+ return property?.toInt() ?: 0
+ }
+
+ @JvmStatic
+ fun getOutputBlockSize(): Int {
+ if (Build.VERSION.SDK_INT < 17) {
+ return 0
+ }
+
+ val context = gContext ?: return 0
+ val audioService = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ val property = audioService.getProperty("android.media.property.OUTPUT_FRAMES_PER_BUFFER")
+
+ return property?.toInt() ?: 0
+ }
+
+ @JvmStatic
+ fun isBluetoothOn(): Boolean {
+ val context = gContext ?: return false
+ val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ return audioManager.isBluetoothA2dpOn || audioManager.isBluetoothScoOn
+ }
+
+ @JvmStatic
+ fun fileDescriptorFromUri(str: String?): Int {
+ gContext?.apply {
+ try {
+ contentResolver.openFileDescriptor(Uri.parse(str), "r")?.apply {
+ return detachFd()
+ }
+ } catch (_: FileNotFoundException) { }
+ }
+
+ return -1
+ }
+
+ internal class PluginBroadcastReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ OutputAAudioHeadphonesChanged()
+ }
+ }
+}
diff --git a/app/src/main/java/org/fmod/MediaCodec.kt b/app/src/main/java/org/fmod/MediaCodec.kt
new file mode 100644
index 00000000..3dafc1fb
--- /dev/null
+++ b/app/src/main/java/org/fmod/MediaCodec.kt
@@ -0,0 +1,200 @@
+package org.fmod
+
+import android.media.MediaCrypto
+import android.media.MediaDataSource
+import android.media.MediaExtractor
+import android.media.MediaCodec.BufferInfo
+import android.util.Log
+import android.view.Surface
+import java.io.IOException
+import java.nio.ByteBuffer
+
+class MediaCodec {
+ private var channelCount = 0
+
+ var mCodecPtr: Long = 0
+ private var mCurrentOutputBufferIndex = -1
+ private var mDataSourceProxy: Any? = null
+ private var mDecoder: android.media.MediaCodec? = null
+ private var mExtractor: MediaExtractor? = null
+ private var mInputBuffers: Array? = null
+ private var mInputFinished = false
+ private var length: Long = 0
+ private var mOutputBuffers: Array? = null
+ private var mOutputFinished = false
+ private var sampleRate = 0
+
+ fun init(j: Long): Boolean {
+ mCodecPtr = j
+ var i = 0
+
+ try {
+ val mediaExtractor = MediaExtractor()
+ mExtractor = mediaExtractor
+ mediaExtractor.setDataSource(object : MediaDataSource() {
+ override fun close() {}
+ override fun readAt(j: Long, bArr: ByteArray, i: Int, i2: Int): Int {
+ return fmodReadAt(mCodecPtr, j, bArr, i, i2)
+ }
+
+ override fun getSize(): Long {
+ return fmodGetSize(mCodecPtr)
+ }
+ })
+ } catch (e5: IOException) {
+ Log.w("fmod", "MediaCodec::init : $e5")
+ return false
+ }
+
+ val trackCount = mExtractor!!.trackCount
+ var i2 = 0
+ while (i2 < trackCount) {
+ val trackFormat = mExtractor!!.getTrackFormat(i2)
+ val string = trackFormat.getString("mime")
+ Log.d(
+ "fmod",
+ "MediaCodec::init : Format $i2 / $trackCount -- $trackFormat"
+ )
+ if (string == "audio/mp4a-latm") {
+ return try {
+ mDecoder = android.media.MediaCodec.createDecoderByType(string)
+ mExtractor!!.selectTrack(i2)
+ mDecoder!!.configure(trackFormat, null as Surface?, null as MediaCrypto?, 0)
+ mDecoder!!.start()
+ mInputBuffers = mDecoder!!.inputBuffers
+ mOutputBuffers = mDecoder!!.outputBuffers
+ val integer =
+ if (trackFormat.containsKey("encoder-delay")) trackFormat.getInteger("encoder-delay") else 0
+ if (trackFormat.containsKey("encoder-padding")) {
+ i = trackFormat.getInteger("encoder-padding")
+ }
+ val j2 = trackFormat.getLong("durationUs")
+ channelCount = trackFormat.getInteger("channel-count")
+ val integer2 = trackFormat.getInteger("sample-rate")
+ sampleRate = integer2
+ length =
+ (((j2 * integer2.toLong() + 999999) / 1000000).toInt() - integer - i).toLong()
+ true
+ } catch (e6: IOException) {
+ Log.e("fmod", "MediaCodec::init : $e6")
+ false
+ }
+ } else {
+ i2++
+ }
+ }
+ return false
+ }
+
+ fun release() {
+ val mediaCodec = mDecoder
+ if (mediaCodec != null) {
+ mediaCodec.stop()
+ mDecoder?.release()
+ mDecoder = null
+ }
+ val mediaExtractor = mExtractor
+ if (mediaExtractor != null) {
+ mediaExtractor.release()
+ mExtractor = null
+ }
+ }
+
+ fun read(bArr: ByteArray?, i: Int): Int {
+ var dequeueInputBuffer = 0
+ val i2 =
+ if (!mInputFinished || !mOutputFinished || mCurrentOutputBufferIndex != -1) 0 else -1
+ while (!mInputFinished && mDecoder!!.dequeueInputBuffer(0).also {
+ dequeueInputBuffer = it
+ } >= 0) {
+ val readSampleData = mExtractor!!.readSampleData(mInputBuffers!![dequeueInputBuffer], 0)
+ if (readSampleData >= 0) {
+ mDecoder!!.queueInputBuffer(
+ dequeueInputBuffer,
+ 0,
+ readSampleData,
+ mExtractor!!.sampleTime,
+ 0
+ )
+ mExtractor!!.advance()
+ } else {
+ mDecoder!!.queueInputBuffer(dequeueInputBuffer, 0, 0, 0, 4)
+ mInputFinished = true
+ }
+ }
+ if (!mOutputFinished && mCurrentOutputBufferIndex == -1) {
+ val bufferInfo = BufferInfo()
+ val dequeueOutputBuffer = mDecoder!!.dequeueOutputBuffer(bufferInfo, 10000)
+ if (dequeueOutputBuffer >= 0) {
+ mCurrentOutputBufferIndex = dequeueOutputBuffer
+ mOutputBuffers!![dequeueOutputBuffer].limit(bufferInfo.size)
+ mOutputBuffers!![dequeueOutputBuffer].position(bufferInfo.offset)
+ } else if (dequeueOutputBuffer == -3) {
+ mOutputBuffers = mDecoder!!.outputBuffers
+ } else if (dequeueOutputBuffer == -2) {
+ Log.d(
+ "fmod",
+ "MediaCodec::read : MediaCodec::dequeueOutputBuffer returned MediaCodec.INFO_OUTPUT_FORMAT_CHANGED " + mDecoder!!.outputFormat
+ )
+ } else if (dequeueOutputBuffer == -1) {
+ Log.d(
+ "fmod",
+ "MediaCodec::read : MediaCodec::dequeueOutputBuffer returned MediaCodec.INFO_TRY_AGAIN_LATER."
+ )
+ } else {
+ Log.w(
+ "fmod",
+ "MediaCodec::read : MediaCodec::dequeueOutputBuffer returned $dequeueOutputBuffer"
+ )
+ }
+ if (bufferInfo.flags and 4 !== 0) {
+ mOutputFinished = true
+ }
+ }
+ val i3 = mCurrentOutputBufferIndex
+ if (i3 == -1) {
+ return i2
+ }
+ val byteBuffer = mOutputBuffers!![i3]
+ val min = byteBuffer.remaining().coerceAtMost(i)
+ byteBuffer[bArr, 0, min]
+ if (!byteBuffer.hasRemaining()) {
+ byteBuffer.clear()
+ mDecoder!!.releaseOutputBuffer(mCurrentOutputBufferIndex, false)
+ mCurrentOutputBufferIndex = -1
+ }
+ return min
+ }
+
+ fun seek(i: Int) {
+ val i2 = mCurrentOutputBufferIndex
+ if (i2 != -1) {
+ mOutputBuffers!![i2].clear()
+ mCurrentOutputBufferIndex = -1
+ }
+ mInputFinished = false
+ mOutputFinished = false
+ mDecoder!!.flush()
+ val j = i.toLong()
+ mExtractor!!.seekTo(j * 1000000 / sampleRate.toLong(), MediaExtractor.SEEK_TO_PREVIOUS_SYNC)
+ val sampleTime = (mExtractor!!.sampleTime * sampleRate.toLong() + 999999) / 1000000
+ var i3 = ((j - sampleTime) * channelCount.toLong() * 2).toInt()
+ if (i3 < 0) {
+ Log.w(
+ "fmod",
+ "MediaCodec::seek : Seek to $i resulted in position $sampleTime"
+ )
+ return
+ }
+ val bArr = ByteArray(1024)
+ while (i3 > 0) {
+ i3 -= read(bArr, Math.min(1024, i3))
+ }
+ }
+
+ companion object {
+ external fun fmodGetSize(j: Long): Long
+
+ external fun fmodReadAt(j: Long, j2: Long, bArr: ByteArray?, i: Int, i2: Int): Int
+ }
+}
diff --git a/build.gradle b/build.gradle
index 11c122cc..a0118ae2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,13 @@
buildscript {
ext {
- compose_version = '1.5.1'
- compose_compiler = '1.4.7'
+ compose_version = '1.5.4'
+ compose_compiler = '1.5.7'
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '8.1.1' apply false
- id 'com.android.library' version '8.1.1' apply false
- id 'org.jetbrains.kotlin.android' version '1.8.21' apply false
+ id 'com.android.application' version '8.2.0' apply false
+ id 'com.android.library' version '8.2.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
}
tasks.register('clean', Delete) {
diff --git a/libs/fmod.jar b/libs/fmod.jar
deleted file mode 100644
index 2c2ecca5..00000000
Binary files a/libs/fmod.jar and /dev/null differ