• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2025 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 "avsession_errors.h"
17 #include "avsession_trace.h"
18 #include "napi_async_work.h"
19 #include "napi_avsession_manager.h"
20 #include "napi_avcast_picker_helper.h"
21 
22 namespace OHOS::AVSession {
23 
24 static const std::string ABILITY_WANT_PARAMS_UIEXTENSIONTARGETTYPE = "ability.want.params.uiExtensionType";
25 static const std::string ABILITY_WANT_ELEMENT_NAME = "com.ohos.mediacontroller";
26 
27 static __thread napi_ref AVCastPickerHelperConstructorRef = nullptr;
28 std::map<std::string, std::pair<NapiAVCastPickerHelper::OnEventHandlerType,
29     NapiAVCastPickerHelper::OffEventHandlerType>> NapiAVCastPickerHelper::eventHandlers_ = {
30     { "pickerStateChange", { OnPickerStateChange, OffPickerStateChange } },
31 };
32 
NapiAVCastPickerHelper(Ace::UIContent * uiContent)33 NapiAVCastPickerHelper::NapiAVCastPickerHelper(Ace::UIContent* uiContent)
34 {
35     SLOGI("construct");
36     uiContent_ = uiContent;
37     isValid_ = std::make_shared<bool>(true);
38 }
39 
~NapiAVCastPickerHelper()40 NapiAVCastPickerHelper::~NapiAVCastPickerHelper()
41 {
42     SLOGI("destroy.");
43     *isValid_ = false;
44 }
45 
Init(napi_env env,napi_value exports)46 napi_value NapiAVCastPickerHelper::Init(napi_env env, napi_value exports)
47 {
48     napi_property_descriptor descriptors[] = {
49         DECLARE_NAPI_FUNCTION("select", SelectAVPicker),
50         DECLARE_NAPI_FUNCTION("on", OnEvent),
51         DECLARE_NAPI_FUNCTION("off", OffEvent),
52     };
53     auto propertyCount = sizeof(descriptors) / sizeof(napi_property_descriptor);
54     napi_value constructor {};
55     auto status = napi_define_class(env, "AVCastPickerHelper", NAPI_AUTO_LENGTH, ConstructorCallback, nullptr,
56                                     propertyCount, descriptors, &constructor);
57     if (status != napi_ok) {
58         SLOGE("define class failed");
59         return NapiUtils::GetUndefinedValue(env);
60     }
61     napi_create_reference(env, constructor, 1, &AVCastPickerHelperConstructorRef);
62     napi_set_named_property(env, exports, "AVCastPickerHelper", constructor);
63 
64     return exports;
65 }
66 
ConstructorCallback(napi_env env,napi_callback_info info)67 napi_value NapiAVCastPickerHelper::ConstructorCallback(napi_env env, napi_callback_info info)
68 {
69     struct ConcreteContext : public ContextBase { Ace::UIContent* uiContent; };
70     auto context = std::make_shared<ConcreteContext>();
71     if (context == nullptr) {
72         NapiUtils::ThrowError(env, "ConstructorCallback failed : no memory",
73             NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
74         return NapiUtils::GetUndefinedValue(env);
75     }
76     auto inputParser = [env, context](size_t argc, napi_value* argv) {
77         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ONE, "invalid arguments",
78             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
79         auto stageCtx = AbilityRuntime::GetStageModeContext(env, argv[ARGV_FIRST]);
80         if (stageCtx == nullptr) {
81             context->status = napi_generic_failure;
82             NapiUtils::ThrowError(env, "get stageCtx failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
83             return;
84         }
85         auto abilityContext = AbilityRuntime::Context::ConvertTo<AbilityRuntime::AbilityContext>(stageCtx);
86         if (abilityContext != nullptr) {
87             context->uiContent = abilityContext->GetUIContent();
88         } else {
89             auto extensionContext = AbilityRuntime::Context::ConvertTo<AbilityRuntime::UIExtensionContext>(stageCtx);
90             CHECK_RETURN_VOID(extensionContext != nullptr, "convert to AbilityContext and ExtensionContext fail");
91             context->uiContent = extensionContext->GetUIContent();
92         }
93         context->status = napi_ok;
94     };
95     context->GetCbInfo(env, info, inputParser);
96     CHECK_AND_RETURN_RET_LOG(context->status == napi_ok, nullptr, "no need to wrap");
97 
98     auto* napiAVCastPickerHelper = new(std::nothrow) NapiAVCastPickerHelper(context->uiContent);
99     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, nullptr, "no memory");
100     auto finalize = [](napi_env env, void* data, void* hint) {
101         auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(data);
102         CHECK_AND_RETURN_LOG(napiAVCastPickerHelper != nullptr, "napiAVCastPickerHelper is nullptr");
103         if (napiAVCastPickerHelper->uiContent_ != nullptr) {
104             napiAVCastPickerHelper->uiContent_->CloseModalUIExtension(napiAVCastPickerHelper->sessionId_);
105         }
106         napi_delete_reference(env, napiAVCastPickerHelper->wrapperRef_);
107         delete napiAVCastPickerHelper;
108         napiAVCastPickerHelper = nullptr;
109     };
110     if (napi_wrap(env, context->self, static_cast<void*>(napiAVCastPickerHelper),
111         finalize, nullptr, nullptr) != napi_ok) {
112         SLOGE("wrap failed");
113         delete napiAVCastPickerHelper;
114         napiAVCastPickerHelper = nullptr;
115         return nullptr;
116     }
117     return context->self;
118 }
119 
OnEvent(napi_env env,napi_callback_info info)120 napi_value NapiAVCastPickerHelper::OnEvent(napi_env env, napi_callback_info info)
121 {
122     auto context = std::make_shared<ContextBase>();
123     if (context == nullptr) {
124         SLOGE("OnEvent failed : no memory");
125         NapiUtils::ThrowError(env, "OnEvent failed : no memory", NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
126         return NapiUtils::GetUndefinedValue(env);
127     }
128 
129     std::string eventName;
130     napi_value callback {};
131     auto input = [&eventName, &callback, env, &context](size_t argc, napi_value* argv) {
132         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_TWO, "invalid argument number",
133             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
134         context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], eventName);
135         CHECK_STATUS_RETURN_VOID(context, "get event name failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
136         napi_valuetype type = napi_undefined;
137         context->status = napi_typeof(env, argv[ARGV_SECOND], &type);
138         CHECK_ARGS_RETURN_VOID(context, (context->status == napi_ok) && (type == napi_function),
139                                "callback type invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
140         callback = argv[ARGV_SECOND];
141     };
142     context->GetCbInfo(env, info, input, true);
143     if (context->status != napi_ok) {
144         NapiUtils::ThrowError(env, context->errMessage.c_str(), context->errCode);
145         return NapiUtils::GetUndefinedValue(env);
146     }
147     auto it = eventHandlers_.find(eventName);
148     if (it == eventHandlers_.end()) {
149         SLOGE("event name invalid");
150         NapiUtils::ThrowError(env, "event name invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
151         return NapiUtils::GetUndefinedValue(env);
152     }
153     auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
154     if (it->second.first(env, napiAVCastPickerHelper, callback) != napi_ok) {
155         NapiUtils::ThrowError(env, "add event callback failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
156     }
157     return NapiUtils::GetUndefinedValue(env);
158 }
159 
OffEvent(napi_env env,napi_callback_info info)160 napi_value NapiAVCastPickerHelper::OffEvent(napi_env env, napi_callback_info info)
161 {
162     auto context = std::make_shared<ContextBase>();
163     if (context == nullptr) {
164         SLOGE("OffEvent failed : no memory");
165         NapiUtils::ThrowError(env, "OffEvent failed : no memory", NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
166         return NapiUtils::GetUndefinedValue(env);
167     }
168     std::string eventName;
169     napi_value callback = nullptr;
170     auto input = [&eventName, env, &context, &callback](size_t argc, napi_value* argv) {
171         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ONE || argc == ARGC_TWO,
172                                "invalid argument number", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
173         context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], eventName);
174         CHECK_STATUS_RETURN_VOID(context, "get event name failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
175         if (argc == ARGC_TWO) {
176             callback = argv[ARGV_SECOND];
177         }
178     };
179     context->GetCbInfo(env, info, input, true);
180     if (context->status != napi_ok) {
181         NapiUtils::ThrowError(env, context->errMessage.c_str(), context->errCode);
182         return NapiUtils::GetUndefinedValue(env);
183     }
184     auto it = eventHandlers_.find(eventName);
185     if (it == eventHandlers_.end()) {
186         SLOGE("event name invalid");
187         NapiUtils::ThrowError(env, "event name invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
188         return NapiUtils::GetUndefinedValue(env);
189     }
190     auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
191     if (napiAVCastPickerHelper == nullptr) {
192         SLOGE("OffEvent failed : napiAVCastPickerHelper is nullptr");
193         NapiUtils::ThrowError(env, "OffEvent failed : napiAVCastPickerHelper is nullptr",
194             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
195         return NapiUtils::GetUndefinedValue(env);
196     }
197     if (napiAVCastPickerHelper != nullptr && it->second.second(env, napiAVCastPickerHelper, callback) != napi_ok) {
198         NapiUtils::ThrowError(env, "remove event callback failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
199     }
200     return NapiUtils::GetUndefinedValue(env);
201 }
202 
SelectAVPicker(napi_env env,napi_callback_info info)203 napi_value NapiAVCastPickerHelper::SelectAVPicker(napi_env env, napi_callback_info info)
204 {
205     struct ConcreteContext : public ContextBase {
206         NapiAVCastPickerOptions napiAVCastPickerOptions;
207     };
208     auto context = std::make_shared<ConcreteContext>();
209     auto inputParser = [env, context](size_t argc, napi_value* argv) {
210         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ZERO || argc == ARGC_ONE,
211                                "invalid argument number", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
212         if (argc == ARGC_ZERO) {
213             SLOGI("NapiAVCastPickerOptions use default options");
214         } else {
215             context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], context->napiAVCastPickerOptions);
216         }
217         CHECK_STATUS_RETURN_VOID(context, "get object failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
218     };
219     context->GetCbInfo(env, info, inputParser);
220     context->taskId = NAPI_CAST_PICKER_HELPER_TASK_ID;
221 
222     auto executor = [context]() {
223         auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
224         AAFwk::Want request;
225         std::string targetType = "sysPicker/mediaControl";
226         request.SetParam(ABILITY_WANT_PARAMS_UIEXTENSIONTARGETTYPE, targetType);
227         request.SetParam("isAVCastPickerHelper", true);
228         request.SetParam("AVCastPickerOptionsType", context->napiAVCastPickerOptions.sessionType);
229         request.SetElementName(ABILITY_WANT_ELEMENT_NAME, "UIExtAbility");
230 
231         PickerCallBack pickerCallBack;
232         auto callback = std::make_shared<ModalUICallback>(napiAVCastPickerHelper->uiContent_, pickerCallBack);
233         Ace::ModalUIExtensionCallbacks extensionCallback = {
234             .onRelease = ([callback](auto arg) { callback->OnRelease(arg); }),
235             .onResult = ([callback](auto arg1, auto arg2) { callback->OnResult(arg1, arg2); }),
236             .onReceive = ([callback, napiAVCastPickerHelper](auto arg) {
237                 callback->OnReceive(arg);
238                 napiAVCastPickerHelper->HandleEvent(EVENT_PICPKER_STATE_CHANGE, STATE_DISAPPEARING);
239             }),
240             .onError = ([callback](auto arg1, auto arg2, auto arg3) { callback->OnError(arg1, arg2, arg3); }),
241             .onRemoteReady = ([callback, napiAVCastPickerHelper](auto arg) {
242                 callback->OnRemoteReady(arg);
243                 napiAVCastPickerHelper->HandleEvent(EVENT_PICPKER_STATE_CHANGE, STATE_APPEARING);
244             }),
245             .onDestroy = ([callback]() { callback->OnDestroy(); }),
246         };
247         Ace::ModalUIExtensionConfig config;
248         config.isProhibitBack = true;
249         napiAVCastPickerHelper->sessionId_ =
250             napiAVCastPickerHelper->uiContent_->CreateModalUIExtension(request, extensionCallback, config);
251         callback->SetSessionId(napiAVCastPickerHelper->sessionId_);
252     };
253     auto complete = [env](napi_value& output) { output = NapiUtils::GetUndefinedValue(env); };
254     return NapiAsyncWork::Enqueue(env, context, "SelectAVPicker", executor, complete);
255 }
256 
OnPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)257 napi_status NapiAVCastPickerHelper::OnPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
258     napi_value callback)
259 {
260     SLOGI("NapiAVCastPickerHelper OnPickerStateChange");
261     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
262     return napiAVCastPickerHelper->AddCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
263 }
264 
OffPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)265 napi_status NapiAVCastPickerHelper::OffPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
266     napi_value callback)
267 {
268     SLOGI("NapiAVCastPickerHelper OffPickerStateChange");
269     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
270     return napiAVCastPickerHelper->RemoveCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
271 }
272 
273 template<typename T>
HandleEvent(int32_t event,const T & param)274 void NapiAVCastPickerHelper::HandleEvent(int32_t event, const T& param)
275 {
276     std::lock_guard<std::mutex> lockGuard(lock_);
277     if (callbacks_[event].empty()) {
278         SLOGE("not register callback event=%{public}d", event);
279         return;
280     }
281     for (auto ref = callbacks_[event].begin(); ref != callbacks_[event].end(); ++ref) {
282         asyncCallback_->CallWithFunc(*ref, isValid_,
283             [this, ref, event]() {
284                 std::lock_guard<std::mutex> lockGuard(lock_);
285                 if (callbacks_[event].empty()) {
286                     SLOGE("checkCallbackValid with empty list for event=%{public}d", event);
287                     return false;
288                 }
289                 bool hasFunc = false;
290                 for (auto it = callbacks_[event].begin(); it != callbacks_[event].end(); ++it) {
291                     hasFunc = (ref == it ? true : hasFunc);
292                 }
293                 if (!hasFunc) {
294                     SLOGE("checkCallbackValid res false for event=%{public}d", event);
295                 }
296                 return hasFunc;
297             },
298             [param](napi_env env, int& argc, napi_value* argv) {
299                 argc = NapiUtils::ARGC_ONE;
300                 NapiUtils::SetValue(env, param, *argv);
301             });
302     }
303 }
304 
AddCallback(napi_env env,int32_t event,napi_value callback)305 napi_status NapiAVCastPickerHelper::AddCallback(napi_env env, int32_t event, napi_value callback)
306 {
307     SLOGI("Add callback %{public}d", event);
308     std::lock_guard<std::mutex> lockGuard(lock_);
309     CHECK_AND_RETURN_RET_LOG(event == EVENT_PICPKER_STATE_CHANGE, napi_generic_failure, "has no event");
310     napi_ref ref = nullptr;
311     CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
312                              napi_generic_failure, "get callback reference failed");
313     CHECK_AND_RETURN_RET_LOG(ref == nullptr, napi_ok, "callback has been registered");
314     napi_status status = napi_create_reference(env, callback, NapiUtils::ARGC_ONE, &ref);
315     if (status != napi_ok) {
316         SLOGE("napi_create_reference failed");
317         return status;
318     }
319     if (asyncCallback_ == nullptr) {
320         asyncCallback_ = std::make_shared<NapiAsyncCallback>(env);
321         if (asyncCallback_ == nullptr) {
322             SLOGE("no memory");
323             return napi_generic_failure;
324         }
325     }
326     callbacks_[event].push_back(ref);
327     return napi_ok;
328 }
329 
RemoveCallback(napi_env env,int32_t event,napi_value callback)330 napi_status NapiAVCastPickerHelper::RemoveCallback(napi_env env, int32_t event, napi_value callback)
331 {
332     SLOGI("Remove callback %{public}d", event);
333     std::lock_guard<std::mutex> lockGuard(lock_);
334     CHECK_AND_RETURN_RET_LOG(event == EVENT_PICPKER_STATE_CHANGE, napi_generic_failure, "has no event");
335     if (callback == nullptr) {
336         for (auto callbackRef = callbacks_[event].begin(); callbackRef != callbacks_[event].end(); ++callbackRef) {
337             napi_status ret = napi_delete_reference(env, *callbackRef);
338             CHECK_AND_RETURN_RET_LOG(napi_ok == ret, ret, "delete callback reference failed");
339         }
340         callbacks_[event].clear();
341         return napi_ok;
342     }
343     napi_ref ref = nullptr;
344     CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
345                              napi_generic_failure, "get callback reference failed");
346     CHECK_AND_RETURN_RET_LOG(ref != nullptr, napi_ok, "callback has been remove");
347     callbacks_[event].remove(ref);
348     return napi_delete_reference(env, ref);
349 }
350 
IsCallbacksEmpty(int32_t event)351 bool NapiAVCastPickerHelper::IsCallbacksEmpty(int32_t event)
352 {
353     CHECK_AND_RETURN_RET_LOG(event == EVENT_PICPKER_STATE_CHANGE, true, "has no event");
354     return callbacks_[event].empty();
355 }
356 
ModalUICallback(Ace::UIContent * uiContent,PickerCallBack & pickerCallBack)357 ModalUICallback::ModalUICallback(Ace::UIContent* uiContent, PickerCallBack& pickerCallBack)
358 {
359     this->uiContent_ = uiContent;
360     this->pickerCallBack_ = pickerCallBack;
361 }
362 
SetSessionId(int32_t sessionId)363 void ModalUICallback::SetSessionId(int32_t sessionId)
364 {
365     this->sessionId_ = sessionId;
366 }
367 
OnRelease(int32_t releaseCode)368 void ModalUICallback::OnRelease(int32_t releaseCode)
369 {
370     SLOGI("ModalUICallback OnRelease enter. release code is %{public}d", releaseCode);
371     this->uiContent_->CloseModalUIExtension(this->sessionId_);
372     pickerCallBack_.ready = true;
373 }
374 
OnResult(int32_t resultCode,const OHOS::AAFwk::Want & result)375 void ModalUICallback::OnResult(int32_t resultCode, const OHOS::AAFwk::Want& result)
376 {
377     SLOGI("ModalUICallback OnResult enter. resultCode code is %{public}d", resultCode);
378     pickerCallBack_.resultCode = resultCode;
379 }
380 
OnReceive(const OHOS::AAFwk::WantParams & request)381 void ModalUICallback::OnReceive(const OHOS::AAFwk::WantParams& request)
382 {
383     SLOGI("ModalUICallback OnReceive enter.");
384 }
385 
OnError(int32_t code,const std::string & name,const std::string & message)386 void ModalUICallback::OnError(int32_t code, const std::string& name, const std::string& message)
387 {
388     SLOGI("ModalUICallback OnError enter. errorCode=%{public}d, name=%{public}s, message=%{public}s",
389         code, name.c_str(), message.c_str());
390     if (!pickerCallBack_.ready) {
391         this->uiContent_->CloseModalUIExtension(this->sessionId_);
392         pickerCallBack_.ready = true;
393     }
394 }
395 
OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy> & uiProxy)396 void ModalUICallback::OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy>& uiProxy)
397 {
398     SLOGI("ModalUICallback OnRemoteReady enter.");
399 }
400 
OnDestroy()401 void ModalUICallback::OnDestroy()
402 {
403     SLOGI("ModalUICallback OnDestroy enter.");
404 }
405 
406 } // namespace OHOS::AVSession
407