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