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