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 "NapiAudioRendererDeviceChangeCallback"
17 #endif
18
19 #include "js_native_api.h"
20 #include "napi_audio_renderer_device_change_callback.h"
21
22 namespace OHOS {
23 namespace AudioStandard {
NapiAudioRendererDeviceChangeCallback(napi_env env)24 NapiAudioRendererDeviceChangeCallback::NapiAudioRendererDeviceChangeCallback(napi_env env)
25 : env_(env)
26 {
27 AUDIO_INFO_LOG("instance create");
28 }
29
~NapiAudioRendererDeviceChangeCallback()30 NapiAudioRendererDeviceChangeCallback::~NapiAudioRendererDeviceChangeCallback()
31 {
32 if (regArDevInfoTsfn_) {
33 napi_release_threadsafe_function(arDevInfoTsfn_, napi_tsfn_abort);
34 }
35 AUDIO_INFO_LOG("instance destroy");
36 }
37
SaveCallbackReference(const std::string & callbackName,napi_value args)38 void NapiAudioRendererDeviceChangeCallback::SaveCallbackReference(const std::string &callbackName, napi_value args)
39 {
40 std::lock_guard<std::mutex> lock(mutex_);
41 bool isEquals = false;
42 napi_value copyValue = nullptr;
43
44 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
45 napi_get_reference_value(env_, (*autoRef)->cb_, ©Value);
46 CHECK_AND_RETURN_LOG(napi_strict_equals(env_, copyValue, args, &isEquals) == napi_ok,
47 "get napi_strict_equals failed");
48 CHECK_AND_RETURN_LOG(!isEquals, "js callback already exits");
49 }
50 // create function that will operate while save callback reference success.
51 std::function<void(std::shared_ptr<AutoRef> generatedCallback)> successed =
52 [this](std::shared_ptr<AutoRef> generatedCallback) {
53 callbacks_.push_back(generatedCallback);
54 };
55 NapiAudioRendererCallbackInner::SaveCallbackReferenceInner(callbackName, args, successed);
56 AUDIO_DEBUG_LOG("SaveAudioRendererDeviceChangeCallback sucessful");
57 }
58
CreateRendererDeviceChangeTsfn(napi_env env)59 void NapiAudioRendererDeviceChangeCallback::CreateRendererDeviceChangeTsfn(napi_env env)
60 {
61 regArDevInfoTsfn_ = true;
62 napi_value cbName;
63 std::string callbackName = "AudioRendererDeviceChange";
64 napi_create_string_utf8(env_, callbackName.c_str(), callbackName.length(), &cbName);
65 napi_create_threadsafe_function(env, nullptr, nullptr, cbName, 0, 1, nullptr, RendererDeviceInfoTsfnFinalize,
66 nullptr, SafeJsCallbackRendererDeviceInfoWork, &arDevInfoTsfn_);
67 }
68
GetRendererDeviceChangeTsfnFlag()69 bool NapiAudioRendererDeviceChangeCallback::GetRendererDeviceChangeTsfnFlag()
70 {
71 return regArDevInfoTsfn_;
72 }
73
RemoveCallbackReference(const std::string & callbackName,napi_env env,napi_value callback,napi_value args)74 void NapiAudioRendererDeviceChangeCallback::RemoveCallbackReference(const std::string &callbackName,
75 napi_env env, napi_value callback, napi_value args)
76 {
77 std::lock_guard<std::mutex> lock(mutex_);
78 bool isEquals = false;
79 napi_value copyValue = nullptr;
80
81 if (args == nullptr) {
82 callbacks_.clear();
83 AUDIO_INFO_LOG("Remove all JS Callback");
84 return;
85 }
86
87 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
88 napi_get_reference_value(env, (*autoRef)->cb_, ©Value);
89 CHECK_AND_RETURN_LOG(copyValue != nullptr, "copyValue is nullptr");
90 CHECK_AND_RETURN_LOG(napi_strict_equals(env, args, copyValue, &isEquals) == napi_ok,
91 "get napi_strict_equals failed");
92
93 if (isEquals == true) {
94 AUDIO_INFO_LOG("found JS Callback, delete it!");
95 callbacks_.remove(*autoRef);
96 return;
97 }
98 }
99 AUDIO_INFO_LOG("RemoveCallbackReference success");
100 }
101
GetEnv()102 napi_env &NapiAudioRendererDeviceChangeCallback::GetEnv()
103 {
104 return env_;
105 }
106
GetCallback(const std::string & callbackName)107 std::shared_ptr<AutoRef> NapiAudioRendererDeviceChangeCallback::GetCallback(const std::string &callbackName)
108 {
109 std::shared_ptr<AutoRef> callbackCur = std::make_shared<AutoRef>(env_, nullptr);
110 return callbackCur;
111 }
112
CheckIfTargetCallbackName(const std::string & callbackName)113 bool NapiAudioRendererDeviceChangeCallback::CheckIfTargetCallbackName(const std::string &callbackName)
114 {
115 if (callbackName == DEVICECHANGE_CALLBACK_NAME) {
116 return true;
117 }
118 return false;
119 }
120
RemoveAllCallbacks()121 void NapiAudioRendererDeviceChangeCallback::RemoveAllCallbacks()
122 {
123 std::lock_guard<std::mutex> lock(mutex_);
124 callbacks_.clear();
125 AUDIO_INFO_LOG("RemoveAllCallbacks successful");
126 }
127
GetCallbackListSize() const128 int32_t NapiAudioRendererDeviceChangeCallback::GetCallbackListSize() const
129 {
130 return callbacks_.size();
131 }
132
OnOutputDeviceChange(const AudioDeviceDescriptor & deviceInfo,const AudioStreamDeviceChangeReason reason)133 void NapiAudioRendererDeviceChangeCallback::OnOutputDeviceChange(const AudioDeviceDescriptor &deviceInfo,
134 const AudioStreamDeviceChangeReason reason)
135 {
136 std::lock_guard<std::mutex> lock(mutex_);
137 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
138 OnJsCallbackRendererDeviceInfo((*autoRef)->cb_, deviceInfo);
139 }
140 }
141
SafeJsCallbackRendererDeviceInfoWork(napi_env env,napi_value js_cb,void * context,void * data)142 void NapiAudioRendererDeviceChangeCallback::SafeJsCallbackRendererDeviceInfoWork(
143 napi_env env, napi_value js_cb, void *context, void *data)
144 {
145 AudioRendererDeviceChangeJsCallback *event = reinterpret_cast<AudioRendererDeviceChangeJsCallback *>(data);
146 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback_ != nullptr),
147 "SafeJsCallbackRendererDeviceInfoWork: No memory");
148 std::shared_ptr<AudioRendererDeviceChangeJsCallback> safeContext(
149 static_cast<AudioRendererDeviceChangeJsCallback*>(data),
150 [](AudioRendererDeviceChangeJsCallback *ptr) {
151 delete ptr;
152 });
153 napi_ref callback = event->callback_;
154 napi_handle_scope scope = nullptr;
155 napi_open_handle_scope(env, &scope);
156 CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
157 AUDIO_INFO_LOG("SafeJsCallbackRendererDeviceInfoWork: safe js callback working.");
158
159 do {
160 napi_value jsCallback = nullptr;
161 napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
162 CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "callback get reference value fail");
163 // Call back function
164 napi_value args[ARGS_ONE] = { nullptr };
165 nstatus = NapiParamUtils::SetValueDeviceInfo(env, event->deviceInfo_, args[0]);
166 CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
167 " fail to convert to jsobj");
168 const size_t argCount = 1;
169 napi_value result = nullptr;
170 nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
171 CHECK_AND_BREAK_LOG(nstatus == napi_ok, "Fail to call devicechange callback");
172 } while (0);
173 napi_close_handle_scope(env, scope);
174 }
175
RendererDeviceInfoTsfnFinalize(napi_env env,void * data,void * hint)176 void NapiAudioRendererDeviceChangeCallback::RendererDeviceInfoTsfnFinalize(napi_env env, void *data, void *hint)
177 {
178 AUDIO_INFO_LOG("RendererDeviceInfoTsfnFinalize: safe thread resource release.");
179 }
180
OnJsCallbackRendererDeviceInfo(napi_ref method,const AudioDeviceDescriptor & deviceInfo)181 void NapiAudioRendererDeviceChangeCallback::OnJsCallbackRendererDeviceInfo(napi_ref method,
182 const AudioDeviceDescriptor &deviceInfo)
183 {
184 CHECK_AND_RETURN_LOG(method != nullptr, "OnJsCallbackRendererDeviceInfo method is nullptr");
185 AudioRendererDeviceChangeJsCallback *event =
186 new AudioRendererDeviceChangeJsCallback {method, env_, deviceInfo};
187 CHECK_AND_RETURN_LOG(event != nullptr, "event is nullptr.");
188
189 napi_acquire_threadsafe_function(arDevInfoTsfn_);
190 napi_call_threadsafe_function(arDevInfoTsfn_, event, napi_tsfn_blocking);
191 }
192
NapiAudioRendererOutputDeviceChangeWithInfoCallback(napi_env env)193 NapiAudioRendererOutputDeviceChangeWithInfoCallback::NapiAudioRendererOutputDeviceChangeWithInfoCallback(napi_env env)
194 : env_(env)
195 {
196 AUDIO_INFO_LOG("instance create");
197 }
198
~NapiAudioRendererOutputDeviceChangeWithInfoCallback()199 NapiAudioRendererOutputDeviceChangeWithInfoCallback::~NapiAudioRendererOutputDeviceChangeWithInfoCallback()
200 {
201 if (regArOutputDevChg_) {
202 napi_release_threadsafe_function(arOutputDevChgTsfn_, napi_tsfn_abort);
203 }
204 AUDIO_INFO_LOG("instance destroy");
205 }
206
SaveCallbackReference(const std::string & callbackName,napi_value args)207 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::SaveCallbackReference(const std::string &callbackName,
208 napi_value args)
209 {
210 std::lock_guard<std::mutex> lock(mutex_);
211 napi_ref callback = nullptr;
212 const int32_t refCount = 1;
213 bool isEquals = false;
214 napi_value copyValue = nullptr;
215
216 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
217 napi_get_reference_value(env_, (*autoRef)->cb_, ©Value);
218 CHECK_AND_RETURN_LOG(napi_strict_equals(env_, copyValue, args, &isEquals) == napi_ok,
219 "get napi_strict_equals failed");
220 CHECK_AND_RETURN_LOG(!isEquals, "js Callback already exist");
221 }
222
223 napi_status status = napi_create_reference(env_, args, refCount, &callback);
224 CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
225 "creating reference for callback fail");
226
227 std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callback);
228 callbacks_.push_back(cb);
229 AUDIO_INFO_LOG("successful");
230 }
231
CreateOutputDeviceChangeTsfn(napi_env env)232 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::CreateOutputDeviceChangeTsfn(napi_env env)
233 {
234 regArOutputDevChg_ = true;
235 napi_value cbName;
236 std::string callbackName = "AROutputDeviceChangeWithInfo";
237 napi_create_string_utf8(env, callbackName.c_str(), callbackName.length(), &cbName);
238 napi_create_threadsafe_function(env, nullptr, nullptr, cbName, 0, 1, nullptr, OutputDeviceInfoTsfnFinalize,
239 nullptr, SafeJsCallbackOutputDeviceInfoWork, &arOutputDevChgTsfn_);
240 }
241
GetOutputDeviceChangeTsfnFlag()242 bool NapiAudioRendererOutputDeviceChangeWithInfoCallback::GetOutputDeviceChangeTsfnFlag()
243 {
244 return regArOutputDevChg_;
245 }
246
GetEnv()247 napi_env &NapiAudioRendererOutputDeviceChangeWithInfoCallback::GetEnv()
248 {
249 return env_;
250 }
251
GetCallback(const std::string & callbackName)252 std::shared_ptr<AutoRef> NapiAudioRendererOutputDeviceChangeWithInfoCallback::GetCallback(
253 const std::string &callbackName)
254 {
255 std::shared_ptr<AutoRef> callbackCur = std::make_shared<AutoRef>(env_, nullptr);
256 return callbackCur;
257 }
258
CheckIfTargetCallbackName(const std::string & callbackName)259 bool NapiAudioRendererOutputDeviceChangeWithInfoCallback::CheckIfTargetCallbackName(
260 const std::string &callbackName)
261 {
262 if (callbackName == OUTPUT_DEVICECHANGE_WITH_INFO) {
263 return true;
264 }
265 return false;
266 }
267
RemoveCallbackReference(const std::string & callbackName,napi_env env,napi_value callback,napi_value args)268 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::RemoveCallbackReference(const std::string &callbackName,
269 napi_env env, napi_value callback, napi_value args)
270 {
271 std::lock_guard<std::mutex> lock(mutex_);
272 bool isEquals = false;
273 napi_value copyValue = nullptr;
274
275 if (args == nullptr) {
276 callbacks_.clear();
277 AUDIO_INFO_LOG("Remove all JS Callback");
278 return;
279 }
280
281 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
282 napi_get_reference_value(env, (*autoRef)->cb_, ©Value);
283 CHECK_AND_RETURN_LOG(copyValue != nullptr, "copyValue is nullptr");
284 CHECK_AND_RETURN_LOG(napi_strict_equals(env, args, copyValue, &isEquals) == napi_ok,
285 "get napi_strict_equals failed");
286
287 if (isEquals == true) {
288 AUDIO_INFO_LOG("found JS Callback, delete it!");
289 callbacks_.remove(*autoRef);
290 return;
291 }
292 }
293
294 AUDIO_INFO_LOG("RemoveCallbackReference success");
295 }
296
RemoveAllCallbacks()297 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::RemoveAllCallbacks()
298 {
299 std::lock_guard<std::mutex> lock(mutex_);
300 callbacks_.clear();
301 AUDIO_INFO_LOG("RemoveAllCallbacks successful");
302 }
303
GetCallbackListSize() const304 int32_t NapiAudioRendererOutputDeviceChangeWithInfoCallback::GetCallbackListSize() const
305 {
306 return callbacks_.size();
307 }
308
OnOutputDeviceChange(const AudioDeviceDescriptor & deviceInfo,const AudioStreamDeviceChangeReason reason)309 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::OnOutputDeviceChange(const AudioDeviceDescriptor &deviceInfo,
310 const AudioStreamDeviceChangeReason reason)
311 {
312 std::lock_guard<std::mutex> lock(mutex_);
313 for (auto autoRef = callbacks_.begin(); autoRef != callbacks_.end(); ++autoRef) {
314 OnJsCallbackOutputDeviceInfo((*autoRef)->cb_, deviceInfo, reason);
315 }
316 }
317
SafeJsCallbackOutputDeviceInfoWork(napi_env env,napi_value js_cb,void * context,void * data)318 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::SafeJsCallbackOutputDeviceInfoWork(
319 napi_env env, napi_value js_cb, void *context, void *data)
320 {
321 AudioRendererOutputDeviceChangeWithInfoJsCallback *event =
322 reinterpret_cast<AudioRendererOutputDeviceChangeWithInfoJsCallback *>(data);
323 CHECK_AND_RETURN_LOG((event != nullptr) && (event->callback_ != nullptr),
324 "OnJsCallbackOutputDeviceInfo: No memory");
325 std::shared_ptr<AudioRendererOutputDeviceChangeWithInfoJsCallback> safeContext(
326 static_cast<AudioRendererOutputDeviceChangeWithInfoJsCallback*>(data),
327 [](AudioRendererOutputDeviceChangeWithInfoJsCallback *ptr) {
328 delete ptr;
329 });
330 napi_ref callback = event->callback_;
331 napi_handle_scope scope = nullptr;
332 napi_open_handle_scope(env, &scope);
333 CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
334 AUDIO_INFO_LOG("SafeJsCallbackOutputDeviceInfoWork: safe js callback working.");
335
336 do {
337 napi_value jsCallback = nullptr;
338 napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
339 CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "callback get reference value fail");
340 // Call back function
341 constexpr size_t argCount = ARGS_ONE;
342 napi_value args[argCount] = {};
343 napi_create_object(env, &args[PARAM0]);
344 napi_value deviceObj = nullptr;
345 nstatus = NapiParamUtils::SetValueDeviceInfo(env, event->deviceInfo_, deviceObj);
346 CHECK_AND_BREAK_LOG(nstatus == napi_ok && deviceObj != nullptr,
347 " fail to convert to jsobj");
348 napi_set_named_property(env, args[PARAM0], "devices", deviceObj);
349 nstatus = NapiParamUtils::SetValueInt32(env, "changeReason", static_cast<const int32_t> (event->reason_),
350 args[PARAM0]);
351 CHECK_AND_BREAK_LOG(nstatus == napi_ok && deviceObj != nullptr,
352 " fail to convert to jsobj");
353
354 napi_value result = nullptr;
355 nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
356 CHECK_AND_BREAK_LOG(nstatus == napi_ok, "Fail to call devicechange callback");
357 } while (0);
358 napi_close_handle_scope(env, scope);
359 }
360
OutputDeviceInfoTsfnFinalize(napi_env env,void * data,void * hint)361 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::OutputDeviceInfoTsfnFinalize(
362 napi_env env, void *data, void *hint)
363 {
364 AUDIO_INFO_LOG("OutputDeviceInfoTsfnFinalize: safe thread resource release.");
365 }
366
OnJsCallbackOutputDeviceInfo(napi_ref method,const AudioDeviceDescriptor & deviceInfo,AudioStreamDeviceChangeReason reason)367 void NapiAudioRendererOutputDeviceChangeWithInfoCallback::OnJsCallbackOutputDeviceInfo(napi_ref method,
368 const AudioDeviceDescriptor &deviceInfo, AudioStreamDeviceChangeReason reason)
369 {
370 CHECK_AND_RETURN_LOG(method != nullptr, "OnJsCallbackOutputDeviceInfo method is nullptr");
371 AudioRendererOutputDeviceChangeWithInfoJsCallback *event =
372 new AudioRendererOutputDeviceChangeWithInfoJsCallback {method, env_, deviceInfo, reason};
373 CHECK_AND_RETURN_LOG(event != nullptr, "event is nullptr.");
374
375 napi_acquire_threadsafe_function(arOutputDevChgTsfn_);
376 napi_call_threadsafe_function(arOutputDevChgTsfn_, event, napi_tsfn_blocking);
377 }
378 } // namespace AudioStandard
379 } // namespace OHOS