1*e712b0bcSBartłomiej Klocek // Copyright © 2021-present 650 Industries, Inc. (aka Expo) 2*e712b0bcSBartłomiej Klocek 3*e712b0bcSBartłomiej Klocek #include "JAVManager.h" 4*e712b0bcSBartłomiej Klocek 5*e712b0bcSBartłomiej Klocek #include <jsi/jsi.h> 6*e712b0bcSBartłomiej Klocek 7*e712b0bcSBartłomiej Klocek #include <jni.h> 8*e712b0bcSBartłomiej Klocek #include <fbjni/fbjni.h> 9*e712b0bcSBartłomiej Klocek 10*e712b0bcSBartłomiej Klocek #include <memory> 11*e712b0bcSBartłomiej Klocek #include <string> 12*e712b0bcSBartłomiej Klocek 13*e712b0bcSBartłomiej Klocek #include <ReactCommon/CallInvokerHolder.h> 14*e712b0bcSBartłomiej Klocek #include <ReactCommon/CallInvoker.h> 15*e712b0bcSBartłomiej Klocek 16*e712b0bcSBartłomiej Klocek #include <android/log.h> 17*e712b0bcSBartłomiej Klocek 18*e712b0bcSBartłomiej Klocek namespace expo { 19*e712b0bcSBartłomiej Klocek namespace av { 20*e712b0bcSBartłomiej Klocek 21*e712b0bcSBartłomiej Klocek namespace jsi = facebook::jsi; 22*e712b0bcSBartłomiej Klocek namespace jni = facebook::jni; 23*e712b0bcSBartłomiej Klocek 24*e712b0bcSBartłomiej Klocek jni::local_ref<JAVManager::jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis)25*e712b0bcSBartłomiej Klocek JAVManager::initHybrid(jni::alias_ref<jhybridobject> jThis) { 26*e712b0bcSBartłomiej Klocek return makeCxxInstance(jThis); 27*e712b0bcSBartłomiej Klocek } 28*e712b0bcSBartłomiej Klocek registerNatives()29*e712b0bcSBartłomiej Klocek void JAVManager::registerNatives() { 30*e712b0bcSBartłomiej Klocek registerHybrid({ 31*e712b0bcSBartłomiej Klocek makeNativeMethod("initHybrid", JAVManager::initHybrid), 32*e712b0bcSBartłomiej Klocek makeNativeMethod("installJSIBindings", JAVManager::installJSIBindings), 33*e712b0bcSBartłomiej Klocek }); 34*e712b0bcSBartłomiej Klocek } 35*e712b0bcSBartłomiej Klocek getMediaPlayerById(int id)36*e712b0bcSBartłomiej Klocek JPlayerData *JAVManager::getMediaPlayerById(int id) { 37*e712b0bcSBartłomiej Klocek static const auto func = javaPart_->getClass()->getMethod<JPlayerData *(jint)>( 38*e712b0bcSBartłomiej Klocek "getMediaPlayerById"); 39*e712b0bcSBartłomiej Klocek auto result = func(javaPart_.get(), id); 40*e712b0bcSBartłomiej Klocek return result->cthis(); 41*e712b0bcSBartłomiej Klocek } 42*e712b0bcSBartłomiej Klocek installJSIBindings(jlong jsRuntimePointer,jni::alias_ref<facebook::react::CallInvokerHolder::javaobject> jsCallInvokerHolder)43*e712b0bcSBartłomiej Klocek void JAVManager::installJSIBindings(jlong jsRuntimePointer, 44*e712b0bcSBartłomiej Klocek jni::alias_ref<facebook::react::CallInvokerHolder::javaobject> jsCallInvokerHolder) { 45*e712b0bcSBartłomiej Klocek auto &runtime = *reinterpret_cast<jsi::Runtime *>(jsRuntimePointer); 46*e712b0bcSBartłomiej Klocek auto callInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); 47*e712b0bcSBartłomiej Klocek 48*e712b0bcSBartłomiej Klocek auto function = [this, callInvoker](jsi::Runtime &runtime, 49*e712b0bcSBartłomiej Klocek const jsi::Value &thisValue, 50*e712b0bcSBartłomiej Klocek const jsi::Value *args, 51*e712b0bcSBartłomiej Klocek size_t argsCount) -> jsi::Value { 52*e712b0bcSBartłomiej Klocek auto playerId = args[0].asNumber(); 53*e712b0bcSBartłomiej Klocek 54*e712b0bcSBartłomiej Klocek auto mediaPlayer = getMediaPlayerById(static_cast<int>(playerId)); 55*e712b0bcSBartłomiej Klocek if (mediaPlayer == nullptr) { 56*e712b0bcSBartłomiej Klocek auto message = "Sound Instance with ID " + std::to_string(playerId) + 57*e712b0bcSBartłomiej Klocek "does not exist!"; 58*e712b0bcSBartłomiej Klocek throw jsi::JSError(runtime, message.c_str()); 59*e712b0bcSBartłomiej Klocek } 60*e712b0bcSBartłomiej Klocek 61*e712b0bcSBartłomiej Klocek if (argsCount > 1 && args[1].isObject()) { 62*e712b0bcSBartłomiej Klocek // second parameter received, it's the callback function. 63*e712b0bcSBartłomiej Klocek auto message = "Setting Audio Sample Buffer Callback for Player " + 64*e712b0bcSBartłomiej Klocek std::to_string(playerId) + "..."; 65*e712b0bcSBartłomiej Klocek __android_log_write(ANDROID_LOG_INFO, TAG, message.c_str()); 66*e712b0bcSBartłomiej Klocek 67*e712b0bcSBartłomiej Klocek auto callback = args[1].asObject(runtime).asFunction(runtime); 68*e712b0bcSBartłomiej Klocek auto callbackShared = std::make_shared<jsi::Function>(std::move(callback)); 69*e712b0bcSBartłomiej Klocek 70*e712b0bcSBartłomiej Klocek mediaPlayer->setSampleBufferCallback([callbackShared, &runtime, callInvoker]( 71*e712b0bcSBartłomiej Klocek jni::alias_ref <jni::JArrayByte> sampleBuffer, double positionSeconds) { 72*e712b0bcSBartłomiej Klocek auto channelsCount = /* TODO: channelsCount */ 1; 73*e712b0bcSBartłomiej Klocek auto size = sampleBuffer->size(); 74*e712b0bcSBartłomiej Klocek 75*e712b0bcSBartłomiej Klocek // TODO: Avoid copy by directly using ArrayBuffer? Or accessing values in the sampleBuffer? 76*e712b0bcSBartłomiej Klocek // copies the JNI array (signed 8 bit int (byte)) into a vector and reinterprets it as an unsigned 8 bit int (u_byte) 77*e712b0bcSBartłomiej Klocek std::vector<uint8_t> buffer(size); 78*e712b0bcSBartłomiej Klocek sampleBuffer->getRegion(0, size, reinterpret_cast<int8_t *>(buffer.data())); 79*e712b0bcSBartłomiej Klocek 80*e712b0bcSBartłomiej Klocek // TODO: Run per channel instead of flat array? 81*e712b0bcSBartłomiej Klocek auto channels = jsi::Array(runtime, channelsCount); 82*e712b0bcSBartłomiej Klocek for (auto i = 0; i < channelsCount; i++) { 83*e712b0bcSBartłomiej Klocek auto channel = jsi::Object(runtime); 84*e712b0bcSBartłomiej Klocek 85*e712b0bcSBartłomiej Klocek auto frames = jsi::Array(runtime, size); 86*e712b0bcSBartłomiej Klocek 87*e712b0bcSBartłomiej Klocek for (size_t ii = 0; ii < size; ii++) { 88*e712b0bcSBartłomiej Klocek // `buffer` is interpreted as a 8-bit unsigned integer (byte), so it ranges from 89*e712b0bcSBartłomiej Klocek // 0 to 255. To normalize it to a -1..1 scale we subtract 128 and divide by 128. 90*e712b0bcSBartłomiej Klocek double frame = (static_cast<double>(buffer[ii]) - 128) / 128.0; 91*e712b0bcSBartłomiej Klocek frames.setValueAtIndex(runtime, ii, jsi::Value(frame)); 92*e712b0bcSBartłomiej Klocek } 93*e712b0bcSBartłomiej Klocek 94*e712b0bcSBartłomiej Klocek channel.setProperty(runtime, "frames", frames); 95*e712b0bcSBartłomiej Klocek channels.setValueAtIndex(runtime, i, channel); 96*e712b0bcSBartłomiej Klocek } 97*e712b0bcSBartłomiej Klocek 98*e712b0bcSBartłomiej Klocek // TODO: Avoid smart pointer here? Cannot move into lambda... 99*e712b0bcSBartłomiej Klocek auto sample = std::make_shared<jsi::Object>(runtime); 100*e712b0bcSBartłomiej Klocek sample->setProperty(runtime, "channels", channels); 101*e712b0bcSBartłomiej Klocek sample->setProperty(runtime, "timestamp", jsi::Value(positionSeconds)); 102*e712b0bcSBartłomiej Klocek 103*e712b0bcSBartłomiej Klocek callInvoker->invokeAsync([callbackShared, &runtime, sample]() { 104*e712b0bcSBartłomiej Klocek try { 105*e712b0bcSBartłomiej Klocek jsi::Object *s = sample.get(); 106*e712b0bcSBartłomiej Klocek callbackShared->call(runtime, std::move(*s)); 107*e712b0bcSBartłomiej Klocek } catch (std::exception &exception) { 108*e712b0bcSBartłomiej Klocek auto message = "Sample Buffer Callback threw an error: " + 109*e712b0bcSBartłomiej Klocek std::string(exception.what()); 110*e712b0bcSBartłomiej Klocek __android_log_write(ANDROID_LOG_ERROR, TAG, message.c_str()); 111*e712b0bcSBartłomiej Klocek } 112*e712b0bcSBartłomiej Klocek }); 113*e712b0bcSBartłomiej Klocek }); 114*e712b0bcSBartłomiej Klocek } else { 115*e712b0bcSBartłomiej Klocek // second parameter omitted or undefined, so remove callback 116*e712b0bcSBartłomiej Klocek __android_log_write(ANDROID_LOG_INFO, TAG, 117*e712b0bcSBartłomiej Klocek "Unsetting Sample Buffer Callback..."); 118*e712b0bcSBartłomiej Klocek 119*e712b0bcSBartłomiej Klocek mediaPlayer->unsetSampleBufferCallback(); 120*e712b0bcSBartłomiej Klocek } 121*e712b0bcSBartłomiej Klocek 122*e712b0bcSBartłomiej Klocek return jsi::Value::undefined(); 123*e712b0bcSBartłomiej Klocek }; 124*e712b0bcSBartłomiej Klocek runtime.global().setProperty(runtime, 125*e712b0bcSBartłomiej Klocek "__EXAV_setOnAudioSampleReceivedCallback", 126*e712b0bcSBartłomiej Klocek jsi::Function::createFromHostFunction(runtime, 127*e712b0bcSBartłomiej Klocek jsi::PropNameID::forAscii( 128*e712b0bcSBartłomiej Klocek runtime, 129*e712b0bcSBartłomiej Klocek "__EXAV_setOnAudioSampleReceivedCallback"), 130*e712b0bcSBartłomiej Klocek 2, 131*e712b0bcSBartłomiej Klocek function)); 132*e712b0bcSBartłomiej Klocek } 133*e712b0bcSBartłomiej Klocek 134*e712b0bcSBartłomiej Klocek } // namespace av 135*e712b0bcSBartłomiej Klocek } // namespace expo 136