diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index eae9b5d..c5a90ef 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -37,11 +37,12 @@ add_library(${CMAKE_PROJECT_NAME} SHARED jni_stepdetector.cpp jni_libpasada.cpp LibPasada.cpp + #// jni_logging.cpp // same implemented in jni_libpasada.cpp // JNI_OnLoad and JNI_OnUnload ) find_package (oboe REQUIRED CONFIG) -add_library(ndk-logger SHARED logging.cpp jni_logging.cpp) +add_library(ndk-logger SHARED logging.cpp) # jni_logging.cpp target_link_libraries(ndk-logger log) # Add pre-built libmpg123 library diff --git a/app/src/main/cpp/LibPasada.cpp b/app/src/main/cpp/LibPasada.cpp index 0c88191..f83b17a 100644 --- a/app/src/main/cpp/LibPasada.cpp +++ b/app/src/main/cpp/LibPasada.cpp @@ -13,6 +13,10 @@ void PasadaPlaybackListener::onError(int errorCode, std::string message) { emitError(errorCode, message); } +void PasadaPlaybackListener::onTrackClosed(int fd) { + emitTrackClosed(fd); +} + /** * Mirrors the libpasada state machine documented in DESIGN.md. * Keep values in sync with PasadaState.java @@ -57,7 +61,7 @@ void LibPasada::init() { std::lock_guard lock(mtxDetector); if(state == LOADED || state == STOPPED) { // perform init - auto *playbackEngine = new PlaybackEngine("", 0); + auto *playbackEngine = new PlaybackEngine("", 0, this); engine = playbackEngine; auto *stepListener = reinterpret_cast(playbackEngine); @@ -100,6 +104,7 @@ void LibPasada::pause() { std::lock_guard lockState(mtxState); if(state != PLAYING) return; state = PAUSED; + // TODO: pause plays a rect noise in PlaybackEngine. debug it! auto *playbackEngine = reinterpret_cast(engine); playbackEngine->pause(); } diff --git a/app/src/main/cpp/LibPasada.h b/app/src/main/cpp/LibPasada.h index f1e8538..0273794 100644 --- a/app/src/main/cpp/LibPasada.h +++ b/app/src/main/cpp/LibPasada.h @@ -12,6 +12,7 @@ // JNI helpers for calling into Java code (PasadaPlaybackListener) void emitTrackFinished(); void emitError(int errorCode, const std::string& message); +void emitTrackClosed(int fd); /** * Mirrors the libpasada state machine documented in DESIGN.md. diff --git a/app/src/main/cpp/PasadaPlaybackListener.h b/app/src/main/cpp/PasadaPlaybackListener.h index d6ed39e..e78dab3 100644 --- a/app/src/main/cpp/PasadaPlaybackListener.h +++ b/app/src/main/cpp/PasadaPlaybackListener.h @@ -16,6 +16,7 @@ public: virtual ~PasadaPlaybackListener() = default; /** Current track reached end; service should advance queue and call {@link LibPasada#play}. */ virtual void onTrackFinished(); + virtual void onTrackClosed(int fd); /** Decode / Oboe / pipeline failure; service should stop safely and surface error. */ virtual void onError(int errorCode, std::string message); diff --git a/app/src/main/cpp/PlaybackEngine.cpp b/app/src/main/cpp/PlaybackEngine.cpp index 9627dbb..a13189b 100644 --- a/app/src/main/cpp/PlaybackEngine.cpp +++ b/app/src/main/cpp/PlaybackEngine.cpp @@ -13,6 +13,7 @@ #include #include #include +#include /** * Read samples from the next mp3-frame into the struct MP3File's buffer. @@ -168,11 +169,13 @@ void PlaybackEngine::closeMusicFile() { haveMusicFile.store(false); musicFile = nullptr; if(android_fd) { - close(android_fd); + //close(android_fd); + if(listener) listener->onTrackClosed(android_fd); android_fd = 0; } isSetMusic.store(false); - mPlayer->setMusic(nullptr); + if(mPlayer) + mPlayer->setMusic(nullptr); } void PlaybackEngine::mapChannels(int *channel_map, int num_ch_in, int num_ch_out) { @@ -198,6 +201,10 @@ void PlaybackEngine::mapChannels(int *channel_map, int num_ch_in, int num_ch_out } void PlaybackEngine::musicFeedThread() { + /* + std::mt19937 mt(42); + std::uniform_real_distribution dist(0.98, 1.0); +*/ LOGI("starting musicFeedThread()"); // strecher num channels: same as output num channels @@ -304,7 +311,10 @@ void PlaybackEngine::musicFeedThread() { // 1024, 512, 512 // 7 x 512 } - memset(buf, 0, num_samples*num_ch_out*sizeof(float)); + //memset(buf, 0, num_samples*num_ch_out*sizeof(float)); // wrong, because buffer is not contiguous for channels + for(int i = 0; i < num_ch_out; i++) { + memset(buf_ptr[i], 0, num_samples * sizeof(float)); + } stretcher.process(buf_ptr, num_samples, false); continue; } @@ -315,6 +325,13 @@ void PlaybackEngine::musicFeedThread() { // feed 1024 music samples // => stretcher is asking for 1024 = getSamplesRequired() // ca. 21 ms + +/* + int64_t a = mpg123_seek(musicFile->handle, 0, SEEK_CUR); + int64_t b = mpg123_tell(musicFile->handle); + LOGI(" checking mpg123_seek(): len=%lld seek=%lld tell=%lld", + (long long)musicFile->num_samples, (long long)a, (long long)b); +*/ } markerPos.store((musicFile->num_samples - musicFile->remaining_samples) / musicFile->samples_per_frame * musicFile->secs_per_frame); @@ -324,30 +341,48 @@ void PlaybackEngine::musicFeedThread() { double pos = seekPos.load(); // compute seek target in samples, clip to [0..num_samples] auto seek_samples = static_cast(pos * musicFile->samples_per_frame / musicFile->secs_per_frame); + LOGI("seek to %.3lf sec = %lld samples", pos, (long long) seek_samples); seek_samples = std::max(seek_samples, 0L); seek_samples = std::min(seek_samples, musicFile->num_samples); - // compute seek delta, to update byte 'offset' - auto seek_samples_delta = seek_samples - (musicFile->num_samples - musicFile->remaining_samples); - auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t); // perform the seek - off_t seekResult = mpg123_seek(musicFile->handle, musicFile->num_samples - musicFile->remaining_samples, SEEK_SET); - if(seekResult != seek_samples) { - LOGE("error seeking in mp3 file: mpg123_seek(handle, %d, SEEK_SET)=%d", seek_samples, seekResult); - } - // update structure - musicFile->remaining_samples = static_cast(musicFile->num_samples - seek_samples); - musicFile->offset += seek_bytes_delta; - requestSeek.store(false); + off_t seekResult = mp3file_seek(musicFile.get(), seek_samples); + idebug = 0; + /*if(seekResult > seek_samples) { + LOGE("error seeking in mp3 file: mpg123_seek(handle, %ld, SEEK_SET)=%lld", seek_samples, (long long) seekResult); + seekPos.store(pos * dist(mt)); // randomize seek target a bit + } else {*/ + // update structure + // compute seek delta, to update byte 'offset' + //auto seek_samples_delta = seekResult - (musicFile->num_samples - musicFile->remaining_samples); + //auto seek_bytes_delta = seek_samples_delta * num_ch_in * sizeof(int16_t); + musicFile->remaining_samples = static_cast(musicFile->num_samples - seekResult); + //musicFile->offset += seek_bytes_delta; + musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR); + // it is not possible to obtain bytes offset reliably?? + //musicFile->num_bytes = -1; // pretend we can read until the end + requestSeek.store(false); + /*}*/ } size_t done = 0; // bytes! - size_t read_size_bytes_calc = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes); - size_t read_size_bytes = read_size_bytes_calc; - if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); } + size_t read_size_bytes = std::min(num_samples * num_ch_in * sizeof(int16_t), cbuf_size_bytes); + /*if (musicFile->offset != 0 && musicFile->num_bytes != -1) { read_size_bytes = std::min(read_size_bytes_calc, (size_t) musicFile->num_bytes - musicFile->offset); }*/ + //if (idebug < 10) LOGI("mft rsbc=%lld rsb=%lld nb=%lld o=%lld", (long long) read_size_bytes_calc, (long long) read_size_bytes, (long long) musicFile->num_bytes, (long long) musicFile->offset); int err = mpg123_read(musicFile->handle, cbuf, read_size_bytes, &done); - if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes <= read_size_bytes_calc) { err = MPG123_DONE; } + //if (idebug < 10) LOGI("mft done=%lld err=%lld small=%d", (long long) done, (long long) err, read_size_bytes < read_size_bytes_calc); + /*if (musicFile->offset != 0 && musicFile->num_bytes != -1 && err == MPG123_OK && read_size_bytes < read_size_bytes_calc) { + LOGW("out of bytes, finished playing"); + err = MPG123_DONE; + }*/ musicFile->remaining_samples -= done / sizeof(int16_t); - musicFile->offset += done; + //musicFile->offset += done; + musicFile->offset = lseek(musicFile->android_fd, 0, SEEK_CUR); + if(musicFile->offset >= musicFile->num_bytes) { + // TODO: this is inaccurate. either we do not decode everything, or we overrun past the end + // TODO: maybe decode as a "stream" instead. + LOGW("out of bytes, finished playing"); + err = MPG123_DONE; + } if (err != MPG123_OK && err != MPG123_DONE) { // error! LOGE("error reading mp3 file: mpg123_read() err=%d done=%d", err, done); @@ -363,6 +398,7 @@ void PlaybackEngine::musicFeedThread() { // next iteration will play silence // we keep Stretcher and Oboe alive LOGI("finished reading mp3 file (MPG123_DONE)"); + idebug = 0; closeMusicFile(); if(listener) listener->onTrackFinished(); continue; @@ -394,6 +430,7 @@ void PlaybackEngine::musicFeedThread() { } void PlaybackEngine::pause() { + LOGI("PlaybackEngine::pause() set isPaused."); // next iteration will play silence isPaused.store(true); } diff --git a/app/src/main/cpp/jni_libpasada.cpp b/app/src/main/cpp/jni_libpasada.cpp index 62b217c..9906eed 100644 --- a/app/src/main/cpp/jni_libpasada.cpp +++ b/app/src/main/cpp/jni_libpasada.cpp @@ -4,28 +4,55 @@ #include #include #include "LibPasada.h" +#include "logging.h" static JavaVM* g_vm = nullptr; static jobject g_listener = nullptr; // global ref, or nullptr static jmethodID g_onTrackFinished = nullptr; +static jmethodID g_onTrackClosed = nullptr; static jmethodID g_onError = nullptr; -static LibPasada* g_libpasada = new LibPasada(); +static LibPasada* g_libpasada = nullptr; void clearListener(JNIEnv* env); extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { g_vm = vm; + + JNIEnv * env = NULL; + //jclass clazz; + //LOGV("JNI_Onload vm:%p reserved:%p", vm, reserved); + + if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) { + return JNI_VERSION_1_6; + } + + logback_init(vm, env); + LOGD("liblockstep-native jni_libpasada.cpp JNI_OnLoad()"); + return JNI_VERSION_1_6; } +extern "C" void +JNI_OnUnload(JavaVM * vm, void * reserved) +{ + JNIEnv * env = NULL; + //LOGV("JNI_OnUnload vm:%p reserved:%p", vm, reserved); + + if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) { + return; + } + logback_uninit(env); +} + /** Called each time a run/playlist session starts (Oboe silent, buffers ready). */ extern "C" JNIEXPORT void JNICALL Java_at_lockstep_player_pasada_LibPasada_init( JNIEnv *env, jclass /*unused*/) { + LOGD("liblockstep-native jni_libpasada.cpp LibPasada_init()"); if (g_libpasada) { clearListener(env); delete g_libpasada; @@ -33,6 +60,7 @@ Java_at_lockstep_player_pasada_LibPasada_init( try { g_libpasada = new LibPasada(); g_libpasada->init(); + return; } catch (const std::bad_alloc&) { jclass cls = env->FindClass("java/lang/OutOfMemoryError"); if (cls) env->ThrowNew(cls, "native allocation failed"); @@ -83,6 +111,7 @@ extern "C" JNIEXPORT void JNICALL Java_at_lockstep_player_pasada_LibPasada_play(JNIEnv *env, jclass clazz, jint fd, jlong offset, jlong length) { + LOGD("liblockstep-native jni_libpasada.cpp LibPasada_play()"); if (g_libpasada == nullptr) return; @@ -306,6 +335,7 @@ Java_at_lockstep_player_pasada_LibPasada_setPlaybackListener(JNIEnv *env, jclass // 3. Resolve methods once (valid until class unload) jclass cls = env->GetObjectClass(g_listener); g_onTrackFinished = env->GetMethodID(cls, "onTrackFinished", "()V"); + g_onTrackClosed = env->GetMethodID(cls, "onTrackClosed", "(I)V"); g_onError = env->GetMethodID( cls, "onError", "(ILjava/lang/String;)V"); env->DeleteLocalRef(cls); @@ -338,6 +368,14 @@ void emitError(int errorCode, const char* message) { env->ExceptionClear(); } } +void emitTrackClosed(int fd) { + if (!g_vm || !g_listener || !g_onTrackClosed) return; + JNIEnv* env = nullptr; + if (g_vm->AttachCurrentThread(&env, nullptr) != JNI_OK) return; + env->CallVoidMethod(g_listener, g_onTrackClosed, static_cast(fd)); + if (env->ExceptionCheck()) env->ExceptionClear(); + // DetachCurrentThread only if this thread won't call JNI again +} void emitError(int errorCode, const std::string& message) { emitError(errorCode, message.c_str()); } diff --git a/app/src/main/cpp/jni_lockstep.cpp b/app/src/main/cpp/jni_lockstep.cpp index 04085db..8584bc1 100644 --- a/app/src/main/cpp/jni_lockstep.cpp +++ b/app/src/main/cpp/jni_lockstep.cpp @@ -22,6 +22,27 @@ Java_at_lockstep_pb_PlaybackEngine_native_1createEngine( return reinterpret_cast(engine); } +JNIEXPORT void JNICALL +Java_at_lockstep_pb_PlaybackEngine_native_1stop( + JNIEnv *env, + jclass, + jlong engineHandle) { + + auto *engine = reinterpret_cast(engineHandle); + engine->stop(); +} + +JNIEXPORT void JNICALL +Java_at_lockstep_pb_PlaybackEngine_native_1pause( + JNIEnv *env, + jclass, + jlong engineHandle) { + + auto *engine = reinterpret_cast(engineHandle); + // TODO: this is broken. there is a square wave noise played, instead of silence + engine->pause(); // do not tear down the Oboe pipeline, such that we can play again later (without having to re-construct PlaybackEngine) +} + JNIEXPORT void JNICALL Java_at_lockstep_pb_PlaybackEngine_native_1deleteEngine( JNIEnv *env, diff --git a/app/src/main/cpp/jni_logging.cpp b/app/src/main/cpp/jni_logging.cpp index fd8f400..6437a16 100644 --- a/app/src/main/cpp/jni_logging.cpp +++ b/app/src/main/cpp/jni_logging.cpp @@ -66,14 +66,14 @@ JNI_OnLoad(JavaVM * vm, void * reserved) jclass clazz; //LOGV("JNI_Onload vm:%p reserved:%p", vm, reserved); - if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_4)) { - //LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_4 failed"); - return JNI_VERSION_1_4; + if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) { + //LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_6 failed"); + return JNI_VERSION_1_6; } logback_init(vm, env); - return JNI_VERSION_1_4; + return JNI_VERSION_1_6; } extern "C" void @@ -82,8 +82,8 @@ JNI_OnUnload(JavaVM * vm, void * reserved) JNIEnv * env = NULL; //LOGV("JNI_OnUnload vm:%p reserved:%p", vm, reserved); - if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_4)) { - //LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_4 failed"); + if (JNI_OK != vm->GetEnv((void**) &env, JNI_VERSION_1_6)) { + //LOGE("JNI_OnUnload GetEnv JNI_VERSION_1_6 failed"); return; } diff --git a/app/src/main/cpp/logging.cpp b/app/src/main/cpp/logging.cpp index 46111a9..9b5cbad 100644 --- a/app/src/main/cpp/logging.cpp +++ b/app/src/main/cpp/logging.cpp @@ -91,7 +91,7 @@ __logback_print(const int level, const char * format, ...) JNIEnv * env = NULL; jint err = JNI_OK; if (! __logback_jvm) return; - if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) { + if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) { if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) { __android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread"); } @@ -115,7 +115,7 @@ __logback_vprint(const int level, const char * format, va_list ap) JNIEnv * env = NULL; jint err = JNI_OK; if (! __logback_jvm) return; - if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) { + if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) { if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) { __android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread"); } @@ -136,7 +136,7 @@ __logback_write(const int level, const char * msg) JNIEnv * env = NULL; jint err = JNI_OK; if (! __logback_jvm) return; - if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_4))) { + if (JNI_EDETACHED == (err = __logback_jvm->GetEnv((void**) &env, JNI_VERSION_1_6))) { if (__logback_jvm->AttachCurrentThread(&env, NULL) < 0) { __android_log_write(LOG_LEVEL_WARN, LOG_TAG, "__logback_write failed to get env neither attach current thread"); } diff --git a/app/src/main/cpp/mp3file.cpp b/app/src/main/cpp/mp3file.cpp index 9755ae2..b49a549 100644 --- a/app/src/main/cpp/mp3file.cpp +++ b/app/src/main/cpp/mp3file.cpp @@ -105,6 +105,8 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn goto on_error; \ } while(0) + int64_t len, a, b; + int err = MPG123_OK; mpg123_handle *mh = mpg123_new(NULL, &err); if(err != MPG123_OK || mh == NULL) { @@ -119,6 +121,9 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn err = mpg123_open_fd(mh, fd); if(err != MPG123_OK) handleError("mpg123_open()"); + // hopefully the magic bullet for seeking in the file (otherwise, mpg123_seek() returns totally random offsets) + mpg123_scan(mh); + int encoding; err = mpg123_getformat(mh, &mp3->rate, &mp3->channels, &encoding); if(err != MPG123_OK) handleError("mpg123_getformat()"); @@ -156,6 +161,17 @@ MP3File* mp3file_open_fd(int fd, long long offset, long long length, int forceEn mp3->remaining_samples = (int) mp3->num_samples; LOGI("channels: %d rate: %ld num_samples: %ld", mp3->channels, mp3->rate, mp3->num_samples); mp3->android_fd = fd; + + // begin debug: seek returns wildly random offsets + + len = mpg123_length(mh); + a = mpg123_seek(mh, 0, SEEK_SET); + b = mpg123_tell(mh); + + LOGI("using mpg123_scan: len=%lld seek=%lld tell=%lld", + (long long)len, (long long)a, (long long)b); + // end debug + return mp3; on_error: @@ -165,3 +181,22 @@ on_error: #undef handleError } + +off_t mp3file_seek(MP3File *mp3file, off_t samples) { + LOGD("mp3file_seek(): seek to %ld samples", samples); + off_t seekResult = mpg123_seek(mp3file->handle, samples, SEEK_SET); + LOGD(" mp3file_seek(): %ld samples remaining", samples - seekResult); + if(seekResult > samples + 4800) { + LOGE("mp3file_seek(): error: landed beyond the seek position!"); + return seekResult; + } + // try to refine the seek position + int max_iter = 6; + int cur_iter = 0; + for(; cur_iter < max_iter && seekResult < samples - 4800; cur_iter++) { + seekResult = mpg123_seek(mp3file->handle, samples - seekResult, SEEK_CUR); + LOGD(" mp3file_seek(): %ld samples remaining", samples - seekResult); + } + LOGD("mp3file_seek(): finished"); + return seekResult; +} diff --git a/app/src/main/cpp/mp3file.h b/app/src/main/cpp/mp3file.h index 1015f05..80f3fd7 100644 --- a/app/src/main/cpp/mp3file.h +++ b/app/src/main/cpp/mp3file.h @@ -33,5 +33,6 @@ MP3File* mp3file_init(mpg123_handle *handle); void mp3file_delete(MP3File *mp3file); MP3File* mp3file_open(const char *filename, int forceEncoding = 0); MP3File* mp3file_open_fd(int fd, long long offset = 0, long long length = -1, int forceEncoding = 0); +off_t mp3file_seek(MP3File *mp3file, off_t samples); #endif //SAMPLES_MP3FILE_H diff --git a/app/src/main/java/at/lockstep/app/LstForegroundService.java b/app/src/main/java/at/lockstep/app/LstForegroundService.java index 96d02ba..b95ce3d 100644 --- a/app/src/main/java/at/lockstep/app/LstForegroundService.java +++ b/app/src/main/java/at/lockstep/app/LstForegroundService.java @@ -40,6 +40,7 @@ public class LstForegroundService extends Service implements SensorEventListener public static final String ACTION_START = "at.lockstep.action.START"; public static final String ACTION_STOP = "at.lockstep.action.STOP"; + public static final String ACTION_PAUSE = "at.lockstep.action.PAUSE"; private SensorManager sensorManager; private Sensor accelerometer; @@ -60,6 +61,11 @@ public class LstForegroundService extends Service implements SensorEventListener intent.setAction(ACTION_STOP); return intent; } + public static Intent pauseIntent(Context context) { + Intent intent = new Intent(context, LstForegroundService.class); + intent.setAction(ACTION_PAUSE); + return intent; + } @Override public void onCreate() { @@ -98,7 +104,7 @@ public class LstForegroundService extends Service implements SensorEventListener String contentUri = intent.getStringExtra("content_uri"); try { if(contentUri != null) { - PlaybackEngine.playMusic(uriToFd(contentUri)); + PlaybackEngine.playMusic(uriToFd(contentUri), this, R.raw.track_beat); } } catch (IOException e) { // TODO proper error handling @@ -109,6 +115,10 @@ public class LstForegroundService extends Service implements SensorEventListener } else if (ACTION_STOP.equals(action)) { Log.i("LstForegroundService", "ACTION_STOP"); stopCollectionAndSelf(); + PlaybackEngine.stop(); + } else if (ACTION_PAUSE.equals(action)) { + Log.i("LstForegroundService", "ACTION_PAUSE"); + PlaybackEngine.pause(); } } return START_STICKY; diff --git a/app/src/main/java/at/lockstep/app/MainActivity.java b/app/src/main/java/at/lockstep/app/MainActivity.java index 2d1a8ee..fe258c3 100644 --- a/app/src/main/java/at/lockstep/app/MainActivity.java +++ b/app/src/main/java/at/lockstep/app/MainActivity.java @@ -32,6 +32,7 @@ import java.io.Writer; public class MainActivity extends AppCompatActivity implements LstForegroundService.OnResultListener { private Button btnStart; private Button btnStop; + private Button btnPause; private Button btnMediaStoreBenchmark; private Button btnPickSong; private final ActivityResultLauncher launcher; @@ -61,6 +62,7 @@ public class MainActivity extends AppCompatActivity implements LstForegroundServ btnStart = findViewById(R.id.btnStart); btnStop = findViewById(R.id.btnStop); + btnPause = findViewById(R.id.btnPause); btnMediaStoreBenchmark = findViewById(R.id.btnMediaStoreBenchmark); btnPickSong = findViewById(R.id.btnPickSong); @@ -83,6 +85,10 @@ public class MainActivity extends AppCompatActivity implements LstForegroundServ startService(LstForegroundService.stopIntent(MainActivity.this)) ); + btnPause.setOnClickListener(v -> + startService(LstForegroundService.pauseIntent(MainActivity.this)) + ); + btnMediaStoreBenchmark.setOnClickListener(v -> { Intent intent = new Intent(MainActivity.this, MediaStoreBenchmarkActivity.class); startActivity(intent); diff --git a/app/src/main/java/at/lockstep/pb/PlaybackEngine.java b/app/src/main/java/at/lockstep/pb/PlaybackEngine.java index 4c73868..d278316 100644 --- a/app/src/main/java/at/lockstep/pb/PlaybackEngine.java +++ b/app/src/main/java/at/lockstep/pb/PlaybackEngine.java @@ -76,16 +76,38 @@ public class PlaybackEngine { return mEngineHandle; } - public static void playMusic(int fd) { + public static void playMusic(int fd, Context context, int beat_resid) { + if (mEngineHandle == 0) { + create(context, beat_resid); + } if (mEngineHandle != 0) { native_playMusic(mEngineHandle, fd); } } + public static void stop() { + if (mEngineHandle != 0) { + //native_stop(mEngineHandle); + // for now, (pause is bugged), deleting engine is the only clean way + native_deleteEngine(mEngineHandle); + mEngineHandle = 0; + } + } + + public static void pause() { + if (mEngineHandle != 0) { + Log.i("PlaybackEngine", "native_pause() ..."); + native_pause(mEngineHandle); + Log.i("PlaybackEngine", "native_pause() done."); + } + } + private static native long native_createEngine(String filesDir, int resid); private static native void native_deleteEngine(long engineHandle); private static native void native_setDefaultStreamValues(int sampleRate, int framesPerBurst); private static native int native_mpg123_init(); private static native void native_playMusic(long engineHandle, int fd); + private static native void native_stop(long engineHandle); + private static native void native_pause(long engineHandle); } diff --git a/app/src/main/java/at/lockstep/player/pasada/PasadaPlaybackListener.java b/app/src/main/java/at/lockstep/player/pasada/PasadaPlaybackListener.java index 48e9009..54c4cbc 100644 --- a/app/src/main/java/at/lockstep/player/pasada/PasadaPlaybackListener.java +++ b/app/src/main/java/at/lockstep/player/pasada/PasadaPlaybackListener.java @@ -9,6 +9,8 @@ public interface PasadaPlaybackListener { /** Current track reached end; service should advance queue and call {@link LibPasada#play}. */ void onTrackFinished(); + void onTrackClosed(int fd); + /** Decode / Oboe / pipeline failure; service should stop safely and surface error. */ void onError(int errorCode, String message); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2690754..8f6464e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -19,6 +19,13 @@ android:layout_marginTop="16dp" android:text="Stop collection" /> +