1 /*
2  * Copyright (C) 2021 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 
16 #include "soundpool_callback_napi.h"
17 #include <uv.h>
18 #include "media_errors.h"
19 #include "media_log.h"
20 #include "scope_guard.h"
21 
22 namespace {
23     constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LOG_CORE, LOG_DOMAIN_SOUNDPOOL, "SoundPoolCallBackNapi"};
24 }
25 
26 namespace OHOS {
27 namespace Media {
SoundPoolCallBackNapi(napi_env env)28 SoundPoolCallBackNapi::SoundPoolCallBackNapi(napi_env env)
29 {
30     env_ = env;
31     MEDIA_LOGI("0x%{public}06" PRIXPTR "Instances create", FAKE_POINTER(this));
32 }
33 
OnLoadCompleted(int32_t soundId)34 void SoundPoolCallBackNapi::OnLoadCompleted(int32_t soundId)
35 {
36     MEDIA_LOGI("OnLoadCompleted recived soundId:%{public}d", soundId);
37     SendLoadCompletedCallback(soundId);
38 }
39 
OnPlayFinished()40 void SoundPoolCallBackNapi::OnPlayFinished()
41 {
42     MEDIA_LOGI("OnPlayFinished recived");
43     SendPlayCompletedCallback();
44 }
45 
OnError(int32_t errorCode)46 void SoundPoolCallBackNapi::OnError(int32_t errorCode)
47 {
48     MEDIA_LOGI("OnError recived:error:%{public}d", errorCode);
49     if (errorCode == MSERR_INVALID_OPERATION) {
50         SendErrorCallback(MSERR_EXT_API9_OPERATE_NOT_PERMIT,
51             "The soundpool timed out. Please confirm that the input stream is normal.");
52     } else if (errorCode == MSERR_NO_MEMORY) {
53         SendErrorCallback(MSERR_EXT_API9_NO_MEMORY, "soundpool memery error.");
54     } else if (errorCode == MSERR_SERVICE_DIED) {
55         SendErrorCallback(MSERR_EXT_API9_SERVICE_DIED, "releated server died");
56     } else {
57         SendErrorCallback(MSERR_EXT_API9_IO, "IO error happened.");
58     }
59 }
60 
SaveCallbackReference(const std::string & name,std::weak_ptr<AutoRef> ref)61 void SoundPoolCallBackNapi::SaveCallbackReference(const std::string &name, std::weak_ptr<AutoRef> ref)
62 {
63     std::lock_guard<std::mutex> lock(mutex_);
64     refMap_[name] = ref;
65     MEDIA_LOGI("Set callback type: %{public}s", name.c_str());
66 }
67 
CancelCallbackReference(const std::string & name)68 void SoundPoolCallBackNapi::CancelCallbackReference(const std::string &name)
69 {
70     std::lock_guard<std::mutex> lock(mutex_);
71     auto iter = refMap_.find(name);
72     if (iter != refMap_.end()) {
73         refMap_.erase(iter);
74     }
75     MEDIA_LOGI("Cancel callback type: %{public}s", name.c_str());
76 }
77 
ClearCallbackReference()78 void SoundPoolCallBackNapi::ClearCallbackReference()
79 {
80     std::lock_guard<std::mutex> lock(mutex_);
81     refMap_.clear();
82     MEDIA_LOGI("ClearCallback!");
83 }
84 
SendErrorCallback(int32_t errCode,const std::string & msg)85 void SoundPoolCallBackNapi::SendErrorCallback(int32_t errCode, const std::string &msg)
86 {
87     std::lock_guard<std::mutex> lock(mutex_);
88     if (refMap_.find(SoundPoolEvent::EVENT_ERROR) == refMap_.end()) {
89         MEDIA_LOGW("can not find error callback!");
90         return;
91     }
92 
93     SoundPoolJsCallBack *cb = new(std::nothrow) SoundPoolJsCallBack();
94     CHECK_AND_RETURN_LOG(cb != nullptr, "cb is nullptr");
95     cb->autoRef = refMap_.at(SoundPoolEvent::EVENT_ERROR);
96     cb->callbackName = SoundPoolEvent::EVENT_ERROR;
97     cb->errorCode = errCode;
98     cb->errorMsg = msg;
99     return OnJsErrorCallBack(cb);
100 }
101 
SendLoadCompletedCallback(int32_t soundId)102 void SoundPoolCallBackNapi::SendLoadCompletedCallback(int32_t soundId)
103 {
104     std::lock_guard<std::mutex> lock(mutex_);
105     if (refMap_.find(SoundPoolEvent::EVENT_LOAD_COMPLETED) == refMap_.end()) {
106         MEDIA_LOGW("can not find loadcompleted callback!");
107         return;
108     }
109 
110     SoundPoolJsCallBack *cb = new(std::nothrow) SoundPoolJsCallBack();
111     CHECK_AND_RETURN_LOG(cb != nullptr, "cb is nullptr");
112     cb->autoRef = refMap_.at(SoundPoolEvent::EVENT_LOAD_COMPLETED);
113     cb->callbackName = SoundPoolEvent::EVENT_LOAD_COMPLETED;
114     cb->loadSoundId = soundId;
115     return OnJsloadCompletedCallBack(cb);
116 }
117 
SendPlayCompletedCallback()118 void SoundPoolCallBackNapi::SendPlayCompletedCallback()
119 {
120     std::lock_guard<std::mutex> lock(mutex_);
121     if (refMap_.find(SoundPoolEvent::EVENT_PLAY_FINISHED) == refMap_.end()) {
122         MEDIA_LOGW("can not find playfinished callback!");
123         return;
124     }
125 
126     SoundPoolJsCallBack *cb = new(std::nothrow) SoundPoolJsCallBack();
127     CHECK_AND_RETURN_LOG(cb != nullptr, "cb is nullptr");
128     cb->autoRef = refMap_.at(SoundPoolEvent::EVENT_PLAY_FINISHED);
129     cb->callbackName = SoundPoolEvent::EVENT_PLAY_FINISHED;
130     return OnJsplayCompletedCallBack(cb);
131 }
132 
OnJsErrorCallBack(SoundPoolJsCallBack * jsCb) const133 void SoundPoolCallBackNapi::OnJsErrorCallBack(SoundPoolJsCallBack *jsCb) const
134 {
135     ON_SCOPE_EXIT(0) {
136         delete jsCb;
137     };
138     uv_loop_s *loop = nullptr;
139     napi_get_uv_event_loop(env_, &loop);
140     CHECK_AND_RETURN_LOG(loop != nullptr, "Fail to get uv event loop");
141 
142     uv_work_t *work = new(std::nothrow) uv_work_t;
143     CHECK_AND_RETURN_LOG(work != nullptr, "fail to new uv_work_t");
144     ON_SCOPE_EXIT(1) {
145         delete work;
146     };
147     work->data = reinterpret_cast<void *>(jsCb);
148     // async callback, jsWork and jsWork->data should be heap object.
149     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {
150         MEDIA_LOGD("OnJsErrorCallBack uv_queue_work_with_qos");
151     }, [] (uv_work_t *work, int status) {
152         // Js Thread
153         CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
154         SoundPoolJsCallBack *event = reinterpret_cast<SoundPoolJsCallBack *>(work->data);
155         event->RunJsErrorCallBackTask(status, event);
156         delete event;
157         delete work;
158     }, uv_qos_user_initiated);
159     if (ret != 0) {
160         MEDIA_LOGI("fail to uv_queue_work_with_qos task");
161     }
162     CANCEL_SCOPE_EXIT_GUARD(0);
163     CANCEL_SCOPE_EXIT_GUARD(1);
164 }
165 
RunJsErrorCallBackTask(int status,SoundPoolJsCallBack * event)166 void SoundPoolCallBackNapi::SoundPoolJsCallBack::RunJsErrorCallBackTask(int status, SoundPoolJsCallBack *event)
167 {
168     std::string request = event->callbackName;
169     do {
170         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
171         std::shared_ptr<AutoRef> ref = event->autoRef.lock();
172         CHECK_AND_BREAK_LOG(ref != nullptr, "%{public}s AutoRef is nullptr", request.c_str());
173 
174         napi_handle_scope scope = nullptr;
175         napi_open_handle_scope(ref->env_, &scope);
176         CHECK_AND_BREAK_LOG(scope != nullptr, "%{public}s scope is nullptr", request.c_str());
177         ON_SCOPE_EXIT(0) {
178             napi_close_handle_scope(ref->env_, scope);
179         };
180 
181         napi_value jsCallback = nullptr;
182         napi_status nstatus = napi_get_reference_value(ref->env_, ref->cb_, &jsCallback);
183         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
184             request.c_str());
185 
186         napi_value msgValStr = nullptr;
187         nstatus = napi_create_string_utf8(ref->env_, event->errorMsg.c_str(), NAPI_AUTO_LENGTH, &msgValStr);
188         CHECK_AND_BREAK_LOG(nstatus == napi_ok && msgValStr != nullptr, "create error message str fail");
189 
190         napi_value args[1] = { nullptr };
191         nstatus = napi_create_error(ref->env_, nullptr, msgValStr, &args[0]);
192         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[0] != nullptr, "create error callback fail");
193 
194         nstatus = CommonNapi::FillErrorArgs(ref->env_, event->errorCode, args[0]);
195         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "create error callback fail");
196         // Call back function
197         napi_value result = nullptr;
198         nstatus = napi_call_function(ref->env_, nullptr, jsCallback, 1, args, &result);
199         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to napi call function", request.c_str());
200     } while (0);
201 }
202 
OnJsloadCompletedCallBack(SoundPoolJsCallBack * jsCb) const203 void SoundPoolCallBackNapi::OnJsloadCompletedCallBack(SoundPoolJsCallBack *jsCb) const
204 {
205     ON_SCOPE_EXIT(0) {
206         delete jsCb;
207     };
208     uv_loop_s *loop = nullptr;
209     napi_get_uv_event_loop(env_, &loop);
210     CHECK_AND_RETURN_LOG(loop != nullptr, "Fail to get uv event loop");
211 
212     uv_work_t *work = new(std::nothrow) uv_work_t;
213     CHECK_AND_RETURN_LOG(work != nullptr, "fail to new uv_work_t");
214     ON_SCOPE_EXIT(1) {
215         delete work;
216     };
217     work->data = reinterpret_cast<void *>(jsCb);
218     // async callback, jsWork and jsWork->data should be heap object.
219     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {
220         MEDIA_LOGD("OnJsloadCompletedCallBack uv_queue_work_with_qos");
221     }, [] (uv_work_t *work, int status) {
222         CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
223         if (work->data != nullptr) {
224             MEDIA_LOGD("work data not nullptr");
225             SoundPoolJsCallBack *event = reinterpret_cast<SoundPoolJsCallBack *>(work->data);
226             event->RunJsloadCompletedCallBackTask(status, event);
227             delete event;
228         }
229         delete work;
230     }, uv_qos_user_initiated);
231     if (ret != 0) {
232         MEDIA_LOGI("fail to uv_queue_work_with_qos task");
233     }
234     CANCEL_SCOPE_EXIT_GUARD(0);
235     CANCEL_SCOPE_EXIT_GUARD(1);
236 }
237 
RunJsloadCompletedCallBackTask(int status,SoundPoolJsCallBack * event)238 void SoundPoolCallBackNapi::SoundPoolJsCallBack::RunJsloadCompletedCallBackTask(int status,
239     SoundPoolJsCallBack *event)
240 {
241     std::string request = event->callbackName;
242     do {
243         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
244         std::shared_ptr<AutoRef> ref = event->autoRef.lock();
245         CHECK_AND_BREAK_LOG(ref != nullptr, "%{public}s AutoRef is nullptr", request.c_str());
246         napi_handle_scope scope = nullptr;
247         napi_open_handle_scope(ref->env_, &scope);
248         CHECK_AND_BREAK_LOG(scope != nullptr, "%{public}s scope is nullptr", request.c_str());
249         ON_SCOPE_EXIT(0) {
250             napi_close_handle_scope(ref->env_, scope);
251         };
252         napi_value jsCallback = nullptr;
253         napi_status nstatus = napi_get_reference_value(ref->env_, ref->cb_, &jsCallback);
254         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
255             request.c_str());
256         napi_value args[1] = { nullptr };
257         nstatus = napi_create_int32(ref->env_, event->loadSoundId, &args[0]);
258         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[0] != nullptr,
259             "%{public}s fail to create callback", request.c_str());
260         const size_t argCount = 1;
261         napi_value result = nullptr;
262         nstatus = napi_call_function(ref->env_, nullptr, jsCallback, argCount, args, &result);
263         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to napi call function", request.c_str());
264     } while (0);
265 }
266 
OnJsplayCompletedCallBack(SoundPoolJsCallBack * jsCb) const267 void SoundPoolCallBackNapi::OnJsplayCompletedCallBack(SoundPoolJsCallBack *jsCb) const
268 {
269     ON_SCOPE_EXIT(0) {
270         delete jsCb;
271     };
272     uv_loop_s *loop = nullptr;
273     napi_get_uv_event_loop(env_, &loop);
274     CHECK_AND_RETURN_LOG(loop != nullptr, "Fail to get uv event loop");
275 
276     uv_work_t *work = new(std::nothrow) uv_work_t;
277     CHECK_AND_RETURN_LOG(work != nullptr, "fail to new uv_work_t");
278     ON_SCOPE_EXIT(1) {
279         delete work;
280     };
281     work->data = reinterpret_cast<void *>(jsCb);
282     // async callback, jsWork and jsWork->data should be heap object.
283     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {
284         MEDIA_LOGD("OnJsplayCompletedCallBack uv_queue_work_with_qos");
285     }, [] (uv_work_t *work, int status) {
286         // Js Thread
287         CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
288         if (work->data != nullptr) {
289             MEDIA_LOGI("work data not nullptr");
290             SoundPoolJsCallBack *event = reinterpret_cast<SoundPoolJsCallBack *>(work->data);
291             event->RunJsplayCompletedCallBackTask(status, event);
292             delete event;
293         }
294         delete work;
295     }, uv_qos_user_initiated);
296     if (ret != 0) {
297         MEDIA_LOGI("fail to uv_queue_work_with_qos task");
298     }
299 
300     CANCEL_SCOPE_EXIT_GUARD(0);
301     CANCEL_SCOPE_EXIT_GUARD(1);
302 }
303 
RunJsplayCompletedCallBackTask(int status,SoundPoolJsCallBack * event)304 void SoundPoolCallBackNapi::SoundPoolJsCallBack::RunJsplayCompletedCallBackTask(int status,
305     SoundPoolJsCallBack *event)
306 {
307     std::string request = event->callbackName;
308     do {
309         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
310         std::shared_ptr<AutoRef> ref = event->autoRef.lock();
311         CHECK_AND_BREAK_LOG(ref != nullptr, "%{public}s AutoRef is nullptr", request.c_str());
312 
313         napi_handle_scope scope = nullptr;
314         napi_open_handle_scope(ref->env_, &scope);
315         CHECK_AND_BREAK_LOG(scope != nullptr, "%{public}s scope is nullptr", request.c_str());
316         ON_SCOPE_EXIT(0) {
317             napi_close_handle_scope(ref->env_, scope);
318         };
319         napi_value jsCallback = nullptr;
320         napi_status nstatus = napi_get_reference_value(ref->env_, ref->cb_, &jsCallback);
321         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
322             request.c_str());
323 
324         napi_value result = nullptr;
325         nstatus = napi_call_function(ref->env_, nullptr, jsCallback, 0, nullptr, &result);
326         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to napi call function", request.c_str());
327     } while (0);
328 }
329 
330 } // namespace Media
331 } // namespace OHOS