Add workflows and fix Android support

This commit is contained in:
Zachary Hall 2024-10-19 16:37:43 -07:00
parent 29b4b3d78d
commit 9b4c38ba01
22 changed files with 246 additions and 63 deletions

View file

@ -3,7 +3,7 @@ run-name: Build the project
on: [push]
jobs:
build:
build-appimage:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
@ -13,18 +13,54 @@ jobs:
ref: main
fetch-depth: 0
- name: Install system dependencies
run: apt update && apt install -y flatpak-builder flatpak python3-aiohttp python3-tenacity libostree-1-1 python3-gi gir1.2-ostree-1.0 wget
- name: Add flathub repo and install flatpak dependencies
run: flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
run: apt update && apt install -y wget python3 python3-pip
- name: Install Python dependencies via pip
run: python -m venv venv && ./venv/bin/pip install lddwrap
- name: Build project
run: flatpak-builder --install-deps-from flathub build-flatpak com.experimentalcraft.NekoPlayer.yml --repo ./flatpak-repo
run: ./build-appimage.sh
- name: Download uploading script
run: wget https://raw.githubusercontent.com/flatpak/flat-manager/9401efbdc0d6bd489507d8401c567ba219d735d5/flat-manager-client -O flat-manager-client && chmod +x flat-manager-client
- name: Upload to flatpak repo
run: ./flat-manager-client -v --token $FLATPAK_TOKEN push --commit --publish $(./flat-manager-client -v --token $FLATPAK_TOKEN create http://192.168.122.249:8080 beta) ./flatpak-repo/
env:
FLATPAK_TOKEN: ${{ secrets.FLATPAK_TOKEN }}
#- uses: actions/upload-artifact@v3
# with:
# name: build
# path: builddir/player
- uses: actions/upload-artifact@v3
with:
name: build
path: build/Looper.AppImage
build-gentoo:
run-on: gentoo
steps:
- name: Build binary package
run: emerge -v media-sound/looper
build-android:
run-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
with:
submodules: recursive
ref: main
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v2.0.10
- name: Build Debug apk
run: ./setup-android-build.sh && cd sdl-android-project && ./gradlew assembleDebug --stacktrace
- name: Build Debug bundle
run: cd sdl-android-project && ./gradlew bundleDebug --stacktrace
- name: Get debug file aab path
id: debugAab
run: echo "aabfile=$(find sdl-android-project/app/build/outputs/bundle/debug/*.aab)" >> $GITHUB_OUTPUT
- name: Get debug file apk path
id: debugApk
run: echo "apkfile=$(find sdl-android-project/app/build/outputs/apk/debug/*.apk)" >> $GITHUB_OUTPUT
- name: Zip Files
uses: papeloto/action-zip@v1
with:
files: ${{ steps.debugAab.outputs.aabfile }} ${{ steps.debugApk.outputs.apkfile }}
dest: ${{ steps.debugApk.outputs.apkfile }}.zip
- name: Upload Debug build to Artifacts
uses: actions/upload-artifact@v3
with:
name: debug-artifacts
path: ${{ steps.debugApk.outputs.apkFile }}

View file

@ -120,6 +120,11 @@ if(DEFINED ANDROID_NDK)
set(USE_G719 OFF CACHE BOOL "" FORCE)
set(USE_VORBIS OFF CACHE BOOL "" FORCE)
set(BUILD_FMT ON CACHE BOOL "" FORCE)
set(protobuf_INSTALL OFF CACHE BOOL "" FORCE)
set(protobuf_BUILD_PROTOBUF_BINARIES ON CACHE BOOL "" FORE)
set(protobuf_BUILD_PROTOC_BINARIES OFF CACHE BOOL "" FORCE)
set(utf8_range_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE)
endif()
if (BUILD_SDL)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
@ -320,12 +325,6 @@ if(BUILD_SOUNDTOUCH)
add_subdirectory(subprojects/soundtouch)
set(SOUNDTOUCH_TARGET SoundTouch)
endif()
if(BUILD_PROTOBUF)
add_subdirectory(subprojects/protobuf)
else()
find_package(protobuf REQUIRED)
find_package(absl CONFIG REQUIRED)
endif()
if(USE_LIBINTL_LITE)
add_subdirectory(subprojects/libintl-lite)
set(LIBINTL_LIBRARY intl CACHE INTERNAL "")
@ -339,7 +338,7 @@ if (DEFINED ANDROID_NDK)
add_subdirectory(subprojects/oboe)
target_link_libraries(liblooper PUBLIC oboe)
endif()
target_link_libraries(liblooper PUBLIC ${Protobuf_LIBRARY_RELEASE} fmt::fmt)
target_link_libraries(liblooper PUBLIC fmt::fmt)
target_include_directories(liblooper PUBLIC ${LIBINTL_INCDIRS})
target_link_libraries(liblooper PUBLIC ${LIBINTL_LIBRARY})
if(DEFINED EMSCRIPTEN)
@ -368,6 +367,13 @@ else()
endif()
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} SDL2main ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream libvgmstream_shared ${JSONCPP_TARGET})
endif()
if(BUILD_PROTOBUF)
add_subdirectory(subprojects/protobuf)
else()
find_package(protobuf REQUIRED)
find_package(absl CONFIG REQUIRED)
endif()
if (${ENABLE_DBUS})
target_link_libraries(liblooper PUBLIC SDBusCpp::sdbus-c++)
target_compile_definitions(liblooper PUBLIC DBUS_ENABLED)

View file

@ -1,6 +1,6 @@
#pragma once
#include <soundtouch_config.h>
#if defined(__EMSCRIPTEN__)||defined(__ANDROID__)
#if defined(__EMSCRIPTEN__)
#define NO_THREADS
#endif
@ -19,4 +19,7 @@
#else
#define SAMPLE_FMT SDL_SAMPLE_FMT
#define USE_SDL
#endif
#endif
#ifdef USE_SDL
#define LockAudioDevice SDL_LockAudioDevice
#endif

View file

@ -128,11 +128,11 @@ class AndroidFile : public File {
bool is_open() override;
};
#endif
#ifdef __ANDROID__
#define FILE_TYPE AndroidFile
#else
//#ifdef __ANDROID__
//#define FILE_TYPE AndroidFile
//#else
#define FILE_TYPE MemFile
#endif
//#endif
STREAMFILE *get_sf_from_file(File *file);
SDL_RWops *get_sdl_file(File *file);
File *open_file(const char *fname);

8
libnames.patch Normal file
View file

@ -0,0 +1,8 @@
272c272
< "SDL2",
---
> "looper",
277c277
< "main"
---
> // "main"

View file

@ -32,6 +32,7 @@ namespace Looper::Log {
}
} else {
#ifdef __ANDROID__
bool is_newline = chr == '\n' || chr == '\r';
if (!is_newline) {
line += chr;
} else {
@ -225,7 +226,7 @@ namespace Looper::Log {
LogStream *_init_logging_normal(int id) {
std::string name = get_log_name_by_idx(id);
#ifdef __ANDROID__
std::map<int, int> stream_files = {
std::map<int, android_LogPriority> stream_files = {
{-1, ANDROID_LOG_DEBUG},
{0, ANDROID_LOG_INFO},
{1, ANDROID_LOG_WARN},

View file

@ -263,7 +263,8 @@ int main(int argc, char **argv) {
return looper_run_as_executable(app.remaining(false));
}
} catch (CLI::CallForHelp) {
looper_run_as_executable(std::vector<std::string>({"--help"}));
return looper_run_as_executable(std::vector<std::string>({"--help"}));
}
free(executable_path);
return 255;
}

View file

@ -78,15 +78,35 @@ oboe::DataCallbackResult PlaybackInstance::onAudioReady(
oboe::AudioStream *audioStream,
void *audioData,
int32_t numFrames) {
SDLCallbackInner((Uint8*)audioData, numFrames * audioStream->getBytesPerFrame());
audio_playback_mutex.lock();
if (!audio_playback_paused.load()) {
SDLCallbackInner((Uint8*)audioData, numFrames * audioStream->getBytesPerFrame());
} else {
memset(audioData, 0, numFrames * audioStream->getBytesPerFrame());
}
audio_playback_mutex.unlock();
return oboe::DataCallbackResult::Continue;
}
#endif
void PlaybackInstance::LockAudioDevice() {
#ifdef USE_SDL
SDL_LockAudioDevice(device);
#else
audio_playback_mutex.lock();
#endif
}
void PlaybackInstance::UnlockAudioDevice() {
#ifdef USE_SDL
SDL_UnlockAudioDevice(device)
#else
audio_playback_mutex.unlock();
#endif
}
void PlaybackInstance::SDLCallback(void *userdata, Uint8 *stream, int len) {
((PlaybackInstance*)userdata)->SDLCallbackInner(stream, len);
}
void PlaybackInstance::Load(const char *file, int idx) {
SDL_LockAudioDevice(device);
LockAudioDevice();
load_finished.store(false);
playback_ready.store(false);
if (process != nullptr) delete process;
@ -147,7 +167,7 @@ void PlaybackInstance::Load(const char *file, int idx) {
delete process;
process = nullptr;
}
SDL_UnlockAudioDevice(device);
UnlockAudioDevice();
load_finished.store(true);
flag_mutex.lock();
stopped.store(false);
@ -157,14 +177,14 @@ void PlaybackInstance::Load(const char *file, int idx) {
}
void PlaybackInstance::Unload() {
if (process == nullptr) return;
SDL_LockAudioDevice(device);
LockAudioDevice();
delete process;
process = nullptr;
SDL_FreeAudioStream(sdl_stream);
sdl_stream = nullptr;
if (buf) free(buf);
buf = nullptr;
SDL_UnlockAudioDevice(device);
UnlockAudioDevice();
}
void PlaybackInstance::UpdateST() {
bool any_changed = false;
@ -226,7 +246,7 @@ void PlaybackInstance::InitLoopFunction() {
oboe::AudioStreamBuilder builder;
builder.setDirection(oboe::Direction::Output);
builder.setSharingMode(oboe::SharingMode::Shared);
builder.setPerformanceMode(oboe::PerformanceMode::None);
builder.setPerformanceMode(oboe::PerformanceMode::PowerSaving);
builder.setFormat(SAMPLE_FMT);
builder.setDataCallback(this);
auto res = builder.openStream(ostream);
@ -328,9 +348,9 @@ void PlaybackInstance::LoopFunction() {
current_stream = 0;
}
} else {
SDL_LockAudioDevice(device);
LockAudioDevice();
if (process && process->process_running()) process->set_stream_idx(current_stream);
SDL_UnlockAudioDevice(device);
UnlockAudioDevice();
}
if (process && process->process_running()) {
playback_ready.store(true);
@ -346,7 +366,10 @@ void PlaybackInstance::LoopFunction() {
set_signal(PlaybackSignalSeeked);
}
if (pause_changed.exchange(false) || just_stopped.exchange(false) || just_started.exchange(false)) {
SDL_PauseAudioDevice(device, (stopped.load() || paused) ? 1 : 0);
audio_playback_paused.store(stopped.load() || paused);
#ifdef USE_SDL
SDL_PauseAudioDevice(device, (audio_playback_paused.load()) ? 1 : 0);
#endif
if (paused) {
set_signal(PlaybackSignalPaused);
} else {
@ -354,10 +377,10 @@ void PlaybackInstance::LoopFunction() {
}
}
if (update.exchange(false)) {
SDL_LockAudioDevice(device);
LockAudioDevice();
real_volume = volume / 100.0;
UpdateST();
SDL_UnlockAudioDevice(device);
UnlockAudioDevice();
}
flag_mutex.unlock();
}
@ -425,16 +448,16 @@ PlaybackInstance::PlaybackInstance() {
bufsize = 0;
process = nullptr;
running.store(true);
#if NO_THREADS
#ifdef NO_THREADS
start_loop();
#else
#else
thread = std::thread(&PlaybackInstance::ThreadFunc, this);
loop_started = true;
#endif
#endif
while (loop_started && !load_finished.exchange(false)) {
#if NO_THREADS
#ifdef NO_THREADS
LoopHook();
#endif
#endif
std::this_thread::sleep_for(20ms);
}
load_finished.store(false);

View file

@ -245,6 +245,12 @@ private:
#endif
PlaybackProcess *process;
std::string filePath;
#ifndef USE_SDL
std::mutex audio_playback_mutex;
std::atomic_bool audio_playback_paused;
#endif
void LockAudioDevice();
void UnlockAudioDevice();
std::atomic_bool running;
std::atomic_bool file_changed;
std::atomic_bool seeking;

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdl-android-project</name>
<comment>Project sdl-android-project created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1729361463965</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -0,0 +1,13 @@
arguments=--init-script /home/catmeow/.local/share/zed/extensions/work/java/jdt-language-server-1.40.0/configuration/org.eclipse.osgi/57/0/.cp/gradle/init/init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/opt/openjdk-bin-21.0.4_p7
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<filteredResources>
<filter>
<id>1729361463958</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -0,0 +1,2 @@
connection.project.dir=..
eclipse.preferences.version=1

View file

@ -11,6 +11,7 @@ android {
buildFeatures {
prefab true
}
ndkVersion "${project.ndk_version}"
if (buildAsApplication) {
namespace "com.complecwaft.looper"
}

View file

@ -277,6 +277,7 @@ public class HIDDeviceManager {
0x044f, // Thrustmaster
0x045e, // Microsoft
0x0738, // Mad Catz
0x0b05, // ASUS
0x0e6f, // PDP
0x0f0d, // Hori
0x10f5, // Turtle Beach
@ -590,7 +591,13 @@ public class HIDDeviceManager {
} else {
flags = 0;
}
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
intent.setPackage(mContext.getPackageName());
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
} else {
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
}
} catch (Exception e) {
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
HIDDeviceOpenResult(deviceID, false);

View file

@ -38,6 +38,10 @@ public class SDL {
}
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
loadLibrary(libraryName, mContext);
}
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
if (libraryName == null) {
throw new NullPointerException("No library name provided.");
@ -53,10 +57,10 @@ public class SDL {
// To use ReLinker, just add it as a dependency. For more information, see
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
//
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
// they've changed during updates.
@ -66,7 +70,7 @@ public class SDL {
// Actually load the library!
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
}
catch (final Throwable e) {
// Fall back

View file

@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 30;
private static final int SDL_MICRO_VERSION = 2;
private static final int SDL_MICRO_VERSION = 8;
/*
// Display InputType.SOURCE/CLASS of events and devices
//
@ -89,7 +89,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
| InputDevice.SOURCE_CLASS_POSITION
| InputDevice.SOURCE_CLASS_TRACKBALL);
if (s2 != 0) cls += "Some_Unkown";
if (s2 != 0) cls += "Some_Unknown";
s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
@ -163,7 +163,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
s2 &= ~FLAG_TAINTED;
if (s2 != 0) src += " Some_Unkown";
if (s2 != 0) src += " Some_Unknown";
Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
}
@ -269,19 +269,19 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
*/
protected String[] getLibraries() {
return new String[] {
"vgmstream",
"looper",
// "SDL2_image",
// "SDL2_mixer",
// "SDL2_net",
// "SDL2_ttf",
"looper"
// "main"
};
}
// Load the .so
public void loadLibraries() {
for (String lib : getLibraries()) {
SDL.loadLibrary(lib);
SDL.loadLibrary(lib, this);
}
}
@ -995,8 +995,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
/* No valid hint, nothing is explicitly allowed */
if (!is_portrait_allowed && !is_landscape_allowed) {
if (resizable) {
/* All orientations are allowed */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
/* All orientations are allowed, respecting user orientation lock setting */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
} else {
/* Fixed window and nothing specified. Get orientation from w/h of created window */
req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
@ -1005,8 +1005,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
/* At least one orientation is allowed */
if (resizable) {
if (is_portrait_allowed && is_landscape_allowed) {
/* hint allows both landscape and portrait, promote to full sensor */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
/* hint allows both landscape and portrait, promote to full user */
req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
} else {
/* Use the only one allowed "orientation" */
req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);

View file

@ -10,6 +10,7 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
ndk_version=27.2.12479018
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit

View file

@ -17,4 +17,7 @@ echo "Android JNI symlink: $ANDROID_JNI_DIR -> $PROJECT_DIR"
pushd "${PROJECT_DIR}"
[ -d "$ANDROID_JNI_DIR" ] && rm -rf "$ANDROID_JNI_DIR"
ln -sf "$PROJECT_DIR" "$ANDROID_JNI_DIR"
rm -rf "${ANDROID_APP_DIR}/src/main/java/org"
cp -r "${PROJECT_DIR}/subprojects/SDL/android-project/app/src/main/java/org" "${ANDROID_APP_DIR}/src/main/java/org"
patch -N "${ANDROID_APP_DIR}/src/main/java/org/libsdl/app/SDLActivity.java" -i "${PROJECT_DIR}/libnames.patch"
popd

@ -1 +1 @@
Subproject commit f461d91cd265d7b9a44b4d472b1df0c0ad2855a0
Subproject commit 79ec168f3c1e2fe27335cb8886439f7ef676fb49

@ -1 +1 @@
Subproject commit 439c42c735ae1efed57ab7771986f2a3c0b99319
Subproject commit 63def39e881afa496502d9c410f4ea948e59490d