目录

Using NDK APIs to Develop PiP (C/C++)

This topic uses video playback as an example to describe how to develop the PiP feature using NDK APIs.

  • Since API version 20, the NDK APIs can be used to develop the PiP feature.
  • The NDK APIs can be used to develop the PiP feature on phones, PCs/2-in-1 devices, and tablets.

Constraints

How to Develop

[h2]Implementation Example at the Node-API Layer

For details about how to register the Node-API module, see Node-API Development Specifications.

  1. Create a PiP parameter configuration descriptor by calling OH_PictureInPicture_CreatePipConfig, and set the initial configuration information in the PiP parameter configuration descriptor by calling OH_PictureInPicture_SetPipMainWindowId, OH_PictureInPicture_SetPipTemplateType, OH_PictureInPicture_SetPipRect, OH_PictureInPicture_SetPipControlGroup, and OH_PictureInPicture_SetPipNapiEnv APIs.

    napi_value PiPManager::CreatePip(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value argv[1] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        napi_value config = argv[0];
    
        napi_value mainWindowIdValue = nullptr;
        napi_value pipTemplateTypeValue = nullptr;
        napi_value widthValue = nullptr;
        napi_value heightValue = nullptr;
        napi_value controlGroupValue = nullptr;
        napi_value pipControllerIdValue = nullptr;
        
        uint32_t controllerId = -1;
        uint32_t mainWindowId = -1;
        PictureInPicture_PipTemplateType pipTemplateType = PictureInPicture_PipTemplateType::VIDEO_PLAY;
        uint32_t width = -1;
        uint32_t height = -1;
        
        napi_get_named_property(env, config, "mainWindowId", &mainWindowIdValue);
        napi_get_named_property(env, config, "pipTemplateType", &pipTemplateTypeValue);
        napi_get_named_property(env, config, "width", &widthValue);
        napi_get_named_property(env, config, "height", &heightValue);
        napi_get_named_property(env, config, "controlGroup", &controlGroupValue);
        napi_get_named_property(env, config, "pipControllerId", &pipControllerIdValue);
        
        ConvertFromJsValue(env, mainWindowIdValue, mainWindowId);
        ConvertFromJsValue(env, pipTemplateTypeValue, pipTemplateType);
        ConvertFromJsValue(env, widthValue, width);
        ConvertFromJsValue(env, heightValue, height);
        ConvertFromJsValue(env, pipControllerIdValue, controllerId);
        
        uint32_t size = 0;
        napi_get_array_length(env, controlGroupValue, &size);
        PictureInPicture_PipControlGroup controlGroup[size];
    
        PiPManager::getElement(env, size, controlGroupValue, controlGroup);
        
        napi_value result = nullptr;
        PictureInPicture_PipConfig pipConfig;
        OH_PictureInPicture_CreatePipConfig(&pipConfig);
        OH_PictureInPicture_SetPipMainWindowId(pipConfig, mainWindowId);
        OH_PictureInPicture_SetPipTemplateType(pipConfig, pipTemplateType);
        OH_PictureInPicture_SetPipRect(pipConfig, width, height);
        OH_PictureInPicture_SetPipControlGroup(pipConfig, controlGroup, size);
        OH_PictureInPicture_SetPipNapiEnv(pipConfig, env);
        // ...
    }
  2. Creates a PiP controller. You can register callbacks for the lifecycle and control events based on the returned controllerId. Specifically, call OH_PictureInPicture_CreatePip to create a PiP controller instance and cache the corresponding controller ID. You are advised to call OH_PictureInPicture_DestroyPipConfig immediately after the creation to destroy the PiP parameter configuration to prevent memory leaks.

    napi_value PiPManager::CreatePip(napi_env env, napi_callback_info info)
    {
        // ...
        int32_t res = OH_PictureInPicture_CreatePip(pipConfig, &controllerId);
        OH_PictureInPicture_DestroyPipConfig(&pipConfig);
        napi_create_uint32(env, controllerId, &result);
        return result;
    }
  3. Call the OH_PictureInPicture_RegisterStartPipCallback API to register the callback for starting PiP and render the video feed based on the returned surfaceId. You can register other callback functions as required.

    void PipStartPipCallback(uint32_t controllerId, uint8_t requestId, uint64_t surfaceId)
    {
        if (jsCallback) {
            napi_value global = nullptr;
            napi_get_global(env_, &global);
            size_t argc = 1;
            std::string tStr = std::to_string(surfaceId);
            const char* cStr = tStr.c_str();
            size_t length = strlen(cStr);
            napi_value str;
            napi_status status = napi_create_string_utf8(env_, cStr, length, &str);
            napi_value argv[1] = {str};
            napi_value jsCallbackValue;
            
            napi_value result = nullptr;
            if (!jsCallback) {
                LOG("js callback is invalid");
            }
            napi_get_reference_value(env_, jsCallback, &jsCallbackValue);
            napi_call_function(env_, global, jsCallbackValue, argc, argv, &result);
        }
    }
    
    napi_value PiPManager::RegisterStartPip(napi_env env, napi_callback_info info)
    {
        size_t argc = 2;
        napi_value argv[2] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        napi_value controllerIdValue = argv[0];
        uint32_t controlId = -1;
        napi_status status = napi_create_reference(env, argv[1], 1, &jsCallback);
        env_ = env;
        ConvertFromJsValue(env, controllerIdValue, controlId);
        napi_value resultValue = nullptr;
        int32_t result = OH_PictureInPicture_RegisterStartPipCallback(controlId, PipStartPipCallback);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }
  4. Start PiP by calling OH_PictureInPicture_StartPip.

    napi_value PiPManager::StartPip(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value argv[1] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        napi_value controlIdValue = argv[0];
        uint32_t controlId = -1;
        ConvertFromJsValue(env, controlIdValue, controlId);
        napi_value resultValue = nullptr;
        int32_t result = OH_PictureInPicture_StartPip(controlId);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }

    Since API version 26.0.0, you can also use OH_PictureInPicture_SetAutoStartEnabled to set whether to automatically start PiP when the main window of the application transitions to the background.

    napi_value PiPManager::SetAutoStart(napi_env env, napi_callback_info info)
    {
        size_t argc = 2;
        napi_value argv[2] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        uint32_t controllerId = -1;
        ConvertFromJsValue(env, argv[0], controllerId);
        bool enabled;
        ConvertFromJsValue(env, argv[1], enabled);
        napi_value resultValue = nullptr;
        LOG("set auto start enable: %{public}d", enabled);
        int32_t result = OH_PictureInPicture_SetAutoStartEnabled(controllerId, enabled);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }
  5. Update the dimensions of a media source by calling OH_PictureInPicture_UpdatePipContentSize.

    napi_value PiPManager::UpdatePipContentSize(napi_env env, napi_callback_info info)
    {
        size_t argc = 3;
        napi_value argv[3] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        uint32_t controlId = -1;
        uint32_t width = -1;
        uint32_t height = -1;
        uint32_t index = 0;
    
        ConvertFromJsValue(env, argv[index++], controlId);
        ConvertFromJsValue(env, argv[index++], width);
        ConvertFromJsValue(env, argv[index], height);
        
        napi_value resultValue = nullptr;
        int32_t result = OH_PictureInPicture_UpdatePipContentSize(controlId, width, height);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }
  6. Stop PiP by calling OH_PictureInPicture_StopPip.

    napi_value PiPManager::StopPip(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value argv[1] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        uint32_t controlId = -1;
        napi_value resultValue = nullptr;
    
        ConvertFromJsValue(env, argv[0], controlId);
        uint32_t result = OH_PictureInPicture_StopPip(controlId);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }
  7. Unregister the PiP start callback by calling OH_PictureInPicture_UnregisterStartPipCallback to prevent memory leaks. Unregister other event callbacks that have been registered.

    napi_value PiPManager::UnregisterStartPip(napi_env env, napi_callback_info info)
    {
        size_t argc = 2;
        napi_value argv[2] = {nullptr};
        napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
        napi_value controllerIdValue = argv[0];
        uint32_t controlId = -1;
        napi_status status = napi_create_reference(env, argv[1], 1, &jsCallback);
        env_ = env;
        ConvertFromJsValue(env, controllerIdValue, controlId);
        napi_value resultValue = nullptr;
        int32_t result = OH_PictureInPicture_UnregisterStartPipCallback(controlId, PipStartPipCallback);
        napi_create_uint32(env, result, &resultValue);
        return resultValue;
    }
  8. The preceding NDK APIs cannot be used yet. You need to use the napi_module_register function for module loading to expose the methods.

    EXTERN_C_START
    static napi_value Init(napi_env env, napi_value exports) {
        napi_property_descriptor desc[] = {
            {"createPip", nullptr, PiPManager::CreatePip, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"startPip", nullptr, PiPManager::StartPip, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"registerStartPip", nullptr, PiPManager::RegisterStartPip, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"deletePip", nullptr, PiPManager::DeletePip, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"stopPip", nullptr, PiPManager::StopPip, nullptr, nullptr, nullptr, napi_default, nullptr},
            {"updatePipContentSize", nullptr, PiPManager::UpdatePipContentSize, nullptr, nullptr, nullptr,
             napi_default, nullptr},
            {"registerLifecycleListener", nullptr, PiPManager::RegisterLifecycleListener, nullptr, nullptr, nullptr,
             napi_default, nullptr},
            {"unregisterStartPip", nullptr, PiPManager::UnregisterStartPip, nullptr, nullptr, nullptr, napi_default,
             nullptr},
            {"unregisterLifecycleListener", nullptr, PiPManager::UnregisterLifecycleListener, nullptr, nullptr, nullptr,
             napi_default, nullptr },
            {"setAutoStart", nullptr, PiPManager::SetAutoStart, nullptr, nullptr, nullptr, napi_default, nullptr},
        };
        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }
    EXTERN_C_END
    
    static napi_module demoModule = {
        .nm_version = 1,
        .nm_flags = 0,
        .nm_filename = nullptr,
        .nm_register_func = Init,
        .nm_modname = "entry",
        .nm_priv = ((void*)0),
        .reserved = { 0 },
    };
    extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
    {
        napi_module_register(&demoModule);
    }
  9. Expose APIs. The API implementation must be consistent with the function declared in step 8. These APIs will be called later.

    // ...
    export declare const createPip: (config: PiPConfig) => number;
    export declare const startPip: (controllerId: number) => number;
    export declare const registerStartPip: (controllerId: number, jsCallback: Function) => number;
    export declare const unregisterStartPip: (controllerId: number, jsCallback: Function) => number;
    export declare const deletePip: (controllerId: number) => number;
    export declare const stopPip: (controllerId: number) => number;
    export declare const updatePipContentSize: (controllerId: number, width: number, height: number) => number;
    export declare const registerLifecycleListener: (controllerId: number, jsCallback: Function) => number;
    export declare const unregisterLifecycleListener: (controllerId: number, jsCallback: Function) => number;
    export declare const setAutoStart: (controllerId: number, enabled: boolean) => number;
  10. Compile the CMakeLists.txt file to generate the corresponding library file.

    # CMakeLists.txt
    # the minimum version of CMake.
    cmake_minimum_required(VERSION 3.5.0)
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    project(MyApplication)
    set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
    if(DEFINED PACKAGE_FIND_FILE)
        include(${PACKAGE_FIND_FILE})
    endif()
    include_directories(${NATIVERENDER_ROOT_PATH}
                        ${NATIVERENDER_ROOT_PATH}/include)
    add_library(entry SHARED napi_init.cpp)
    target_link_libraries(entry PUBLIC libace_napi.z.so libace_ndk.z.so libnative_window_manager.so libhilog_ndk.z.so)

[h2]Implementation Example at the Application Layer

  1. When the application is started, the window ID needs to be saved during UIAbility creation to bind the main window to PiP.

    export default class EntryAbility extends UIAbility {
    // ...
      onWindowStageCreate(windowStage: window.WindowStage): void {
        // Main window is created, set main page for this ability
        Logger.info('testTag', '%{public}s', 'Ability onWindowStageCreate');
        let windowClass: window.Window | undefined = undefined;
        let windowClassId: number = -1;
    
        windowStage.getMainWindow().then((window) => {
          if (window == null) {
            Logger.error('Failed to obtaining the window. Cause: The data is empty');
            return;
          }
          windowClass = window;
          windowClass.setUIContent('pages/Index');
          windowClassId = windowClass.getWindowProperties().id;
          AppStorage.setOrCreate('windowId', windowClassId);
          // ...
    }
  2. Create a controller using the saved window ID. After the PiP controller is successfully created, you need to register the PiP start callback using the obtained controller ID to switch the surface ID to be rendered. In addition, your application can register the PiP lifecycle callback to meet service requirements.

    import testNapi, {PiPConfig} from 'libentry.so';
    // ...
      changeSurface = (surfaceId: string) => {
        if(this.player) {
          this.player.setSurfaceId(surfaceId);
          return;
        }
        Logger.info(`[${TAG}] change surface failed`);
      }
    
      private onStateChange = (state: PiPWindow.PiPState) => {
        switch(state) {
          case PiPWindow.PiPState.ABOUT_TO_START:
            Logger.info(`[${TAG}] ABOUT_TO_START`);
            break;
          case PiPWindow.PiPState.STARTED:
            Logger.info(`[${TAG}] STARTED`);
            break;
          case PiPWindow.PiPState.ABOUT_TO_STOP:
            Logger.info(`[${TAG}] ABOUT_TO_STOP`);
            break;
          case PiPWindow.PiPState.STOPPED:
            if (this.mXComponentController) {
              this.changeSurface(this.mXComponentController?.getXComponentSurfaceId());
            }
            Logger.info(`[${TAG}] STOPPED`);
            break;
          case PiPWindow.PiPState.ABOUT_TO_RESTORE:
            this.changeSurface(this.surfaceId);
            Logger.info(`[${TAG}] ABOUT_TO_RESTORE`);
            break;
          case PiPWindow.PiPState.ERROR:
            Logger.info(`[${TAG}] ERROR`);
            break;
          default:
            break;
        }
      }
      // ...
                let windowId: number | undefined = AppStorage.get('windowId');
                let config: PiPConfig = {
                  mainWindowId: windowId as number,
                  pipTemplateType: this.pipType,
                  width: this.contentWidth,
                  height: this.contentHeight,
                  controlGroup: this.pipControlGroups
                }
                this.controllerId = testNapi.createPip(config);
                testNapi.registerStartPip(this.controllerId, this.changeSurface);
                testNapi.registerLifecycleListener(this.controllerId, this.onStateChange);
                // ...
  3. Use the obtained controller ID to start PiP.

    testNapi.startPip(this.controllerId);
  4. Use the obtained controller ID to enable PiP auto-start. When the main window that can start PiP goes to the background, PiP is automatically started.

    let ret: number = testNapi.setAutoStart(this.controllerId, true);
    Logger.info(`set auto start enabled result: ${ret}`);
  5. Use the obtained controller ID to adjust the PiP size.

    testNapi.updatePipContentSize(this.controllerId, 900, 1600);
  6. Use the obtained controller ID to stop PiP.

    testNapi.stopPip(this.controllerId);
  7. When the PiP controller is no longer used, you need to use the obtained controller ID to unregister the callback and delete the PiP controller to prevent memory leaks.

    testNapi.unregisterStartPip(this.controllerId, this.changeSurface);
    testNapi.unregisterLifecycleListener(this.controllerId, this.onStateChange);
    testNapi.deletePip(this.controllerId);