implement enough to launch 2.2 (#2)

* generic maintenance and allow armv8

* implement missing java functions

* Update build.yml

* add missing audiodevice code

* split apk support

* ignore (gitignore)

* replace library load with native code

* set supported abis again

---------

Co-authored-by: alk <45172705+altalk23@users.noreply.github.com>
This commit is contained in:
Chloeandalk authored and GitHub committed 2023-12-22 04:14:05 -07:00
1 parent c08ae0ec13
commit c85470013f
24 files changed
+521 -251

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ name: Build launcher
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: ["main"] branches: '*'
jobs: jobs:
build: build:
+1 -6
View File
@@ -1,12 +1,7 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea/caches /.idea/*
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store .DS_Store
/build /build
/captures /captures
-3
View File
@@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
Generated
-1
View File
@@ -1 +0,0 @@
launcher
-123
View File
@@ -1,123 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
-20
View File
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
-23
View File
@@ -1,23 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.21" />
</component>
</project>
-31
View File
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/compose-model-1653085351752.xml" value="0.375" />
<entry key="../../../../layout/compose-model-1653085541745.xml" value="2.0" />
<entry key="../../../../layout/compose-model-1653085956106.xml" value="0.33" />
<entry key="../../../../layout/compose-model-1653125149886.xml" value="0.33" />
<entry key="../../../../layout/compose-model-1653260606908.xml" value="0.2690033783783784" />
<entry key="../../../../layout/compose-model-1653262716326.xml" value="0.25" />
<entry key="../../../../layout/compose-model-1653262789080.xml" value="0.33" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.304" />
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.304" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.3525" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.3525" />
</map>
</option>
</component>
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="android.annotation.SuppressLint" />
</list>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+7 -8
View File
@@ -9,7 +9,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.geode.launcher" applicationId "com.geode.launcher"
minSdk 23 minSdk 23
targetSdk 33 targetSdk 34
versionCode 1 versionCode 1
versionName "0.6.0-alpha.1" versionName "0.6.0-alpha.1"
@@ -21,8 +21,8 @@ android {
arguments "-DDOBBY_DEBUG=OFF" arguments "-DDOBBY_DEBUG=OFF"
} }
} }
// support only armv7 as GD only supports armv7
ndkConfig.abiFilters "armeabi-v7a" ndkConfig.abiFilters "arm64-v8a", "armeabi-v7a"
} }
buildTypes { buildTypes {
@@ -62,14 +62,13 @@ android {
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-ktx:1.12.0'
implementation "androidx.compose.ui:ui:$compose_version" 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.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation 'androidx.activity:activity-compose:1.7.2' implementation 'androidx.activity:activity-compose:1.8.2'
implementation 'androidx.activity:activity-ktx:1.7.2' implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.11.0'
implementation files('../libs/fmod.jar')
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
} }
+2
View File
@@ -23,3 +23,5 @@ if(NOT dobby_POPULATED)
target_include_directories(launcherfix PRIVATE ${dobby_SOURCE_DIR}/include) target_include_directories(launcherfix PRIVATE ${dobby_SOURCE_DIR}/include)
target_link_libraries(launcherfix dobby) target_link_libraries(launcherfix dobby)
endif() endif()
target_link_libraries(launcherfix log)
+44 -2
View File
@@ -1,7 +1,13 @@
#include <android/dlext.h>
#include <android/log.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <string>
#include <dobby.h>
#include <jni.h> #include <jni.h>
#include <string>
#include <unistd.h>
#ifndef DISABLE_LAUNCHER_FIX
#include <dobby.h>
#endif
class DataPaths { class DataPaths {
public: public:
@@ -27,6 +33,8 @@ JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setDataPath(
auto data_path_str = env->GetStringUTFChars(data_path, &is_copy); auto data_path_str = env->GetStringUTFChars(data_path, &is_copy);
DataPaths::get_instance().data_path = std::string(data_path_str); DataPaths::get_instance().data_path = std::string(data_path_str);
env->ReleaseStringUTFChars(data_path, data_path_str);
} }
extern "C" 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); auto data_path_str = env->GetStringUTFChars(data_path, &is_copy);
DataPaths::get_instance().original_data_path = std::string(data_path_str); 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); 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() { [[gnu::constructor]] [[gnu::used]] void setup_hooks() {
#ifndef DISABLE_LAUNCHER_FIX
auto fopen_addr = dlsym(RTLD_NEXT, "fopen"); auto fopen_addr = dlsym(RTLD_NEXT, "fopen");
DobbyHook( DobbyHook(
@@ -67,4 +108,5 @@ FILE* fopen_hook(const char* pathname, const char* mode) {
reinterpret_cast<dobby_dummy_func_t>(&fopen_hook), reinterpret_cast<dobby_dummy_func_t>(&fopen_hook),
reinterpret_cast<dobby_dummy_func_t*>(&fopen_original) reinterpret_cast<dobby_dummy_func_t*>(&fopen_original)
); );
#endif
} }
@@ -9,6 +9,7 @@ import android.net.NetworkCapabilities
import android.net.Uri import android.net.Uri
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import com.customRobTop.JniToCpp.resumeSound import com.customRobTop.JniToCpp.resumeSound
import org.cocos2dx.lib.Cocos2dxGLSurfaceView.Companion.closeIMEKeyboard import org.cocos2dx.lib.Cocos2dxGLSurfaceView.Companion.closeIMEKeyboard
@@ -127,6 +128,11 @@ object BaseRobTopActivity {
isLoaded = true isLoaded = true
} }
@JvmStatic
fun getDeviceRefreshRate(): Float {
return (me.get()?.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.refreshRate
}
// Everyplay doesn't even exist anymore lol // Everyplay doesn't even exist anymore lol
@JvmStatic @JvmStatic
fun setupEveryplay() {} fun setupEveryplay() {}
@@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.pm.PackageInfo
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
@@ -44,7 +45,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
private var mHasWindowFocus = false private var mHasWindowFocus = false
private var mReceiver: BroadcastReceiver? = null private var mReceiver: BroadcastReceiver? = null
@SuppressLint("UnsafeDynamicallyLoadedCode")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setupUIState() setupUIState()
@@ -57,7 +57,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
} }
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0) val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
val gdNativeLibraryPath = "${gdPackageInfo.applicationInfo.nativeLibraryDir}/"
try { try {
LaunchUtils.addAssetsFromPackage(assets, gdPackageInfo) LaunchUtils.addAssetsFromPackage(assets, gdPackageInfo)
@@ -86,8 +85,8 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
Cocos2dxHelper.init(this, this) Cocos2dxHelper.init(this, this)
GeodeUtils.setContext(this) GeodeUtils.setContext(this)
System.load("$gdNativeLibraryPath/lib${Constants.FMOD_LIB_NAME}.so") tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
System.load("$gdNativeLibraryPath/lib${Constants.COCOS_LIB_NAME}.so") tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
if (getLoadTesting()) { if (getLoadTesting()) {
loadTestingLibraries() 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") @SuppressLint("UnsafeDynamicallyLoadedCode")
private fun loadGeodeLibrary(): Boolean { private fun loadGeodeLibrary(): Boolean {
// Load Geode if exists // Load Geode if exists
@@ -10,4 +10,6 @@ object LauncherFix {
external fun setDataPath(dataPath: String) external fun setDataPath(dataPath: String)
external fun setOriginalDataPath(dataPath: String) external fun setOriginalDataPath(dataPath: String)
external fun loadLibraryFromOffset(libraryName: String, fd: Int, offset: Long): Boolean
} }
@@ -4,7 +4,6 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.Context import android.content.Context
import com.geode.launcher.utils.Constants
import java.io.File import java.io.File
object LaunchUtils { object LaunchUtils {
@@ -45,6 +44,10 @@ object LaunchUtils {
// (the source recommends replacing with AssetManager.setApkAssets(ApkAssets[], boolean) lol) // (the source recommends replacing with AssetManager.setApkAssets(ApkAssets[], boolean) lol)
val clazz = assetManager.javaClass val clazz = assetManager.javaClass
val aspMethod = clazz.getDeclaredMethod("addAssetPath", String::class.java) val aspMethod = clazz.getDeclaredMethod("addAssetPath", String::class.java)
aspMethod.invoke(assetManager, packageInfo.applicationInfo.sourceDir) aspMethod.invoke(assetManager, packageInfo.applicationInfo.sourceDir)
packageInfo.applicationInfo.splitSourceDirs?.forEach {
aspMethod.invoke(assetManager, it)
}
} }
} }
+66
View File
@@ -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)
}
}
+142
View File
@@ -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()
}
}
}
+200
View File
@@ -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<ByteBuffer>? = null
private var mInputFinished = false
private var length: Long = 0
private var mOutputBuffers: Array<ByteBuffer>? = 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
}
}
+5 -5
View File
@@ -1,13 +1,13 @@
buildscript { buildscript {
ext { ext {
compose_version = '1.5.1' compose_version = '1.5.4'
compose_compiler = '1.4.7' compose_compiler = '1.5.7'
} }
}// Top-level build file where you can add configuration options common to all sub-projects/modules. }// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '8.1.1' apply false id 'com.android.application' version '8.2.0' apply false
id 'com.android.library' version '8.1.1' apply false id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.8.21' apply false id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
} }
tasks.register('clean', Delete) { tasks.register('clean', Delete) {
BIN
View File
Binary file not shown.