1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #ifndef LOG_TAG
16 #define LOG_TAG "NapiAudioRendererCallback"
17 #endif
18
19 #include "js_native_api.h"
20 #include "napi_audio_renderer_callback.h"
21 #include "napi_param_utils.h"
22 #include "napi_audio_error.h"
23
24 namespace OHOS {
25 namespace AudioStandard {
26
NapiAudioRendererCallback(napi_env env)27 NapiAudioRendererCallback::NapiAudioRendererCallback(napi_env env)
28 : env_(env)
29 {
30 AUDIO_DEBUG_LOG("instance create");
31 }
32
~NapiAudioRendererCallback()33 NapiAudioRendererCallback::~NapiAudioRendererCallback()
34 {
35 if (regArStateChgTsfn_) {
36 napi_release_threadsafe_function(arStateChgTsfn_, napi_tsfn_abort);
37 } else if (regArInterruptTsfn_) {
38 napi_release_threadsafe_function(arInterruptTsfn_, napi_tsfn_abort);
39 }
40 AUDIO_DEBUG_LOG("instance destroy");
41 }
42
OnInterrupt(const InterruptEvent & interruptEvent)43 void NapiAudioRendererCallback::OnInterrupt(const InterruptEvent &interruptEvent)
44 {
45 std::lock_guard<std::mutex> lock(mutex_);
46 AUDIO_DEBUG_LOG("OnInterrupt is called,hintType: %{public}d", interruptEvent.hintType);
47 CHECK_AND_RETURN_LOG(interruptCallback_ != nullptr, "Cannot find the reference of interrupt callback");
48
49 std::unique_ptr<AudioRendererJsCallback> cb = std::make_unique<AudioRendererJsCallback>();
50 CHECK_AND_RETURN_LOG(cb != nullptr, "No memory");
51 cb->callback = interruptCallback_;
52 cb->callbackName = INTERRUPT_CALLBACK_NAME;
53 cb->interruptEvent = interruptEvent;
54 return OnJsCallbackInterrupt(cb);
55 }
56
OnStateChange(const RendererState state,const StateChangeCmdType cmdType)57 void NapiAudioRendererCallback::OnStateChange(const RendererState state,
58 const StateChangeCmdType __attribute__((unused)) cmdType)
59 {
60 std::lock_guard<std::mutex> lock(mutex_);
61 AUDIO_DEBUG_LOG("OnStateChange is called, state: %{public}d", state);
62 CHECK_AND_RETURN_LOG(stateChangeCallback_ != nullptr, "Cannot find the reference of stateChange callback");
63
64 std::unique_ptr<AudioRendererJsCallback> cb = std::make_unique<AudioRendererJsCallback>();
65 CHECK_AND_RETURN_LOG(cb != nullptr, "No memory");
66 cb->callback = stateChangeCallback_;
67 cb->callbackName = STATE_CHANGE_CALLBACK_NAME;
68 cb->state = state;
69 return OnJsCallbackStateChange(cb);
70 }
71
SaveCallbackReference(const std::string & callbackName,napi_value args)72 void NapiAudioRendererCallback::SaveCallbackReference(const std::string &callbackName, napi_value args)
73 {
74 std::lock_guard<std::mutex> lock(mutex_);
75 // create function that will operate while save callback reference success.
76 std::function<void(std::shared_ptr<AutoRef> generatedCallback)> successed =
77 [this, callbackName](std::shared_ptr<AutoRef> generatedCallback) {
78 if (callbackName == INTERRUPT_CALLBACK_NAME || callbackName == AUDIO_INTERRUPT_CALLBACK_NAME) {
79 interruptCallback_ = generatedCallback;
80 return;
81 }
82 if (callbackName == STATE_CHANGE_CALLBACK_NAME) {
83 stateChangeCallback_ = generatedCallback;
84 return;
85 }
86 };
87 NapiAudioRendererCallbackInner::SaveCallbackReferenceInner(callbackName, args, successed);
88 }
89
RemoveCallbackReference(const std::string & callbackName,napi_env env,napi_value callback,napi_value args)90 void NapiAudioRendererCallback::RemoveCallbackReference(const std::string &callbackName, napi_env env,
91 napi_value callback, napi_value args)
92 {
93 std::lock_guard<std::mutex> lock(mutex_);
94 // create function that will operate while save callback reference success.
95 std::function<void()> successed = [this, callbackName]() {
96 if (callbackName == INTERRUPT_CALLBACK_NAME || callbackName == AUDIO_INTERRUPT_CALLBACK_NAME) {
97 interruptCallback_ = nullptr;
98 return;
99 }
100 if (callbackName == STATE_CHANGE_CALLBACK_NAME) {
101 stateChangeCallback_ = nullptr;
102 return;
103 }
104 };
105 RemoveCallbackReferenceInner(callbackName, env, callback, successed);
106 }
107
GetCallback(const std::string & callbackName)108 std::shared_ptr<AutoRef> NapiAudioRendererCallback::GetCallback(const std::string &callbackName)
109 {
110 std::shared_ptr<AutoRef> cb = nullptr;
111 if (callbackName == INTERRUPT_CALLBACK_NAME || callbackName == AUDIO_INTERRUPT_CALLBACK_NAME) {
112 return interruptCallback_;
113 }
114 if (callbackName == STATE_CHANGE_CALLBACK_NAME) {
115 return stateChangeCallback_;
116 }
117 AUDIO_ERR_LOG("NapiAudioRendererCallback->GetCallback Unknown callback type: %{public}s", callbackName.c_str());
118 return cb;
119 }
120
CreateArInterrupt(napi_env env)121 void NapiAudioRendererCallback::CreateArInterrupt(napi_env env)
122 {
123 regArInterruptTsfn_ = true;
124 std::string callbackName = "Interrupt";
125 napi_value cbName;
126 napi_create_string_utf8(env, callbackName.c_str(), callbackName.length(), &cbName);
127 napi_create_threadsafe_function(env, nullptr, nullptr, cbName, 0, 1, nullptr,
128 InterruptTsfnFinalize, nullptr, SafeJsCallbackInterruptWork, &arInterruptTsfn_);
129 }
130
CreateArStateChange(napi_env env)131 void NapiAudioRendererCallback::CreateArStateChange(napi_env env)
132 {
133 regArStateChgTsfn_ = true;
134 napi_value cbName;
135 std::string callbackName = "stateChange";
136 napi_create_string_utf8(env_, callbackName.c_str(), callbackName.length(), &cbName);
137 napi_create_threadsafe_function(env, nullptr, nullptr, cbName, 0, 1, nullptr,
138 StateChangeTsfnFinalize, nullptr, SafeJsCallbackStateChangeWork, &arStateChgTsfn_);
139 }
140
GetArInterruptTsfnFlag()141 bool NapiAudioRendererCallback::GetArInterruptTsfnFlag()
142 {
143 return regArInterruptTsfn_;
144 }
145
GetArStateChangeTsfnFlag()146 bool NapiAudioRendererCallback::GetArStateChangeTsfnFlag()
147 {
148 return regArStateChgTsfn_;
149 }
150
GetEnv()151 napi_env &NapiAudioRendererCallback::GetEnv()
152 {
153 return env_;
154 }
155
CheckIfTargetCallbackName(const std::string & callbackName)156 bool NapiAudioRendererCallback::CheckIfTargetCallbackName(const std::string &callbackName)
157 {
158 if (callbackName == INTERRUPT_CALLBACK_NAME || callbackName == AUDIO_INTERRUPT_CALLBACK_NAME ||
159 callbackName == STATE_CHANGE_CALLBACK_NAME) {
160 return true;
161 }
162 return false;
163 }
164
SafeJsCallbackInterruptWork(napi_env env,napi_value js_cb,void * context,void * data)165 void NapiAudioRendererCallback::SafeJsCallbackInterruptWork(napi_env env, napi_value js_cb, void *context, void *data)
166 {
167 AudioRendererJsCallback *event = reinterpret_cast<AudioRendererJsCallback *>(data);
168 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr),
169 "OnJsCallbackInterrupt: no memory");
170 std::shared_ptr<AudioRendererJsCallback> safeContext(
171 static_cast<AudioRendererJsCallback*>(data),
172 [](AudioRendererJsCallback *ptr) {
173 delete ptr;
174 });
175 std::string request = event->callbackName;
176 napi_ref callback = event->callback->cb_;
177 napi_handle_scope scope = nullptr;
178 napi_open_handle_scope(env, &scope);
179 CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
180 AUDIO_INFO_LOG("SafeJsCallbackInterruptWork: safe js callback working.");
181
182 do {
183 napi_value jsCallback = nullptr;
184 napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
185 CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
186 request.c_str());
187 // Call back function
188 napi_value args[ARGS_ONE] = { nullptr };
189 NapiParamUtils::SetInterruptEvent(env, event->interruptEvent, args[0]);
190 CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
191 "%{public}s fail to create Interrupt callback", request.c_str());
192
193 const size_t argCount = ARGS_ONE;
194 napi_value result = nullptr;
195 nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
196 CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call Interrupt callback", request.c_str());
197 } while (0);
198 napi_close_handle_scope(env, scope);
199 }
200
InterruptTsfnFinalize(napi_env env,void * data,void * hint)201 void NapiAudioRendererCallback::InterruptTsfnFinalize(napi_env env, void *data, void *hint)
202 {
203 AUDIO_INFO_LOG("StateChangeTsfnFinalize: safe thread resource release.");
204 }
205
OnJsCallbackInterrupt(std::unique_ptr<AudioRendererJsCallback> & jsCb)206 void NapiAudioRendererCallback::OnJsCallbackInterrupt(std::unique_ptr<AudioRendererJsCallback> &jsCb)
207 {
208 if (jsCb.get() == nullptr) {
209 AUDIO_ERR_LOG("OnJsCallbackInterrupt: jsCb.get() is null");
210 return;
211 }
212
213 AudioRendererJsCallback *event = jsCb.release();
214 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr), "event is nullptr.");
215
216 napi_acquire_threadsafe_function(arInterruptTsfn_);
217 napi_call_threadsafe_function(arInterruptTsfn_, event, napi_tsfn_blocking);
218 }
219
SafeJsCallbackStateChangeWork(napi_env env,napi_value js_cb,void * context,void * data)220 void NapiAudioRendererCallback::SafeJsCallbackStateChangeWork(napi_env env, napi_value js_cb, void *context, void *data)
221 {
222 AudioRendererJsCallback *event = reinterpret_cast<AudioRendererJsCallback *>(data);
223 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr),
224 "OnJsCallbackStateChange: no memory");
225 std::shared_ptr<AudioRendererJsCallback> safeContext(
226 static_cast<AudioRendererJsCallback*>(data),
227 [](AudioRendererJsCallback *ptr) {
228 delete ptr;
229 });
230 std::string request = event->callbackName;
231 napi_ref callback = event->callback->cb_;
232 napi_handle_scope scope = nullptr;
233 napi_open_handle_scope(env, &scope);
234 CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
235 AUDIO_INFO_LOG("SafeJsCallbackStateChangeWork: safe js callback working.");
236
237 do {
238 napi_value jsCallback = nullptr;
239 napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
240 CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
241 request.c_str());
242
243 // Call back function
244 napi_value args[1] = { nullptr };
245 nstatus = NapiParamUtils::SetValueInt32(env, event->state, args[0]);
246 CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
247 "%{public}s fail to create Interrupt callback", request.c_str());
248
249 const size_t argCount = 1;
250 napi_value result = nullptr;
251 nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
252 CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call Interrupt callback", request.c_str());
253 } while (0);
254 napi_close_handle_scope(env, scope);
255 }
256
StateChangeTsfnFinalize(napi_env env,void * data,void * hint)257 void NapiAudioRendererCallback::StateChangeTsfnFinalize(napi_env env, void *data, void *hint)
258 {
259 AUDIO_INFO_LOG("StateChangeTsfnFinalize: safe thread resource release.");
260 }
261
OnJsCallbackStateChange(std::unique_ptr<AudioRendererJsCallback> & jsCb)262 void NapiAudioRendererCallback::OnJsCallbackStateChange(std::unique_ptr<AudioRendererJsCallback> &jsCb)
263 {
264 if (jsCb.get() == nullptr) {
265 AUDIO_ERR_LOG("OnJsCallbackStateChange: jsCb.get() is null");
266 return;
267 }
268
269 AudioRendererJsCallback *event = jsCb.release();
270 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback != nullptr), "event is nullptr.");
271
272 napi_acquire_threadsafe_function(arStateChgTsfn_);
273 napi_call_threadsafe_function(arStateChgTsfn_, event, napi_tsfn_blocking);
274 }
275 } // namespace AudioStandard
276 } // namespace OHOS