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