diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 4f5ceb5ed3d940f1fb7ac8021bdacc9c28da6a7a..0292e8557342fb7d9cb8ac8d716295125108cc9f 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -25,6 +25,9 @@ group("sdk") { if (is_ios) { public_deps += [ ":framework_objc" ] } + if (is_ohos) { + public_deps += [ "ohos" ] + } } } diff --git a/sdk/ohos/BUILD.gn b/sdk/ohos/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..e94ad247f1bed3e960af4f4ffa49f4995417cc42 --- /dev/null +++ b/sdk/ohos/BUILD.gn @@ -0,0 +1,150 @@ +# Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if (is_ohos) { + import("../../webrtc.gni") + + group("ohos") { + if (!build_with_chromium && is_ohos) { + public_deps = [ + ":libohos_webrtc", + ] + } + } + + ohos_webrtc_src_path = "src/ohos_webrtc" + + rtc_shared_library("libohos_webrtc") { + sources = [ + "$ohos_webrtc_src_path/audio_processing_factory.cpp", + "$ohos_webrtc_src_path/certificate.cpp", + "$ohos_webrtc_src_path/configuration.cpp", + "$ohos_webrtc_src_path/data_channel.cpp", + "$ohos_webrtc_src_path/dtls_transport.cpp", + "$ohos_webrtc_src_path/dtmf_sender.cpp", + "$ohos_webrtc_src_path/ice_candidate.cpp", + "$ohos_webrtc_src_path/ice_transport.cpp", + "$ohos_webrtc_src_path/media_devices.cpp", + "$ohos_webrtc_src_path/media_source.cpp", + "$ohos_webrtc_src_path/media_stream.cpp", + "$ohos_webrtc_src_path/media_stream_track.cpp", + "$ohos_webrtc_src_path/media_track_constraints.cpp", + "$ohos_webrtc_src_path/napi_module.cpp", + "$ohos_webrtc_src_path/peer_connection.cpp", + "$ohos_webrtc_src_path/peer_connection_factory.cpp", + "$ohos_webrtc_src_path/rtp_parameters.cpp", + "$ohos_webrtc_src_path/rtp_receiver.cpp", + "$ohos_webrtc_src_path/rtp_sender.cpp", + "$ohos_webrtc_src_path/rtp_transceiver.cpp", + "$ohos_webrtc_src_path/sctp_transport.cpp", + "$ohos_webrtc_src_path/session_description.cpp", + "$ohos_webrtc_src_path/video_decoder_factory.cpp", + "$ohos_webrtc_src_path/video_encoder_factory.cpp", + "$ohos_webrtc_src_path/async_work/async_worker_enumerate_devices.cpp", + "$ohos_webrtc_src_path/async_work/async_worker_get_display_media.cpp", + "$ohos_webrtc_src_path/async_work/async_worker_get_stats.cpp", + "$ohos_webrtc_src_path/async_work/async_worker_get_user_media.cpp", + "$ohos_webrtc_src_path/audio_device/audio_capturer.cpp", + "$ohos_webrtc_src_path/audio_device/audio_device_enumerator.cpp", + "$ohos_webrtc_src_path/audio_device/audio_device_module.cpp", + "$ohos_webrtc_src_path/audio_device/audio_renderer.cpp", + "$ohos_webrtc_src_path/camera/camera_capturer.cpp", + "$ohos_webrtc_src_path/camera/camera_device_info.cpp", + "$ohos_webrtc_src_path/camera/camera_enumerator.cpp", + "$ohos_webrtc_src_path/desktop_capture/desktop_capturer.cpp", + "$ohos_webrtc_src_path/helper/camera.cpp", + "$ohos_webrtc_src_path/logging/hilog_sink.cpp", + "$ohos_webrtc_src_path/logging/log_sink.cpp", + "$ohos_webrtc_src_path/logging/native_logging.cpp", + "$ohos_webrtc_src_path/render/egl_context.cpp", + "$ohos_webrtc_src_path/render/egl_env.cpp", + "$ohos_webrtc_src_path/render/gl_drawer.cpp", + "$ohos_webrtc_src_path/render/gl_shader.cpp", + "$ohos_webrtc_src_path/render/native_video_renderer.cpp", + "$ohos_webrtc_src_path/render/native_window_renderer.cpp", + "$ohos_webrtc_src_path/render/native_window_renderer_gl.cpp", + "$ohos_webrtc_src_path/render/native_window_renderer_raster.cpp", + "$ohos_webrtc_src_path/render/video_frame_drawer.cpp", + "$ohos_webrtc_src_path/render/yuv_converter.cpp", + "$ohos_webrtc_src_path/user_media/media_constraints.cpp", + "$ohos_webrtc_src_path/user_media/media_constraints_util.cpp", + "$ohos_webrtc_src_path/video/texture_buffer.cpp", + "$ohos_webrtc_src_path/video/video_frame_receiver_gl.cpp", + "$ohos_webrtc_src_path/video/video_frame_receiver_native.cpp", + "$ohos_webrtc_src_path/video/video_track_source.cpp", + "$ohos_webrtc_src_path/video_codec/default_video_decoder_factory.cpp", + "$ohos_webrtc_src_path/video_codec/default_video_encoder_factory.cpp", + "$ohos_webrtc_src_path/video_codec/hardware_video_decoder.cpp", + "$ohos_webrtc_src_path/video_codec/hardware_video_decoder_factory.cpp", + "$ohos_webrtc_src_path/video_codec/hardware_video_encoder.cpp", + "$ohos_webrtc_src_path/video_codec/hardware_video_encoder_factory.cpp", + "$ohos_webrtc_src_path/video_codec/media_codec_utils.cpp", + "$ohos_webrtc_src_path/video_codec/software_video_decoder_factory.cpp", + "$ohos_webrtc_src_path/video_codec/software_video_encoder_factory.cpp", + ] + + include_dirs = [ + "$ohos_webrtc_src_path", + "src/node-addon-api", + ] + + defines = [ + "NODE_ADDON_API_DISABLE_DEPRECATED", + "NAPI_DISABLE_CPP_EXCEPTIONS", + "NDK_HELPER_DISABLE_CPP_EXCEPTIONS", + "NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS", + ] + + cflags = [ + "-g", + "-O0", + ] + + ldflags = [ + "-lace_napi.z", + "-lhilog_ndk.z", + "-lohaudio", + "-lohcamera", + "-lohimage", + "-limage_receiver", + "-lnative_buffer", + "-lnative_window", + "-lnative_image", + "-lEGL", + "-lGLESv3", + "-lnative_media_codecbase", + "-lnative_media_core", + "-lnative_media_venc", + "-lnative_media_vdec", + "-lnative_avscreen_capture", + # "-static-libstdc++", + ] + + deps = [ + "../../pc:libjingle_peerconnection", + "../../api:create_peerconnection_factory", + "../../api/audio_codecs:builtin_audio_decoder_factory", + "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../modules/video_coding:webrtc_h264", + "../../modules/video_coding:webrtc_vp8", + "../../modules/video_coding:webrtc_vp9", + "../../modules/video_coding/codecs/av1:libaom_av1_encoder", + "../../modules/video_coding/codecs/av1:dav1d_decoder", + "../../rtc_base:checks", + "//third_party/libyuv", + ] + + output_extension = "so" + } + +} diff --git a/sdk/ohos/README.md b/sdk/ohos/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4149fa1fa8b8809987e8d8623289593e71babcb7 --- /dev/null +++ b/sdk/ohos/README.md @@ -0,0 +1,54 @@ +# WebRTC SDK for OHOS + +## 简介 + +本目录下提供WebRTC的ArkTS接口封装,并在C++层适配了视频的采集、渲染及编解码等模块。其中大部分接口的设计遵循[WebRTC规范](https://www.w3.org/TR/webrtc/),另外也根据实际需要,并参考Android平台,额外提供PeerConnectionFactory和AudioDeviceModule等接口。 + +**图1** SDK架构图 + +![architecture](./img/webrtc-arch.png) + +## 目录 + +SDK目录结构及说明如下: + +```shell +sdk/ohos/ +├── BUILD.gn # src 编译脚本 +├── api # 存放接口文件 +│ ├── ets +│ │ ├── log +│ │ │ └── Logging.ets # 日志 +│ │ └── xcomponent +│ │ └── VideoRenderController.ets # 视频渲染 +│ ├── libohos_webrtc +│ │ ├── index.d.ts +│ │ └── webrtc.d.ts # 接口定义 +│ └── libwebrtc.md # 接口文档 +├── har_hap # Deveco Studio 工程目录,包含一个 HAR 模块,和一个示例 HAP 模块 +└── src + ├── node-addon-api # 三方库,辅助 NAPI 开发 + └── ohos_webrtc # 实现源码 + ├── async_work # 一些用于处理异步任务的辅助类 + ├── audio_device # 音频相关模块 + ├── camera # 相机相关模块 + ├── desktop_capture # 桌面采集 + ├── helper # 一些 NDK 接口辅助类 + ├── logging # 日志接口适配 + ├── render # 视频渲染相关模块 + ├── user_media # 媒体约束相关的辅助类,如根据指定的分辨率筛选合适的相机配置参数 + ├── utils # 工具 + ├── video # 视频相关模块 + ├── video_codec # 视频编解码相关模块 + ├── peer_connection.cpp # RTPPeerConnection 实现 + ├── peer_connection.h # RTPPeerConnection 实现 + |—— ... # 其它接口实现 +``` + +## 其它 + +接口说明及使用方法请参阅[文档](./api/libwebrtc.md),实现代码在src/ohos_webrtc目录下,其中NAPI接口的使用借助了开源项目[node-addon-api](https://github.com/nodejs/node-addon-api)。 + +通过[BUILD.gn](./BUILD.gn)可编译得到动态库`libohos_webrtc.so`,之后可用于构建HAR包以供其它应用模块使用,完整的HAR及示例工程在`har_hap`目录下。动态库的编译方法请参阅[文档](/docs/ohos/webrtc_build.md)。 + +**OHOS SDK要求12及以上**。 diff --git a/sdk/ohos/api/ets/log/Logging.ets b/sdk/ohos/api/ets/log/Logging.ets new file mode 100644 index 0000000000000000000000000000000000000000..567234a2b51d502dfaf2c19cb7b8d8a0629ef9d7 --- /dev/null +++ b/sdk/ohos/api/ets/log/Logging.ets @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Loggable, NativeLogging} from 'libohos_webrtc.so' + +export enum LoggingSeverity { VERBOSE, INFO, WARNING, ERROR, NONE } + +export class Logging { + private static loggingEnabled: boolean = false; + private static loggable: Loggable | null; + private static loggableSeverity: LoggingSeverity = LoggingSeverity.ERROR; + + static injectLoggable(injectedLoggable: Loggable, severity: LoggingSeverity) { + if (injectedLoggable != null) { + Logging.loggable = injectedLoggable; + Logging.loggableSeverity = severity; + NativeLogging.injectLoggable(injectedLoggable, severity); + } + } + + static deleteInjectedLoggable() { + Logging.loggable = null; + NativeLogging.deleteLoggable(); + } + + static enableLogThreads() { + NativeLogging.enableLogThreads(); + } + + static enableLogTimeStamps() { + NativeLogging.enableLogTimeStamps(); + } + + static enableLogToDebugOutput(severity: LoggingSeverity) { + if (Logging.loggable != null) { + throw new Error("Logging to native debug output not supported while Loggable is injected." + + "Delete the Loggable before calling this method."); + } + NativeLogging.enableLogToDebugOutput(severity); + Logging.loggingEnabled = true; + } + + static log(severity: LoggingSeverity, tag: string, message: string) { + if (Logging.loggable) { + // Filter log messages below loggableSeverity. + if (severity < Logging.loggableSeverity) { + return; + } + + Logging.loggable.logMessage(message, severity, tag); + return; + } + + // Try native logging if no loggable is injected. + if (Logging.loggingEnabled) { + NativeLogging.log(message, severity, tag); + return; + } + + // Fallback to system + switch (severity) { + case LoggingSeverity.ERROR: + console.error(tag, message); + break; + case LoggingSeverity.WARNING: + console.warn(tag, message); + break; + case LoggingSeverity.INFO: + console.info(tag, message); + break; + default: + console.debug(tag, message); + break; + } + } + + static d(tag: string, message: string) { + Logging.log(LoggingSeverity.INFO, tag, message); + } + + static e(tag: string, message: string, e?: Error) { + Logging.log(LoggingSeverity.ERROR, tag, message); + if (e != undefined) { + Logging.log(LoggingSeverity.ERROR, tag, e.message); + // Logging.log(LoggingSeverity.ERROR, tag, e.stack); + } + } + + static w(tag: string, message: string, e: Error) { + Logging.log(LoggingSeverity.WARNING, tag, message); + if (e != undefined) { + Logging.log(LoggingSeverity.WARNING, tag, e.message); + // Logging.log(LoggingSeverity.WARNING, tag, e.stack); + } + } + + static v(tag: string, message: string) { + Logging.log(LoggingSeverity.VERBOSE, tag, message); + } + +} diff --git a/sdk/ohos/api/ets/xcomponent/VideoRenderController.ets b/sdk/ohos/api/ets/xcomponent/VideoRenderController.ets new file mode 100644 index 0000000000000000000000000000000000000000..03bdd02a7b75a65094998dcd9c395fb768d3872b --- /dev/null +++ b/sdk/ohos/api/ets/xcomponent/VideoRenderController.ets @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NativeVideoRenderer, MediaStreamTrack } from 'libohos_webrtc.so'; +import { Logging } from '../log/Logging' + +const TAG: string = '[XComponentController1]'; + +export class VideoRenderController extends XComponentController { + private renderer: NativeVideoRenderer = new NativeVideoRenderer(); + + setVideoTrack(track: MediaStreamTrack | null): void { + this.renderer.setVideoTrack(track); + } + + onSurfaceCreated(surfaceId: string): void { + Logging.d(TAG, 'onSurfaceCreated surfaceId: ' + surfaceId); + this.renderer.init(surfaceId); + } + + onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void + { + Logging.d(TAG, 'onSurfaceChanged surfaceId: ' + surfaceId); + Logging.d(TAG, 'onSurfaceChanged rect: ' + rect); + } + + onSurfaceDestroyed(surfaceId: string): void + { + Logging.d(TAG, 'onSurfaceDestroyed surfaceId: ' + surfaceId); + this.renderer.release(); + } +} diff --git a/sdk/ohos/api/libohos_webrtc/index.d.ts b/sdk/ohos/api/libohos_webrtc/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9164e828c019ed25c6325b8554e9e130e5e330b3 --- /dev/null +++ b/sdk/ohos/api/libohos_webrtc/index.d.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './webrtc' diff --git a/sdk/ohos/api/libohos_webrtc/webrtc.d.ts b/sdk/ohos/api/libohos_webrtc/webrtc.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..faf6d559ac920580b3f3733c77a8340314c46e8f --- /dev/null +++ b/sdk/ohos/api/libohos_webrtc/webrtc.d.ts @@ -0,0 +1,1020 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type RTCErrorDetailType = "data-channel-failure" | "dtls-failure" | "fingerprint-failure" | "hardware-encoder-error" | "hardware-encoder-not-available" | "sctp-failure" | "sdp-syntax-error"; +export type RTCIceProtocol = "tcp" | "udp"; +export type RTCIceCandidateType = "host" | "prflx" | "relay" | "srflx"; +export type RTCIceTcpCandidateType = "active" | "passive" | "so"; +export type RTCIceComponent = "rtp" | "rtcp"; +export type RTCIceGathererState = "complete" | "gathering" | "new"; +export type RTCIceTransportState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new"; +export type RTCIceRole = "unknown" | "controlling" | "controlled"; +export type RTCSdpType = 'offer' | 'answer' | "pranswer" | "rollback"; +export type BinaryType = "blob" | "arraybuffer"; +export type DataChannelState = "closed" | "closing" | "connecting" | "open"; +export type RTCDtlsTransportState = "new" | "connecting" | "connected" | "closed" | "failed"; +export type RTCIceGatheringState = "new" | "gathering" | "complete"; +export type RTCIceConnectionState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new"; +export type RTCSignalingState = "closed" | "have-local-offer" | "have-local-pranswer" | "have-remote-offer" | "have-remote-pranswer" | "stable"; +export type RTCPeerConnectionState = "closed" | "connected" | "connecting" | "disconnected" | "failed" | "new"; +export type RTCBundlePolicy = "balanced" | "max-bundle" | "max-compat"; +export type RTCRtcpMuxPolicy = "require"; +export type RTCIceTransportPolicy = "all" | "relay"; +export type DegradationPreference = "balanced" | "maintain-framerate" | "maintain-resolution"; +// export type RTCPriorityType = "high" | "low" | "medium" | "very-low"; +export type RTCRtpTransceiverDirection = "inactive" | "recvonly" | "sendonly" | "sendrecv" | "stopped"; +export type RTCSctpTransportState = "connecting" | "connected" | "closed"; +export type RTCStatsType = "candidate-pair" | "certificate" | "codec" | "data-channel" | "inbound-rtp" | "local-candidate" | "media-playout" | "media-source" | "outbound-rtp" | "peer-connection" | "remote-candidate" | "remote-inbound-rtp" | "remote-outbound-rtp" | "transport"; +export type RTCStatsIceCandidatePairState = "failed" | "frozen" | "in-progress" | "inprogress" | "succeeded" | "waiting"; +export type MediaStreamTrackState = 'live' | 'ended'; +export type MediaDeviceKind = 'audioinput' | 'audiooutput' | 'videoinput'; +export type MediaSourceState = 'initializing' | 'live' | 'ended' | 'muted'; +export type VideoFacingModeEnum = "user" | "environment" | "left" | "right"; +export type VideoResizeModeEnum = "none" | "crop-and-scale"; +export type AudioErrorType = 'init' | 'start-exception' | 'start-state-mismatch' | 'general'; +export type AudioState = 'start' | 'stop'; + +export type AlgorithmIdentifier = Algorithm | string; +export type HighResTimeStamp = number; +export type EpochTimeStamp = number; +export type ConstrainBoolean = boolean | ConstrainBooleanParameters; +export type ConstrainULong = number | ConstrainULongRange; +export type ConstrainDouble = number | ConstrainDoubleRange; +export type ConstrainString = string | string[] | ConstrainStringParameters; + +export interface ULongRange { + max?: number; + min?: number; +} + +export interface DoubleRange { + max?: number; + min?: number; +} + +export interface ConstrainBooleanParameters { + exact?: boolean; + ideal?: boolean; +} + +export interface ConstrainStringParameters { + exact?: string | string[]; + ideal?: string | string[]; +} + +export interface ConstrainDoubleRange extends DoubleRange { + exact?: number; + ideal?: number; +} + +export interface ConstrainULongRange extends ULongRange { + exact?: number; + ideal?: number; +} + +// event +// base class of events +export interface Event { + readonly type: string; +} + +// declare var Event: { +// prototype: Event; +// new(): Event; +// }; + +export interface EventTarget { + // empty for now +} + +// error +// https://www.w3.org/TR/webrtc/#rtcerrorinit-dictionary +export interface RTCErrorInit { + errorDetail: RTCErrorDetailType; + sdpLineNumber?: number; + sctpCauseCode?: number; + receivedAlert?: number; + sentAlert?: number; +} + +// https://www.w3.org/TR/webrtc/#rtcerror-interface +export interface RTCError extends /*DOMException*/ Error { + readonly errorDetail: RTCErrorDetailType; + readonly sdpLineNumber?: number; + readonly sctpCauseCode?: number; + readonly receivedAlert?: number; + readonly sentAlert?: number; +} + +// https://www.w3.org/TR/webrtc/#rtcerrorevent-interface +export interface RTCErrorEvent extends Event { + readonly error: RTCError; +} + +// https://www.w3.org/TR/webrtc/#rtctrackevent +export interface RTCTrackEvent extends Event { + readonly receiver: RTCRtpReceiver; + readonly track: MediaStreamTrack; + readonly streams: ReadonlyArray; + readonly transceiver: RTCRtpTransceiver; +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent +export interface RTCPeerConnectionIceEvent extends Event { + readonly candidate?: RTCIceCandidate; + readonly url?: string | null; +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceerrorevent +export interface RTCPeerConnectionIceErrorEvent extends Event { + readonly address?: string; + readonly port?: number; + readonly url?: string; + readonly errorCode?: number; + readonly errorText?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcdatachannelevent +export interface RTCDataChannelEvent extends Event { + readonly channel: RTCDataChannel; +} + +// https://www.w3.org/TR/webrtc/#rtcdtmftonechangeevent +export interface RTCDTMFToneChangeEvent extends Event { + readonly tone: string; +} + +// https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interface +export interface MessageEvent extends Event { + readonly data: T; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastreamtrackevent +export interface MediaStreamTrackEvent extends Event { + readonly track: MediaStreamTrack; +} + +export interface VideoCapturerStartedEvent extends Event { + readonly success: boolean; +} + +// https://www.w3.org/TR/WebCryptoAPI/#algorithm +export interface Algorithm { + name: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtptransceiverinit +export interface RTCRtpTransceiverInit { + direction?: RTCRtpTransceiverDirection; + streams?: MediaStream[]; + sendEncodings?: RTCRtpEncodingParameters[]; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit +export interface RTCSessionDescriptionInit { + sdp?: string; + type: RTCSdpType; +} + +// https://www.w3.org/TR/webrtc/#rtcsessiondescription-class +export interface RTCSessionDescription { + readonly sdp: string; + readonly type: RTCSdpType; + + toJSON(): RTCSessionDescriptionInit; +} + +declare var RTCSessionDescription: { + prototype: RTCSessionDescription; + new(descriptionInitDict: RTCSessionDescriptionInit): RTCSessionDescription; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcicecandidateinit +export interface RTCIceCandidateInit { + candidate: string; + sdpMLineIndex?: number; + sdpMid?: string; + usernameFragment?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcicecandidate-interface +export interface RTCIceCandidate { + readonly candidate: string; + readonly sdpMid?: string; + readonly sdpMLineIndex?: number; + readonly foundation?: string; + readonly component?: RTCIceComponent; + readonly priority?: number; + readonly address?: string; + readonly protocol?: RTCIceProtocol; + readonly port?: number; + readonly type?: RTCIceCandidateType; + readonly tcpType?: RTCIceTcpCandidateType; + readonly relatedAddress?: string; + readonly relatedPort?: number; + readonly usernameFragment?: string; + + toJSON(): RTCIceCandidateInit; +} + +declare var RTCIceCandidate: { + prototype: RTCIceCandidate; + new(candidateInitDict?: RTCIceCandidateInit): RTCIceCandidate; +}; + +// https://www.w3.org/TR/webrtc/#rtcdatachannel +export interface RTCDataChannel { + readonly label: string; + readonly ordered: boolean; + readonly maxPacketLifeTime?: number; + readonly maxRetransmits?: number; + readonly protocol: string; + readonly negotiated: boolean; + readonly id?: number; + readonly readyState: DataChannelState; + readonly bufferedAmount: number; + bufferedAmountLowThreshold: number; + binaryType: BinaryType; + + onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null; + onclose: ((this: RTCDataChannel, ev: Event) => any) | null; + onclosing: ((this: RTCDataChannel, ev: Event) => any) | null; + onopen: ((this: RTCDataChannel, ev: Event) => any) | null; + onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null; + onerror: ((this: RTCDataChannel, ev: RTCErrorEvent) => any) | null; + + close(): void; + send(data: string): void; + send(data: ArrayBuffer): void; +} + +declare var RTCDataChannel: { + prototype: RTCDataChannel; + new(): RTCDataChannel; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit +export interface RTCDataChannelInit { + ordered?: boolean; + maxPacketLifeTime?: number; + maxRetransmits?: number; + protocol?: string; + negotiated?: boolean; + id?: number; +} + +// https://www.w3.org/TR/webrtc/#configuration +export interface RTCConfiguration { + iceServers?: RTCIceServer[]; + iceTransportPolicy?: RTCIceTransportPolicy; + bundlePolicy?: RTCBundlePolicy; + rtcpMuxPolicy?: RTCRtcpMuxPolicy; + certificates?: RTCCertificate[]; + iceCandidatePoolSize?: number; + // tcpCandidatePolicy + // continualGatheringPolicy +} + +// https://www.w3.org/TR/webrtc/#dom-rtciceserver +export interface RTCIceServer { + urls: string | string[]; + username?: string; + credential?: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcofferansweroptions +export interface RTCOfferAnswerOptions { +} + +// https://www.w3.org/TR/webrtc/#dom-rtcofferoptions +export interface RTCOfferOptions extends RTCOfferAnswerOptions { + iceRestart?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcansweroptions +export interface RTCAnswerOptions extends RTCOfferAnswerOptions { +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions-0 +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions-1 +export interface RTCPeerConnection extends EventTarget { + readonly canTrickleIceCandidates?: boolean; + readonly signalingState: RTCSignalingState; + readonly iceGatheringState: RTCIceGatheringState; + readonly iceConnectionState: RTCIceConnectionState; + readonly connectionState: RTCPeerConnectionState; + readonly localDescription?: RTCSessionDescription; + readonly remoteDescription?: RTCSessionDescription; + readonly currentLocalDescription?: RTCSessionDescription; + readonly currentRemoteDescription?: RTCSessionDescription; + readonly pendingLocalDescription?: RTCSessionDescription; + readonly pendingRemoteDescription?: RTCSessionDescription; + readonly sctp?: RTCSctpTransport; + + onnegotiationneeded: ((this: RTCPeerConnection, ev: Event) => any) | null; + onicecandidate: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceEvent) => any) | null; + onicecandidateerror: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceErrorEvent) => any) | null; + oniceconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onicegatheringstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onsignalingstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + ontrack: ((this: RTCPeerConnection, ev: RTCTrackEvent) => any) | null; + ondatachannel: ((this: RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null; + + addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender; + removeTrack(sender: RTCRtpSender): void; + setLocalDescription(description?: RTCSessionDescriptionInit): Promise; + setRemoteDescription(description: RTCSessionDescriptionInit): Promise; + createOffer(options?: RTCOfferOptions): Promise; + createAnswer(options?: RTCAnswerOptions): Promise; + createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel; + addIceCandidate(candidate?: RTCIceCandidateInit): Promise; + getSenders(): RTCRtpSender[]; + getReceivers(): RTCRtpReceiver[]; + getTransceivers(): RTCRtpTransceiver[]; + getConfiguration(): RTCConfiguration; + restartIce(): void; + setConfiguration(configuration?: RTCConfiguration): void; + addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver; + close(): void; + getStats(selector?: MediaStreamTrack): Promise; +} + +declare var RTCPeerConnection: { + prototype: RTCPeerConnection; + new(configuration?: RTCConfiguration): RTCPeerConnection; + // https://www.w3.org/TR/webrtc/#sec.cert-mgmt + generateCertificate(keygenAlgorithm: AlgorithmIdentifier): Promise; +}; + +// https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface +export interface RTCRtpReceiver { + readonly track: MediaStreamTrack; + readonly transport: RTCDtlsTransport | null; + + getParameters(): RTCRtpReceiveParameters; + getStats(): Promise; + getContributingSources(): RTCRtpContributingSource[]; + getSynchronizationSources(): RTCRtpSynchronizationSource[]; +} + +declare var RTCRtpReceiver: { + prototype: RTCRtpReceiver; + new(): RTCRtpReceiver; + getCapabilities(kind: string): RTCRtpCapabilities | null; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodingparameters +export interface RTCRtpCodingParameters { + rid?: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters +export interface RTCRtpEncodingParameters extends RTCRtpCodingParameters { + active?: boolean; + maxBitrate?: number; + maxFramerate?: number; + scaleResolutionDownBy?: number; + // networkPriority?: RTCPriorityType; + // priority?: RTCPriorityType; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodecparameters +export interface RTCRtpCodecParameters { + clockRate: number; + channels?: number; + mimeType: string; + sdpFmtpLine: string; + payloadType: number; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpheaderextensionparameters +export interface RTCRtpHeaderExtensionParameters { + id: number; + uri: string; + encrypted?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtcpparameters +export interface RTCRtcpParameters { + cname?: string; + reducedSize?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpparameters +export interface RTCRtpParameters { + codecs: RTCRtpCodecParameters[]; + headerExtensions: RTCRtpHeaderExtensionParameters[]; + rtcp: RTCRtcpParameters; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpsendparameters +export interface RTCRtpSendParameters extends RTCRtpParameters { + // degradationPreference?: DegradationPreference; + encodings: RTCRtpEncodingParameters[]; + transactionId: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpreceiveparameters +export interface RTCRtpReceiveParameters extends RTCRtpParameters { +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcontributingsource +export interface RTCRtpContributingSource { + timestamp: HighResTimeStamp; + source: number; + audioLevel?: number; + rtpTimestamp: number; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpsynchronizationsource +export interface RTCRtpSynchronizationSource extends RTCRtpContributingSource { +} + +// https://www.w3.org/TR/webrtc/#rtcrtpsender-interface +// https://www.w3.org/TR/webrtc/#rtcrtpsender-interface-extensions +export interface RTCRtpSender { + readonly track: MediaStreamTrack | null; + readonly transport: RTCDtlsTransport | null; + readonly dtmf: RTCDTMFSender | null; + + setParameters(parameters: RTCRtpSendParameters): Promise; + getParameters(): RTCRtpSendParameters; + replaceTrack(withTrack: MediaStreamTrack | null): Promise; + setStreams(...streams: MediaStream[]): void; + getStats(): Promise; +} + +declare var RTCRtpSender: { + prototype: RTCRtpSender; + new(): RTCRtpSender; + /** + * get the most optimistic view of the capabilities of the system for sending media of the given kind. + * @param kind 'audio' or 'video'. + * @returns instance of RTCRtpCapabilities, or null if has no capabilities corresponding to the value of the kind argument. + */ + getCapabilities(kind: string): RTCRtpCapabilities | null; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodec +export interface RTCRtpCodec { + mimeType: string; + clockRate: number; + channels?: number; + sdpFmtpLine?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcrtpheaderextensioncapability +export interface RTCRtpHeaderExtensionCapability { + uri: string; +} + +// https://www.w3.org/TR/webrtc/#rtcrtpcapabilities +export interface RTCRtpCapabilities { + codecs: RTCRtpCodec[]; + headerExtensions: RTCRtpHeaderExtensionCapability[]; +} + +// https://www.w3.org/TR/webrtc/#rtcdtmfsender +export interface RTCDTMFSender extends EventTarget { + readonly canInsertDTMF: boolean; + readonly toneBuffer: string; + + ontonechange: ((this: RTCDTMFSender, ev: RTCDTMFToneChangeEvent) => any) | null; + + insertDTMF(tones: string, duration?: number, interToneGap?: number): void; +} + +declare var RTCDTMFSender: { + prototype: RTCDTMFSender; + new(): RTCDTMFSender; +}; + +// https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface +export interface RTCRtpTransceiver { + readonly mid: string | null; + readonly sender: RTCRtpSender; + readonly receiver: RTCRtpReceiver; + direction: RTCRtpTransceiverDirection; + readonly currentDirection: RTCRtpTransceiverDirection | null; + + stop(): void; + setCodecPreferences(codecs: RTCRtpCodec[]): void; +} + +declare var RTCRtpTransceiver: { + prototype: RTCRtpTransceiver; + new(): RTCRtpTransceiver; +}; + +// https://www.w3.org/TR/webrtc/#rtcdtlstransport-interface +export interface RTCDtlsTransport extends EventTarget { + readonly iceTransport: RTCIceTransport; + readonly state: RTCDtlsTransportState; + + onstatechange: ((this: RTCDtlsTransport, ev: Event) => any) | null; + onerror: ((this: RTCDtlsTransport, ev: RTCErrorEvent) => any) | null; + + getRemoteCertificates(): ArrayBuffer[]; +} + +declare var RTCDtlsTransport: { + prototype: RTCDtlsTransport; + new(): RTCDtlsTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcdtlsfingerprint +export interface RTCDtlsFingerprint { + algorithm?: string; + value?: string; +} + +// https://www.w3.org/TR/webrtc/#rtccertificate-interface +export interface RTCCertificate { + readonly expires: EpochTimeStamp; + + getFingerprints(): RTCDtlsFingerprint[]; +} + +declare var RTCCertificate: { + prototype: RTCCertificate; + new(): RTCCertificate; +}; + +// https://www.w3.org/TR/webrtc/#rtcicetransport +export interface RTCIceTransport extends EventTarget { + readonly role: RTCIceRole; + readonly component: RTCIceComponent; + readonly state: RTCIceTransportState; + readonly gatheringState: RTCIceGathererState; + + onstatechange: ((this: RTCIceTransport, ev: Event) => any) | null; + ongatheringstatechange: ((this: RTCIceTransport, ev: Event) => any) | null; + onselectedcandidatepairchange: ((this: RTCIceTransport, ev: Event) => any) | null; + + getSelectedCandidatePair(): RTCIceCandidatePair | null; +} + +declare var RTCIceTransport: { + prototype: RTCIceTransport; + new(): RTCIceTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcicecandidatepair +export interface RTCIceCandidatePair { + local?: RTCIceCandidate; + remote?: RTCIceCandidate; +} + +// https://www.w3.org/TR/webrtc/#rtcsctptransport-interface +export interface RTCSctpTransport extends EventTarget { + readonly maxChannels?: number; + readonly maxMessageSize: number; + readonly state: RTCSctpTransportState; + readonly transport: RTCDtlsTransport; + + onstatechange: ((this: RTCSctpTransport, ev: Event) => any) | null; +} + +declare var RTCSctpTransport: { + prototype: RTCSctpTransport; + new(): RTCSctpTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcstats-dictionary +export interface RTCStats { + timestamp: HighResTimeStamp; + type: RTCStatsType; + id: string; +} + +// https://www.w3.org/TR/webrtc/#rtcstatsreport-object +export interface RTCStatsReport { + readonly stats: Map; + // readonly timestamp: HighResTimeStamp; // android + // forEach(callback: (value: RTCStats, key: string, parent: RTCStatsReport) => void): void; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtctransportstats +export interface RTCTransportStats extends RTCStats { + packetsSent?: number; + packetsReceived?: number; + bytesSent?: number; + bytesReceived?: number; + iceRole?: RTCIceRole; + iceLocalUsernameFragment?: string; + dtlsState: RTCDtlsTransportState; + iceState?: RTCIceTransportState; + selectedCandidatePairId?: string; + localCertificateId?: string; + remoteCertificateId?: string; + tlsVersion?: string; + dtlsCipher?: string; + // dtlsRole?: RTCDtlsRole; + srtpCipher?: string; + selectedCandidatePairChanges: number; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtcrtpstreamstats +export interface RTCRtpStreamStats extends RTCStats { + ssrc: number; + kind: string; + transportId?: string; + codecId?: string; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats +export interface RTCIceCandidatePairStats extends RTCStats { + transportId: string; + localCandidateId: string; + remoteCandidateId: string; + state: RTCStatsIceCandidatePairState; + nominated?: boolean; + packetsSent?: number; + packetsReceived?: number; + bytesSent?: number; + bytesReceived?: number; + lastPacketSentTimestamp?: HighResTimeStamp; + lastPacketReceivedTimestamp?: HighResTimeStamp; + totalRoundTripTime?: number; + currentRoundTripTime?: number; + availableOutgoingBitrate?: number; + availableIncomingBitrate?: number; + requestsReceived?: number; + requestsSent?: number; + responsesReceived?: number; + responsesSent?: number; + consentRequestsSent?: number; + packetsDiscardedOnSend?: number; + bytesDiscardedOnSend?: number; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-capabilities +export interface MediaTrackCapabilities { + width?: ULongRange; + height?: ULongRange; + aspectRatio?: DoubleRange; + frameRate?: DoubleRange; + facingMode?: string[]; + resizeMode?: string[]; + sampleRate?: ULongRange; + sampleSize?: ULongRange; + echoCancellation?: boolean[]; + autoGainControl?: boolean[]; + noiseSuppression?: boolean[]; + latency?: DoubleRange; + channelCount?: ULongRange; + deviceId?: string; + groupId?: string; +} + +export interface MediaTrackConstraintSet { + width?: ConstrainULong; + height?: ConstrainULong; + aspectRatio?: ConstrainDouble; + frameRate?: ConstrainDouble; + facingMode?: ConstrainString; + resizeMode?: ConstrainString; + sampleRate?: ConstrainULong; + sampleSize?: ConstrainULong; + echoCancellation?: ConstrainBoolean; + autoGainControl?: ConstrainBoolean; + noiseSuppression?: ConstrainBoolean; + latency?: ConstrainDouble; + channelCount?: ConstrainULong; + deviceId?: ConstrainString; + groupId?: ConstrainString; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-constraints +export interface MediaTrackConstraints extends MediaTrackConstraintSet { + advanced?: MediaTrackConstraintSet[]; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-settings +export interface MediaTrackSettings { + width?: number; + height?: number; + aspectRatio?: number; + frameRate?: number; + facingMode?: string; + resizeMode?: string; + sampleRate?: number; + sampleSize?: number; + echoCancellation?: boolean; + autoGainControl?: boolean; + noiseSuppression?: boolean; + latency?: number; + channelCount?: number; + deviceId?: string; + groupId?: string; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-supported-constraints +export interface MediaTrackSupportedConstraints { + width?: boolean; + height?: boolean; + aspectRatio?: boolean; + frameRate?: boolean; + facingMode?: boolean; + resizeMode?: boolean; + sampleRate?: boolean; + sampleSize?: boolean; + echoCancellation?: boolean; + autoGainControl?: boolean; + noiseSuppression?: boolean; + latency?: boolean; + channelCount?: boolean; + deviceId?: boolean; + groupId?: boolean; +} + +export interface MediaStreamConstraints { + // default is false + video?: boolean | MediaTrackConstraints; + // default is false + audio?: boolean | MediaTrackConstraints; +} + +// https://www.w3.org/TR/screen-capture/#displaymediastreamoptions +export interface DisplayMediaStreamOptions { + video?: boolean | MediaTrackConstraints; + audio?: boolean | MediaTrackConstraints; +} + +export interface MediaSource { + readonly state: MediaSourceState; +} + +export interface AudioSource extends MediaSource { + setVolume(volume: number); +} + +export interface VideoSource extends MediaSource { + oncapturerstarted: ((this: VideoSource, ev: VideoCapturerStartedEvent) => any) | null; + oncapturerstopped: ((this: VideoSource, ev: Event) => any) | null; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastreamtrack +export interface MediaStreamTrack extends EventTarget { + readonly kind: string; + readonly id: string; + enabled: boolean; + readonly readyState: MediaStreamTrackState; + + stop(): void; +} + +export declare var MediaStreamTrack: { + prototype: MediaStreamTrack; + new(): MediaStreamTrack; +}; + +export interface AudioTrack extends MediaStreamTrack { +} + +export interface VideoTrack extends MediaStreamTrack { +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastream +export interface MediaStream extends EventTarget { + readonly id: string; + readonly active: boolean; + + addTrack(track: MediaStreamTrack): void; + removeTrack(track: MediaStreamTrack): void; + getTrackById(trackId: string): MediaStreamTrack | null; + getTracks(): MediaStreamTrack[]; + getAudioTracks(): MediaStreamTrack[]; + getVideoTracks(): MediaStreamTrack[]; +} + +declare var MediaStream: { + prototype: MediaStream; + new(): MediaStream; + new(stream: MediaStream): MediaStream; + new(tracks: MediaStreamTrack[]): MediaStream; +}; + +export interface MediaDeviceInfo { + readonly deviceId: string; + readonly kind:MediaDeviceKind; + readonly label: string; + readonly groupId: string; +} + +export interface DeviceChangeEvent extends Event { + readonly devices: ReadonlyArray; + readonly userInsertedDevices: ReadonlyArray; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediadevices +export interface MediaDevices extends EventTarget { + enumerateDevices(): Promise; + getSupportedConstraints(): MediaTrackSupportedConstraints; + getUserMedia(constraints?: MediaStreamConstraints): Promise; + getDisplayMedia(options?: DisplayMediaStreamOptions): Promise; +} + +declare var MediaDevices: { + prototype: MediaDevices; + new(): MediaDevices; +}; + +export interface NativeVideoRenderer { + readonly surfaceId?: string; + readonly videoTrack?: MediaStreamTrack; + + init(surfaceId: string): void; + setVideoTrack(videoTrack: MediaStreamTrack | null): void; + release(): void; +} + +declare var NativeVideoRenderer: { + prototype: NativeVideoRenderer; + new(): NativeVideoRenderer; +}; + +export interface AudioError extends Error { + readonly type: AudioErrorType; +} + +export interface AudioErrorEvent extends Event { + readonly error: AudioError; +} + +export interface AudioStateChangeEvent extends Event { + readonly state: AudioState; +} + +export interface AudioCapturerSamplesReadyEvent extends Event { + readonly samples: AudioSamples; +} + +export interface AudioDeviceModuleOptions { + // input source. see ohos.multimedia.audio.SourceType, default is SOURCE_TYPE_VOICE_COMMUNICATION. + audioSource?: number; + + // input format. see ohos.multimedia.audio.AudioSampleFormat, default SAMPLE_FORMAT_S16LE. + audioFormat?: number; + + // input sample rate, default is 48000. + inputSampleRate?: number; + + // Control if stereo input should be used or not. The default is mono. + useStereoInput?: boolean; + + // output sample rate, default is 48000. + outputSampleRate?: number; + + // Control if stereo output should be used or not. The default is mono. + useStereoOutput?: boolean; + + // output audio usage. see ohos.multimedia.audio.StreamUsage, default is STREAM_USAGE_VOICE_COMMUNICATION + rendererUsage?: number; + + // enable low latency capturing and rendering, default is false + useLowLatency?: number; + + // Control if the built-in HW acoustic echo canceler should be used or not, default is false. + // It is possible to query support by calling AudioDeviceModule.isBuiltInAcousticEchoCancelerSupported() + useHardwareAcousticEchoCanceler?: boolean; + + // Control if the built-in HW noise suppressor should be used or not, default is false. + // It is possible to query support by calling AudioDeviceModule.isBuiltInNoiseSuppressorSupported() + useHardwareNoiseSuppressor?: boolean; +} + +export interface AudioSamples { + // See ohos.multimedia.audio.AudioSampleFormat + readonly audioFormat: number; + + // See ohos.multimedia.audio.AudioChannel + readonly channelCount: number; + + // See ohos.multimedia.audio.AudioSamplingRate + readonly sampleRate: number; + + // audio data + readonly data: ArrayBuffer; +} + +export interface AudioDeviceModule { + oncapturererror: ((this: any, event: AudioErrorEvent) => void) | null; + oncapturerstatechange: ((this: any, event: AudioStateChangeEvent) => void) | null; + // Called when new audio samples are ready. This should only be set for debug purposes + oncapturersamplesready: ((this: any, event: AudioCapturerSamplesReadyEvent) => void) | null; + onrenderererror: ((this: any, event: AudioErrorEvent) => void) | null; + onrendererstatechange: ((this: any, event: AudioStateChangeEvent) => void) | null; + + setSpeakerMute(mute: boolean): void; + setMicrophoneMute(mute: boolean): void; + setNoiseSuppressorEnabled(enabled: boolean): boolean; +} + +declare var AudioDeviceModule: { + prototype: AudioDeviceModule; + new(options?: AudioDeviceModuleOptions): AudioDeviceModule; + isBuiltInAcousticEchoCancelerSupported(): boolean; + isBuiltInNoiseSuppressorSupported(): boolean; +}; + +// Hold a native webrtc.AudioProcessing instance +export interface AudioProcessing {} + +export interface AudioProcessingFactory { + create(): AudioProcessing; +} + +declare var AudioProcessingFactory: { + prototype: AudioProcessingFactory; + new(): AudioProcessingFactory; +}; + +export interface VideoEncoderFactory {} + +export interface VideoDecoderFactory {} + +export interface HardwareVideoEncoderFactory extends VideoEncoderFactory { + readonly enableH264HighProfile: boolean; +} + +declare var HardwareVideoEncoderFactory: { + prototype: HardwareVideoEncoderFactory; + new(enableH264HighProfile?: boolean): HardwareVideoEncoderFactory; +}; + +export interface SoftwareVideoEncoderFactory extends VideoEncoderFactory { +} + +declare var SoftwareVideoEncoderFactory: { + prototype: SoftwareVideoEncoderFactory; + new(): SoftwareVideoEncoderFactory; +}; + +export interface HardwareVideoDecoderFactory extends VideoDecoderFactory { +} + +declare var HardwareVideoDecoderFactory: { + prototype: HardwareVideoDecoderFactory; + new(): HardwareVideoDecoderFactory; +}; + +export interface SoftwareVideoDecoderFactory extends VideoDecoderFactory { +} + +declare var SoftwareVideoDecoderFactory: { + prototype: SoftwareVideoDecoderFactory; + new(): SoftwareVideoDecoderFactory; +}; + +export interface PeerConnectionFactoryOptions { + adm?: AudioDeviceModule; + videoEncoderFactory?: VideoEncoderFactory; + videoDecoderFactory?: VideoDecoderFactory; + audioProcessing?: AudioProcessing; +} + +export interface PeerConnectionFactory { + createPeerConnection(config: RTCConfiguration): RTCPeerConnection; + createAudioSource(constraints?: MediaTrackConstraints): AudioSource; + createAudioTrack(id: string, source: AudioSource): AudioTrack; + createVideoSource(constraints?: MediaTrackConstraints, isScreencast?: boolean): VideoSource; + createVideoTrack(id: string, source: VideoSource): VideoTrack; + startAecDump(fd: number, max_size_bytes: number): boolean; + stopAecDump(): void; +} + +declare var PeerConnectionFactory: { + prototype: PeerConnectionFactory; + new(options?: PeerConnectionFactoryOptions): PeerConnectionFactory; + getDefault(): PeerConnectionFactory; + setDefault(factory: PeerConnectionFactory): void; +}; + +export interface Loggable { + logMessage(message: string, severity: number, tag: string): void; +} + +export class NativeLogging { + static injectLoggable(loggable: Loggable, severity: number): void; + static deleteLoggable(): void; + static enableLogToDebugOutput(severity): void; + static enableLogThreads(): void; + static enableLogTimeStamps(): void; + static log(message: string, severity: number, tag: string): void; +} diff --git a/sdk/ohos/api/libwebrtc.md b/sdk/ohos/api/libwebrtc.md new file mode 100644 index 0000000000000000000000000000000000000000..77dd36a6c03bf3a95f0eb04a67285c9d030f8e15 --- /dev/null +++ b/sdk/ohos/api/libwebrtc.md @@ -0,0 +1,2966 @@ +# ohos_webrtc + +## RTCErrorDetailType + +表示 WebRTC 中可能出现的错误详细类型。 + +| 名称 | 类型 | 说明 | +|--------------------------------|--------|-------------| +| data-channel-failure | string | 数据通道发生了故障 | +| dtls-failure | string | DTLS 安全传输失败 | +| fingerprint-failure | string | 安全证书指纹校验失败 | +| hardware-encoder-error | string | 硬件编码器发生错误 | +| hardware-encoder-not-available | string | 硬件编码器不可用 | +| sctp-failure | string | SCTP 传输失败 | +| sdp-syntax-error | string | SDP 语法错误 | + +## RTCIceProtocol + +用于表示 ICE 协议的类型别名。 + +| 名称 | 类型 | 说明 | +|-----|--------|--------------| +| tcp | string | 传输控制协议(TCP) | +| udp | string | 用户数据报协议(UDP) | + +## RTCIceCandidateType + +表示 ICE 候选者类型的类型别名。 + +| 名称 | 类型 | 说明 | +|-------|--------|---------| +| host | string | 主机本地地址 | +| prflx | string | 反射地址 | +| relay | string | 中继服务器 | +| srflx | string | 服务器反射地址 | + +## RTCIceTcpCandidateType + +表示 ICE TCP 候选者类型的类型别名。 + +| 名称 | 类型 | 说明 | +|---------|--------|--------------| +| active | string | 主动连接到远程对等方 | +| passive | string | 等待远程对等方连接 | +| so | string | 尝试与对等端同时打开连接 | + +## RTCIceGathererState + +用于表示 ICE 收集器的状态。 + +| 名称 | 类型 | 说明 | +|-----------|--------|--------------------| +| complete | string | ICE 收集器处于初始状态 | +| gathering | string | ICE 收集器正在收集候选者 | +| new | string | ICE 收集器已经完成了候选者的收集 | + +## RTCIceTransportState + +表示 WebRTC 中 ICE 传输的不同状态。 + +| 名称 | 类型 | 说明 | +|--------------|--------|-----------------| +| checking | string | ICE 传输处于初始状态 | +| closed | string | ICE 传输正在检查连接 | +| completed | string | ICE 传输已连接但还没有完成 | +| connected | string | ICE 传输已完成所有连接检查 | +| disconnected | string | ICE 传输已断开连接 | +| failed | string | ICE 传输连接失败 | +| new | string | ICE 传输已关闭 | + +## RTCIceRole + +表示 ICE 代理在连接协商过程中的角色。 + +| 名称 | 类型 | 说明 | +|-------------|--------|---------------| +| unknown | string | ICE 角色未知 | +| controlling | string | ICE 代理处于控制角色 | +| controlled | string | ICE 代理处于被控制角色 | + +## RTCIceComponent + +表示 ICE 候选者的组件类型。 + +| 名称 | 类型 | 说明 | +|------|--------|----------------------| +| rtp | string | 用于传输 RTP 数据的 ICE 组件 | +| rtcp | string | 用于传输 RTCP 数据的 ICE 组件 | + +## RTCSdpType + +描述 SDP 会话描述类型的枚举类型。 + +| 名称 | 类型 | 说明 | +|----------|--------|--------------------------------------| +| offer | string | 表示一个 SDP 描述,用于向另一端发起媒体会话的邀请 | +| answer | string | 表示一个 SDP 描述,用于响应来自远端的媒体会话邀请 | +| pranswer | string | 表示一个 SDP 描述,用于发送给远端作为对 Offer 的临时响应 | +| rollback | string | 表示一个 SDP 描述,用于指示取消当前的会话描述状态,回滚到之前的状态 | + +## BinaryType + +表示 WebRTC 中可能的二进制数据类型。 + +| 名称 | 类型 | 说明 | +|-------------|--------|---------------------| +| blob | string | 处理文件或其它类型的大量二进制数据 | +| arraybuffer | string | 通用的、固定长度的原始二进制数据缓冲区 | + +## DataChannelState + +表示数据通道的状态。 + +| 名称 | 类型 | 说明 | +|------------|--------|----------------------| +| closed | string | 表示数据通道已关闭 | +| closing | string | 表示数据通道正在关闭中 | +| connecting | string | 表示数据通道正在连接中 | +| open | string | 表示数据通道已经打开并且可以进行数据传输 | + +## RTCDtlsTransportState + +表示 DTLS 传输状态的可能值。 + +| 名称 | 类型 | 说明 | +|------------|--------|--------------------------------------------------------| +| new | string | 此状态表示传输对象处于初始状态,连接尚未建立,适用于初始配置和资源分配 | +| connecting | string | 此状态表示正在进行 DTLS 握手过程,用于建立安全通道。在此阶段,可以监控握手进度,并处理潜在的连接问题 | +| connected | string | 此状态表示传输已准备好发送和接收加密数据。开发者可以在此状态下开始安全的数据传输 | +| closed | string | 此状态表示连接已正常关闭。可以在此状态下进行资源清理和释放,并通知应用层连接已终止 | +| failed | string | 此状态表示传输因错误无法建立或维持连接。开发者需要在此状态下处理错误,可能需要重新尝试连接或通知用户连接失败 | + +## RTCIceGatheringState + +表示 ICE 收集的状态。 + +| 名称 | 类型 | 说明 | +|-----------|--------|---------------------| +| new | string | 对等连接刚刚创建,尚未进行任何网络连接 | +| gathering | string | ICE 代理正在收集候选的连接 | +| complete | string | ICE 代理完成了候选的收集 | + +## RTCIceConnectionState + +表示 ICE 连接状态。 + +| 名称 | 类型 | 说明 | +|--------------|--------|------------------------------------------------------------| +| new | string | ICE 代理正在搜集地址或者等待远程候选可用 | +| checking | string | ICE 代理已收到至少一个远程候选,并进行校验,无论此时是否有可用连接。同时可能在继续收集候选 | +| connected | string | ICE 代理至少对每个候选发现了一个可用的连接,此时仍然会继续测试远程候选以便发现更优的连接。同时可能在继续收集候选 | +| completed | string | ICE 代理已经发现了可用的连接,不再测试远程候选 | +| failed | string | ICE 候选测试了所有远程候选没有发现匹配的候选 | +| disconnected | string | 测试不再活跃 | +| closed | string | ICE 代理关闭,不再应答任何请求 | + +## RTCSignalingState + +表示与对等端连接过程中的信令处理状态。 + +| 名称 | 类型 | 说明 | +|----------------------|--------|------------------------------------------------| +| stable | string | 没有正在进行的 offer/answer 的交换 | +| have-local-offer | string | 成功应用了类型为 offer 的远程描述 | +| have-remote-offer | string | 成功应用了类型为 offer 的远程描述 | +| have-local-pranswer | string | 成功应用了类型为 offer 的远程描述,并且成功应用了类型为 pranswer 的本地描述 | +| have-remote-pranswer | string | 成功应用了类型为 offer 的本地描述,并且成功应用了类型为 pranswer 的远程描述 | +| closed | string | RTCPeerConnection 已关闭 | + +## RTCPeerConnectionState + +表示当前所有被使用的 ICE 连接的状态。 + +| 名称 | 类型 | 说明 | +|--------------|--------|------------------------------------------------------------------------------------------------------| +| new | string | 表示至少有一个 ICE 连接处于 new 状态,并且没有连接处于以下状态: connecting、checking、failed、disconnected,或者这些连接都处于 closed 状态 | +| connecting | string | 表示至少有一个 ICE 连接处于正在建立连接的状态;也就是说,它们的 iceConnectionState 值为 checking 或 connected,并且没有连接处于 failed 状态 | +| connected | string | 表示每一个 ICE 连接要么正在使用(connected 或 completed 状态),要么已被关闭(closed 状态);并且,至少有一个连接处于 connected 或 completed 状态 | +| disconnected | string | 表示至少有一个 ICE 连接处于 disconnected 状态,并且没有连接处于 failed、connecting 或 checking 状态 | +| failed | string | 表示至少有一个 ICE 连接处于 failed 的状态 | +| closed | string | RTCPeerConnection 已关闭 | + + +## RTCBundlePolicy + +表示与捆绑相关的 ICE 收集策略。 + +| 名称 | 类型 | 说明 | +|------------|--------|----------------------------------------------------------------------| +| balanced | string | ICE 代理为每种使用的媒体类型(视频、音频和数据)收集 ICE 选项。如果远程对等端不支持捆绑,在单独的传输上仅协商一个音频和视频轨道 | +| max-compat | string | ICE 代理为每个轨道收集 ICE 候选者。如果远程对等端不支持捆绑,在单独的传输上协商所有的媒体轨道 | +| max-bundle | string | ICE 代理只收集一个轨道的 ICE 候选者。如果远程对等端不支持捆绑,仅协商一个媒体轨道 | + + +## RTCRtcpMuxPolicy + +表示与RTCP多路复用相关的 ICE 收集策略。 + +| 名称 | 类型 | 说明 | +|---------|--------|----------------------------------------------------------------------| +| require | string | 仅在 RTP 候选者上为 RTP 和和 RTCP 多路复用收集 ICE 候选。如果远程对等端不能使用 RTCP 多路复用,会话协商将失败 | + +## RTCIceTransportPolicy + +ICE 候选策略,WebRTC 使用该策略向应用程序显示允许的候选者,只有这些候选者将用于连接检查。 + +| 名称 | 类型 | 说明 | +|-------|--------|-----------------------------------| +| all | string | ICE 代理可以使用任何类型的候选者 | +| relay | string | ICE 代理只使用媒体中继候选者,如经过 TURN 服务器的候选者 | + +## RTCRtpTransceiverDirection + +可能的字符串值,每个值表示一种传输方向的状态。 + +| 名称 | 类型 | 说明 | +|----------|--------|----------------------------------------| +| inactive | string | 表示既不发送也不接收媒体数据,传输器处于非活动状态。 | +| recvonly | string | 表示只接收媒体数据,不发送数据,这通常用于希望仅监听远端媒体流的情况。 | +| sendonly | string | 表示只发送媒体数据,不接收数据,这通常用于希望仅向远端发送媒体流的情况。 | +| sendrecv | string | 表示既发送又接收媒体数据,这是最常见的双向通信模式。 | +| stopped | string | 表示传输器已停止,不再发送或接收媒体数据,此状态表示传输器的生命周期已结束。 | + +## RTCSctpTransportState + +描述 SCTP 传输的状态的枚举类型。 + +| 名称 | 类型 | 说明 | +|------------|--------|----------------------| +| connecting | string | 表示 SCTP 传输正在建立连接的过程中 | +| connected | string | 表示 SCTP 传输已成功建立连接 | +| closed | string | 表示 SCTP 传输已关闭 | + +## RTCStatsType + +用于指定 WebRTC 统计信息(RTCStats)的类型。 + +| 名称 | 类型 | 说明 | +|---------------------|--------|-------------------------| +| candidate-pair | string | 表示 ICE 候选对的统计信息 | +| certificate | string | 表示与安全证书相关的统计信息 | +| codec | string | 表示编解码器的统计信息 | +| data-channel | string | 表示数据通道的统计信息 | +| inbound-rtp | string | 表示入站 RTP 流的统计信息 | +| local-candidate | string | 表示本地 ICE 候选者的统计信息 | +| media-playout | string | 表示媒体播放的统计信息 | +| media-source | string | 表示媒体源的统计信息 | +| outbound-rtp | string | 表示出站 RTP 流的统计信息 | +| peer-connection | string | 表示 PeerConnection 的统计信息 | +| remote-candidate | string | 表示远端 ICE 候选者的统计信息 | +| remote-inbound-rtp | string | 表示远端 ICE 候选者的统计信息 | +| remote-outbound-rtp | string | 表示远端的出站 RTP 流的统计信息。 | +| transport | string | 表示传输层的统计信息 | + +## RTCStatsIceCandidatePairState + +描述 ICE 候选对状态的枚举类型。 + +| 名称 | 类型 | 说明 | +|-------------|--------|----------------| +| failed | string | ICE 候选对连接失败 | +| frozen | string | ICE 候选对连接被冻结 | +| in-progress | string | ICE 候选对连接正在进行中 | +| inprogress | string | ICE 候选对连接正在进行中 | +| succeeded | string | ICE 候选对连接成功建立 | +| waiting | string | ICE 候选对处于等待状态 | + +## MediaStreamTrackState + +媒体流轨道的状态。 + +| 名称 | 类型 | 说明 | +|-------|--------|--------------------| +| live | string | 轨道处于活动状态,正在发送媒体数据。 | +| ended | string | 轨道已经结束,不再发送媒体数据。 | + +## MediaDeviceKind + +媒体设备类型 + +| 名称 | 类型 | 说明 | +|-------------|--------|--------------| +| audioinput | string | 音频输入设备,如麦克风。 | +| audiooutput | string | 音频输出设备,如麦克风。 | +| videoinput | string | 视频输入设备,如相机。 | + +## HighResTimeStamp + +表示高分辨率时间戳。 + +| 名称 | 类型 | 说明 | +|------------------|--------|----------------| +| HighResTimeStamp | number | 表示从某个固定起点开始的时间 | + +## EpochTimeStamp + +时间戳 + +| 名称 | 类型 | 说明 | +|----------------|--------|-----------| +| EpochTimeStamp | number | 用于存储时间戳信息 | + +## ConstrainBoolean + +用于表示 boolean 或 ConstrainBooleanParameters 类型。 + +| 名称 | 类型 | 说明 | +| ---------------- | ------------------------------------- | ------------------------------------------------------------ | +| ConstrainBoolean | boolean \| ConstrainBooleanParameters | boolean:表示可以直接接受 true 或 false 的布尔值。ConstrainBooleanParameters:表示一个接口或类型,通常用于更复杂的约束条件,用于描述对布尔值的约束条件,例如期望的值或确切的值。 | + +## ConstrainULong + +用于表示 number(unsigned long) 或 ConstrainULongRange 类型。 + +| 名称 | 类型 | 说明 | +| -------------- | ----------------------------- | ------------------------------------------------------------ | +| ConstrainULong | number \| ConstrainULongRange | number:表示可以直接接受一个数值。ConstrainULongRange:表示一个接口或类型,通常用于更复杂的约束条件。 | + +## ConstrainDouble + +用于表示 number(double) 或 ConstrainDoubleRange 类型。 + +| 名称 | 类型 | 说明 | +| --------------- | ------------------------------- |----------------------| +| ConstrainDouble | number \| ConstrainDoubleRange | number:表示可以直接接受一个数值。ConstrainDoubleRange:表示一个接口或类型,通常用于更复杂的约束条件。 | + +## ConstrainString + +用于表示 string 或 string[] 或 ConstrainStringParameters 类型。 + +| 名称 | 类型 | 说明 | +| --------------- | ----------------------------------------------- | ------------------------------------------------------------ | +| ConstrainString | string \| string[] \| ConstrainStringParameters | string:表示可以直接接受一个字符串。string[]:表示可以接受一个字符串数组,用于描述多个可能的字符串值。ConstrainStringParameters:表示一个接口或类型,通常用于更复杂的约束条件,可以包含 exact和 ideal等属性,用于描述对字符串值的约束条件,例如期望的值或确切的值。 | + +## AlgorithmIdentifier + +用于表示 string 或 Algorithm 类型。 + +| 名称 | 类型 | 说明 | +| --------------- | ----------------------------------------------- | ------------------------------------------------------------ | +| AlgorithmIdentifier | string \| Algorithm | string:表示可以直接接受一个字符串。Algorithm:表示一个接口或类型,用于更详细的描述一个算法 | + +## RTCErrorInit + +用于初始化RTCError。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| ------------- | ------------------ | ---- | ---- | ---- | -------------------------------- | +| errorDetail | RTCErrorDetailType | 是 | 否 | 是 | 提供关于错误类型的明确描述 | +| sdpLineNumber | number | 是 | 否 | 否 | 指示出错的SDP行号 | +| sctpCauseCode | number | 是 | 否 | 否 | 提供了导致连接问题的具体错误代码 | +| receivedAlert | number | 是 | 否 | 否 | 指示接收到的警报代码 | +| sentAlert | number | 是 | 否 | 否 | 指示发送的警报代码 | + +## RTCError + +继承自Error,用于描述WebRTC相关的错误信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------------- | ------------------ | ---- | ---- | -------------------------------- | +| errorDetail | RTCErrorDetailType | 是 | 否 | 提供关于错误类型的明确描述 | +| sdpLineNumber | number | 是 | 否 | 指示出错的SDP行号 | +| sctpCauseCode | number | 是 | 否 | 提供了导致连接问题的具体错误代码 | +| receivedAlert | number | 是 | 否 | 指示接收到的警报代码 | +| sentAlert | number | 是 | 否 | 指示发送的警报代码 | + +## Event + +表示一个事件(当前无任何内容,由特定事件类型继承)。 + +## EventTarget + +表示当某件事情发送时,该对象会分发相关事件(当前无任何内容)。 + +## RTCErrorEvent + +继承自 Event,提供了关于发生的错误的详细描述。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ----- | -------- | ---- | ---- | ------------ | +| error | RTCError | 是 | 否 | 错误详细信息 | + +## RTCTrackEvent + +继承自 Event,向 PeerConnection 中添加媒体轨道时会发送该事件。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-------------|--------------------------------------------|-----|-----|-----------------| +| receiver | [RTCRtpReceiver](#rtcrtpreceiver) | 是 | 否 | 表示与事件相关联的接收器。 | +| track | [MediaStreamTrack](#mediastreamtrack) | 是 | 否 | 表示与事件相关联的媒体流轨道。 | +| streams | ReadonlyArray<[MediaStream](#mediastream)> | 是 | 否 | 表示与事件相关的媒体流数组。 | +| transceiver | [RTCRtpTransceiver](#rtcrtptransceiver) | 是 | 否 | 表示与事件相关联的收发器。 | + +## RTCPeerConnectionIceEvent + +表示 PeerConnection 中 ICE 候选者相关的事件。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| --------- | --------------- | ---- | ---- | ---------------------------------- | +| candidate | RTCIceCandidate | 是 | 否 | ICE 候选者 | + +## RTCPeerConnectionIceErrorEvent + +表示 ICE 错误的详细信息,随`icecandidateerror`事件发布。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| --------- | ------ | ---- | ---- | ------------------------------------------------------------ | +| address | string | 是 | 否 | 与 STUN 或 TURN 服务器通信的本地IP地址 | +| port | number | 是 | 否 | 一个无符号整数值,与 STUN 或 TURN 服务器进行通信的端口号 | +| url | string | 是 | 否 | 指示发生错误的 STUN 或 TURN 服务器的 URL 的字符串。 | +| errorCode | number | 是 | 否 | 一个无符号整数值,表示 STUN 或 TURN 服务器返回的错误代码 | +| errorText | string | 是 | 否 | 包含 STUN 或 TURN 服务器返回的 STUN 原因文本的字符串 | + +## RTCDataChannelEvent + +代表与特定 RTCDataChannel 相关的事件。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------- | -------------- | ---- | ---- | --------------------------- | +| channel | RTCDataChannel | 是 | 否 | 与该事件关联的RTCDataChannel对象 | + +## MediaStreamTrackEvent + +当 MediaStream 中添加或移除媒体轨道时会触发该事件。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ----- | ---------------- | ---- | ---- | ---------------------------- | +| track | MediaStreamTrack | 是 | 否 | 表示触发事件的音频或视频轨道 | + +## RTCDTMFToneChangeEvent + +在通过 WebRTC 发送 DTMF(双音多频)信号时触发的事件。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ---- | ------ | ---- | ---- | ------------------------------ | +| tone | string | 是 | 否 | 触发事件时发送的 DTMF 信号音调 | + +## MessageEvent\ + +用于定义事件对象,特别是包含消息数据的事件对象。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ---- | ---- | ---- | ---- | ------------------ | +| data | T | 是 | 否 | 存储事件携带的数据 | + +## ULongRange + +用于表示一个数值范围,其值的类型为无符号长整型值(unsigned long)。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ---- | ------ | ---- | ---------------- | +| max | number | 否 | 表示范围的最大值 | +| min | number | 否 | 表示范围的最小值 | + +## DoubleRange + +用于表示一个数值范围,其值的类型为双精度浮点数(double)。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ---- | ------ | ---- | ---------------- | +| max | number | 否 | 表示范围的最大值 | +| min | number | 否 | 表示范围的最小值 | + +## ConstrainDoubleRange + +用于表示一个受限制的数值范围,其值的类型为双精度浮点数(double)。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ----- | ------ | ---- | ---------------- | +| exact | number | 否 | 表示属性的精确值 | +| ideal | number | 否 | 表示属性的理想值 | + +## ConstrainULongRange + +用于表示一个受限制的数值范围,其值的类型为无符号长整型值(unsigned long)。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ----- | ------ | ---- | ---------------- | +| exact | number | 否 | 表示属性的精确值 | +| ideal | number | 否 | 表示属性的理想值 | + +## ConstrainBooleanParameters + +用于在约束条件中定义布尔类型参数的精确值和理想值。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ----- | ------- | ---- | ---------------- | +| exact | boolean | 否 | 表示属性的精确值 | +| ideal | boolean | 否 | 表示属性的理想值 | + +## ConstrainStringParameters + +用于在约束条件中定义字符串类型参数的精确值和理想值。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ----- | ------------------ | ---- | ---------------------------------------------- | +| exact | string \| string[] | 否 | 表示属性的精确值,可以是单个字符串或字符串数组 | +| ideal | string \| string[] | 否 | 表示属性的理想值,可以是单个字符串或字符串数组 | + +## Algorithm + +用于描述一个算法。 + +| 名称 | 类型 | 必填 | 说明 | +| ---- | ------ | ---- | ---------------- | +| name | string | 是 | 已注册的算法名称 | + +## RTCIceServer + +定义了连接单个ICE服务器(如 STUN 或 TURN 服务器)所需的参数。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ---------- | -------------------- | ---- | ----------------------------------------------------- | +| urls | string \| string[] | 是 | 单个字符串或字符串数组,每个字符串表示一个服务器 URL 。 | +| username | string | 否 | 如果是一个 TURN 服务器,则该属性表示鉴权所需的用户名。 | +| credential | string | 否 | 登录 TURN 服务器所需的证明,通常为密码。 | + +## RTCConfiguration + +WebRTC 连接的配置选项 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| -------------------- | --------------------- | ---- | ------------------ | +| iceServers | RTCIceServer[] | 否 | ICE 服务器配置数组 | +| iceTransportPolicy | RTCIceTransportPolicy | 否 | ICE 传输策略 | +| bundlePolicy | RTCBundlePolicy | 否 | Bundle 策略 | +| rtcpMuxPolicy | RTCRtcpMuxPolicy | 否 | RTCP 复用策略 | +| certificates | RTCCertificate[] | 否 | 证书数组 | +| iceCandidatePoolSize | number | 否 | ICE 候选池大小 | + +## RTCOfferAnswerOptions + +在创建 offer 和 answer 时提供了额外的配置选项(当前为空)。 + +## RTCOfferOptions + +在创建 WebRTC offer 时提供了额外的配置选项。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ---------- | ------- | ---- | ---- | +| iceRestart | boolean | 否 | 要在活动连接上重新启动 ICE,请将其设置为true。这将导致返回的 offer 与已经存在的凭证不同。如果你应用返回的 offer,则 ICE 将重新启动。指定false以保留相同的凭据,因此不重新启动 ICE。默认值为false | + +## RTCAnswerOptions + +在创建 WebRTC answer 时提供了配置选项(当前为空)。 + +## RTCPeerConnection + +用于在本地和远程对等端之间建立和管理点对点连接。它提供了一系列方法和事件,用于创建、维护、监视和关闭连接。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| -------------------------------------- | ---------------------- | ---- | ---- | ------------------------------------------------------------ | +| canTrickleIceCandidates | boolean | 是 | 否 | 表示远程对等端是否支持 trickle ICE | +| signalingState | RTCSignalingState | 是 | 否 | 返回一个字符串,表示与对等端连接过程中的信令处理状态 | +| iceGatheringState | RTCIceGatheringState | 是 | 否 | 表示 ICE 收集的状态 | +| iceConnectionState | RTCIceConnectionState | 是 | 否 | 表示 ICE 连接状态 | +| connectionState | RTCPeerConnectionState | 是 | 否 | 表示当前所有被使用的 ICE 连接的状态 | +| localDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription ,代表连接的本地描述。如果本地描述还没有被设置,返回 null | +| remoteDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription 对象,代表连接的远程描述,包括配置和媒体信息。如果当前尚未设置,将返回 null | +| currentLocalDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription 对象,该对象描述自上次 RTCPeerConnection 完成协商并连接到远程对等端后,最近一次成功协商的本地描述,同时也可能包括自创建 Offer 或 Answer 以来由 ICE 代理生成的任何本地候选者 | +| currentRemoteDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription 对象,该对象描述自上次 RTCPeerConnection 完成协商并连接到远程对等端后,最近一次成功协商的远程描述。同时也可能包括自创建 Offer 或 Answer 以来通过 addIcecCandidate() 提供的任何本地候选者 | +| pendingLocalDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription 对象,表示正在协商过程中的本地描述 | +| pendingRemoteDescription | RTCSessionDescription | 是 | 否 | 返回一个 RTCSessionDescription 对象,表示正在协商过程中的远程描述 | +| onnegotiationneeded | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 negotiationneeded | +| onicecandidate | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 icecandidate | +| onicecandidateerror | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 icecandidateerror | +| oniceconnectionstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 iceconnectionstatechange | +| onicegatheringstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 icegatheringstatechange | +| onsignalingstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 signalingstatechange | +| onconnectionstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 connectionstatechange | +| ontrack | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 track | +| ondatachannel | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 datachannel | + +### 事件 + +| 名称 | 接口 | 说明 | +| ---------------------------------- | ---------------------- | ------------------------------------------------------------ | +| negotiationneeded | Event | 当需要通过信令通道协商连接时,向 RTCPeerConnection 发送该事件。如连接的初始设置期间,以及通信环境更改需要重新配置连接时 | +| icecandidate | RTCPeerConnectionIceEvent | 当收集到新的 ICE 候选者时触发 | +| icecandidateerror | RTCPeerConnectionIceErrorEvent | 当收集 ICE 候选者过程中发送错误时触发 | +| iceconnectionstatechange | Event | 当 RTCPeerConnection 的 ICE 连接状态改变时触发 | +| icegatheringstatechange | Event | 当 RTCPeerConnection 的 ICE 收集状态改变时触发 | +| onsignalingstatechange | Event | 当 RTCPeerConnection 的信令状态改变时触发 | +| connectionstatechange | Event | 当 RTCPeerConnection 的连接状态改变时触发 | +| track | RTCTrackEvent | 当已经为特定的 RTCRtpReceiver 协商了新的传入媒体,并且该 RTCRtpReceiver 的轨道已添加到任意相关的远程 MediaStream 中时触发 | +| datachannel | RTCDataChannelEvent | 当远程对等端创建了一个 RTCDataChannel 时触发 | + +### addTrack + +addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender; + +用于将媒体轨道添加到 RTCPeerConnection 连接中。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ------- | ---------------- | ---- | -------------------------- | +| track | MediaStreamTrack | 是 | 表示要发送的媒体轨道 | +| streams | MediaStream[] | 否 | 用来管理和控制媒体轨道的组 | + +返回值: + +| 类型 | 说明 | +|-------------------------------|------------------------------------| +| [RTCRtpSender](#rtcrtpsender) | 表示已添加轨道的发送器对象,用于控制和管理发送到远程对等端的数据流。 | + +### removeTrack + +removeTrack(sender: RTCRtpSender): void; + +从连接中移除一个 RTCRtpSender ,从而停止将特定的媒体流轨道发送到远程对等端。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ------ | ------------ | ---- | ---------------------- | +| sender | [RTCRtpSender](#rtcrtpsender) | 是 | 表示要移除的轨道发送器 | + +### setLocalDescription + +setLocalDescription(description?: RTCSessionDescription): Promise; + +设置与 RTCPeerConnection 连接关联的本地描述。此描述指定连接本地端的属性,包括媒体格式。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ----------- | --------------------- | ---- | ----------------------------------------------- | +| description | RTCSessionDescription | 否 | 本地描述, 通常调用 createOffer 或 createAnswer 获取 | + +返回值: + +| 类型 | 说明 | +| ------------- | ------------------------------------------------------------ | +| Promise | 返回一个 Promise 对象,表示设置本地描述的操作是否成功。成功时 Promise 解析,失败时抛出错误。 | + +### setRemoteDescription + +setRemoteDescription(description: RTCSessionDescription): Promise; + +设置远程对等端的 Offer 或 Answer 。该描述指定连接另一端的属性,包括媒体格式。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ----------- | --------------------- | ---- | ------------------------ | +| description | RTCSessionDescription | 是 | 远程描述,通常从对端获取 | + +返回值: + +| 类型 | 说明 | +| ------------- | ------------------------------------------------------------ | +| Promise | 返回一个 Promise 对象,表示设置远程描述的操作是否成功。成功时 Promise 解析,失败时抛出错误。 | + +### createOffer + +createOffer(options?: RTCOfferOptions): Promise; + +创建 Offer,作为要约以用于发起新的 RTCPeerConnection 连接。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| :------ | --------------- | ---- | ----------------------------- | +| options | RTCOfferOptions | 否 | 创建 Offer 时的参数 | + +返回值: + +| 类型 | 说明 | +| ------------------------------ | ------------------------------------------------------------ | +| Promise | 返回一个 Promise 对象,当 offer 生成成功时,Promise 被解析为一个包含了 offer 的 RTCSessionDescription 对象。 | + +### createAnswer + +createAnswer(options?: RTCAnswerOptions): Promise; + +在 Offer/Answer 协商过程中,创建一个 Answer ,作为从对端收到的 Offer 的应答。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ------- | ---------------- | ---- | ----------------- | +| options | RTCAnswerOptions | 否 | 创建Offer时的参数 | + +返回值: + +| 类型 | 说明 | +| ------------------------------ | ------------------------------------------------------------ | +| Promise | 当成功创建本地 answer 时,Promise 被解析为一个 `RTCSessionDescription` 对象,该对象包含了本地端生成的 answer。 | + +### createDataChannel + +createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel; + +创建一个与远程对等连接的新通道,该通道可以传输任何类型的数据。例如图像、文件传输、文本聊天、游戏更新包等。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| --------------- | ------------------ | ---- | ------------------------ | +| label | string | 是 | 通道名 | +| dataChannelDict | RTCDataChannelInit | 否 | 提供了数据通道的配置选项 | + +返回值: + +| 类型 | 说明 | +| -------------- | ------------------------------------------------ | +| RTCDataChannel | 返回一个 RTCDataChannel 对象,表示创建的数据通道。 | + +### addIceCandidate + +addIceCandidate(candidate?: RTCIceCandidateInit): Promise; + +用于将 ICE 候选者添加到 RTCPeerConnection 中。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| --------- | ------------------- | ---- | ----------------------------- | +| candidate | RTCIceCandidateInit | 否 | 包含要添加的 ICE 候选者的信息 | + +返回值: + +| 类型 | 说明 | +|----------------|-------------------------------------------------------------| +| Promise\ | 返回一个 Promise 对象,表示添加 ICE 候选者的操作是否成功。成功时 Promise 解析,失败时抛出错误。 | + +### getSenders + +getSenders(): RTCRtpSender[]; + +获取当前连接中所有的 RTCRtpSender 对象数组。 + +返回值: + +| 类型 | 说明 | +|---------------------------------|-------------------------| +| [RTCRtpSender](#rtcrtpsender)[] | 返回一个数组,包含当前连接中的所有发送器对象。 | + +### getReceivers + +getReceivers(): RTCRtpReceiver[]; + +获取当前 RTCPeerConnection 中所有的 RTCRtpReceiver 对象数组。 + +返回值: + +| 类型 | 说明 | +|----------------------------------------|-------------------------| +| [RTCRtpReceiver](#rtcrtptransceiver)[] | 返回一个数组,包含当前连接中的所有接收器对象。 | + +### getTransceivers + +getTransceivers(): RTCRtpTransceiver[]; + +获取当前 RTCPeerConnection 中所有的 RTCRtpTransceiver 对象数组。 + +返回值: + +| 类型 | 说明 | +|-------------------------------------------|-------------------------| +| [RTCRtpTransceiver](#rtcrtptransceiver)[] | 返回一个数组,包含当前连接中的所有传输器对象。 | + +### getConfiguration + +getConfiguration(): RTCConfiguration; + +获取当前 RTCPeerConnection 的配置信息。 + +返回值: + +| 类型 | 说明 | +| ---------------- | ------------------------------------ | +| RTCConfiguration | 返回一个包含当前连接配置信息的对象。 | + +### restartIce + +restartIce(): void; + +用于触发 RTCPeerConnection 实例的 ICE 重启过程。 + +### setConfiguration + +setConfiguration(configuration?: RTCConfiguration): void; + +用于设置 RTCPeerConnection 实例的配置信息,包括 ICE 服务器、传输策略和安全证书等。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ------------- | ---------------- | ---- | ------------------------ | +| configuration | RTCConfiguration | 否 | 包含要设置的新配置信息。 | + +### addTransceiver + +addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver; + +用于向 RTCPeerConnection 实例中添加一个新的 RTCRtpTransceiver,用于管理音频或视频流的传输。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +| ----------- | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | +| trackOrKind | [MediaStreamTrack](#mediastreamtrack) \| string | 是 | 可以是 MediaStreamTrack 对象,表示要传输的媒体类型。如果是字符串,指示要添加的传输器的类型,取值为"source"或"video"。 | +| init | [RTCRtpTransceiverInit](#rtcrtptransceiverInit) \| undefined | 否 | 用于配置传输器的初始设置 | + +返回值: + +| 类型 | 说明 | +|------------------------| --------------------------------------------- | +| [RTCRtpTransceiver](#rtcrtptransceiver) | 返回新创建的传输器 (RTCRtpTransceiver) 对象。 | + +### close + +close(): void; + +关闭 RTCPeerConnection 实例,释放与对等连接相关的所有资源,并终止任何正在进行的连接。 + +### getStats + +getStats(selector?: MediaStreamTrack): Promise; + +用于获取指定 MediaStreamTrack 或所有轨道的统计信息 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| -------- | ---------------- | ---- | ------------------------------------ | +| selector | MediaStreamTrack | 否 | 指定要请求统计信息的 MediaStreamTrack | + +返回值: + +| 类型 | 说明 | +| ----------------------- | ------------------------------------------------------------ | +| Promise | 返回一个 Promise 对象,该对象在解析后会得到一个 RTCStatsReport ,其中包含了当前时间段内收集到的统计信息。 | + +## MediaTrackCapabilities + +定义媒体轨道的各种能力和属性。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|------------------|-------------|-----|--------------------| +| width | ULongRange | 否 | 表示媒体轨道支持的视频宽度范围 | +| height | ULongRange | 否 | 表示媒体轨道支持的视频高度范围 | +| aspectRatio | DoubleRange | 否 | 表示媒体轨道支持的视频纵横比范围 | +| frameRate | DoubleRange | 否 | 表示媒体轨道支持的帧率范围 | +| facingMode | string[] | 否 | 表示媒体轨道支持的摄像头朝向模式 | +| resizeMode | string[] | 否 | 表示媒体轨道支持的视频调整模式 | +| sampleRate | ULongRange | 否 | 表示媒体轨道支持的音频采样率范围 | +| sampleSize | ULongRange | 否 | 表示媒体轨道支持的音频样本大小范围 | +| echoCancellation | boolean[] | 否 | 表示媒体轨道是否支持回声消除 | +| autoGainControl | boolean[] | 否 | 表示媒体轨道是否支持自动增益控制 | +| noiseSuppression | boolean[] | 否 | 表示媒体轨道是否支持噪音抑制 | +| latency | DoubleRange | 否 | 表示媒体轨道支持的音频延迟范围 | +| channelCount | ULongRange | 否 | 表示媒体轨道支持的音频通道数量范围 | +| deviceId | string | 否 | 表示媒体轨道所属的设备的唯一标识符 | +| groupId | string | 否 | 表示媒体轨道所属的设备组的唯一标识符 | + +## MediaTrackConstraintSet + +用于定义媒体轨道的约束条件。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|------------------|------------------|-----|------------------------| +| width | ConstrainULong | 否 | 表示媒体轨道需要满足的视频宽度约束条件 | +| height | ConstrainULong | 否 | 表示媒体轨道需要满足的视频高度约束条件 | +| aspectRatio | ConstrainDouble | 否 | 表示媒体轨道需要满足的视频纵横比约束条件 | +| frameRate | ConstrainDouble | 否 | 表示媒体轨道需要满足的帧率约束条件 | +| facingMode | ConstrainString | 否 | 表示媒体轨道需要满足的摄像头朝向模式约束条件 | +| resizeMode | ConstrainString | 否 | 表示媒体轨道需要满足的视频调整模式约束条件 | +| sampleRate | ConstrainULong | 否 | 表示媒体轨道需要满足的音频采样率约束条件 | +| sampleSize | ConstrainULong | 否 | 表示媒体轨道需要满足的音频样本大小约束条件 | +| echoCancellation | ConstrainBoolean | 否 | 表示媒体轨道需要满足的回声消除约束条件 | +| autoGainControl | ConstrainBoolean | 否 | 表示媒体轨道需要满足的自动增益控制约束条件 | +| noiseSuppression | ConstrainBoolean | 否 | 表示媒体轨道需要满足的噪音抑制约束条件 | +| latency | ConstrainDouble | 否 | 表示媒体轨道需要满足的音频延迟约束条件 | +| channelCount | ConstrainULong | 否 | 表示媒体轨道需要满足的音频通道数量约束条件 | +| deviceId | ConstrainString | 否 | 表示媒体轨道需要满足的设备 ID 约束条件 | +| groupId | ConstrainString | 否 | 表示媒体轨道需要满足的设备组 ID 约束条件 | + +## MediaTrackConstraints + +用于定义媒体轨道的约束条件,继承自 MediaTrackConstraintSet。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|----------|---------------------------|-----|------------------| +| advanced | MediaTrackConstraintSet[] | 否 | 用于指定更高级的媒体轨道约束条件 | + +## MediaTrackSettings + +用于描述媒体轨道的设置参数。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|------------------|---------|-----|--------------------| +| width | number | 否 | 表示媒体轨道的视频宽度 | +| height | number | 否 | 表示媒体轨道的视频高度 | +| aspectRatio | number | 否 | 表示媒体轨道的视频纵横比 | +| frameRate | number | 否 | 表示媒体轨道的帧率 | +| facingMode | string | 否 | 表示媒体轨道的摄像头朝向模式 | +| resizeMode | string | 否 | 表示媒体轨道的视频调整模式 | +| sampleRate | number | 否 | 表示媒体轨道的音频采样率 | +| sampleSize | number | 否 | 表示媒体轨道的音频样本大小 | +| echoCancellation | boolean | 否 | 表示媒体轨道是否启用回声消除 | +| autoGainControl | boolean | 否 | 表示媒体轨道是否启用自动增益控制 | +| noiseSuppression | boolean | 否 | 表示媒体轨道是否启用噪音抑制 | +| latency | number | 否 | 表示媒体轨道的音频延迟 | +| channelCount | number | 否 | 表示媒体轨道的音频通道数量 | +| deviceId | string | 否 | 表示媒体轨道所属的设备的唯一标识符 | +| groupId | string | 否 | 表示媒体轨道所属的设备组的唯一标识符 | + +## MediaTrackSupportedConstraints + +用于描述媒体轨道支持的约束条件。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|------------------|---------|-----|-----------------------| +| width | boolean | 否 | 表示媒体轨道是否支持宽度约束条件 | +| height | boolean | 否 | 表示媒体轨道是否支持高度约束条件 | +| aspectRatio | boolean | 否 | 表示媒体轨道是否支持纵横比约束条件 | +| frameRate | boolean | 否 | 表示媒体轨道是否支持帧率约束条件 | +| facingMode | boolean | 否 | 表示媒体轨道是否支持摄像头朝向模式约束条件 | +| resizeMode | boolean | 否 | 表示媒体轨道是否支持视频调整模式约束条件 | +| sampleRate | boolean | 否 | 表示媒体轨道是否支持音频采样率约束条件 | +| sampleSize | boolean | 否 | 表示媒体轨道是否支持音频样本大小约束条件 | +| echoCancellation | boolean | 否 | 表示媒体轨道是否支持回声消除约束条件 | +| autoGainControl | boolean | 否 | 表示媒体轨道是否支持自动增益控制约束条件 | +| noiseSuppression | boolean | 否 | 表示媒体轨道是否支持噪音抑制约束条件 | +| latency | boolean | 否 | 表示媒体轨道是否支持音频延迟约束条件 | +| channelCount | boolean | 否 | 表示媒体轨道是否支持音频通道数量约束条件 | +| deviceId | boolean | 否 | 表示媒体轨道是否支持设备 ID 约束条件 | +| groupId | boolean | 否 | 表示媒体轨道是否支持设备组 ID 约束条件 | + +## MediaStreamConstraints + +用于指示用户在 MediaDevices.getUserMedia() 返回的 MediaStream 中包含哪种类型的 MediaStreamTracks。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|-------|----------|-------------------------------------------------|-----| +| audio | boolean \| [MediaTrackConstraints](#mediatrackconstraints) | 否 | 如果为 true,则请求返回的 MediaStream 包含音频轨道,如果为 false, MediaStream必须不包含音频轨道。如果提供了 MediaTrackConstraints 结构体,将进一步指定视频轨道的性质和设置。 | +| video | boolean \| [MediaTrackConstraints](#mediatrackconstraints) | 否 | 如果为 true,则请求返回的 MediaStream 包含视频轨道,如果为 false, MediaStream必须不包含视频轨道。如果提供了 MediaTrackConstraints 结构体,将进一步指定视频轨道的性质和设置。 | + +## DisplayMediaStreamOptions + +用于指示用户在 MediaDevices.getDisplayMedia() 返回的 MediaStream 中包含哪种类型的 MediaStreamTracks。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|-------|----------|-------------------------------------------------|-----| +| audio | boolean \| [MediaTrackConstraints](#mediatrackconstraints) | 否 | 如果为 true,则请求返回的 MediaStream 包含音频轨道,如果为 false, MediaStream必须不包含音频轨道。如果提供了 MediaTrackConstraints 结构体,将进一步指定视频轨道的性质和设置。 | +| video | boolean \| [MediaTrackConstraints](#mediatrackconstraints) | 否 | 如果为 true,则请求返回的 MediaStream 包含视频轨道,如果为 false, MediaStream必须不包含视频轨道。如果提供了 MediaTrackConstraints 结构体,将进一步指定视频轨道的性质和设置。 | + + +## MediaStreamTrack + +用于描述媒体流轨道(如音频或视频轨道)的基本属性和方法。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|------------|-------------------------------------------------|-----|-----|--------------------------------| +| kind | string | 是 | 否 | 表示媒体流轨道的类型,取值为"source"或"video" | +| id | string | 是 | 否 | 表示媒体流轨道的唯一标识符 | +| enabled | boolean | 是 | 是 | 表示媒体流轨道是否启用 | +| readyState | [MediaStreamTrackState](#mediastreamtrackstate) | 是 | 否 | 表示媒体流轨道的就绪状态 | + +### stop + +stop(): void + +移除所有通过该 MediaStreamTrack 添加到对应 VideoSource 的 VideoSink 实例(如 NativeVideoRenderer)。当对应 VideoSource 的所有 VideoSink 实例被移除后, VideoSource 会自动停止采集。 + +#### 参数 + +无 + +返回值 + +无 + +## MediaStream + +一个媒体流对象,它可以包含一个或多个媒体轨道(如音频轨道和视频轨道),并提供了管理这些轨道的方法和事件处理能力。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|--------|---------|-----|-----|-----------------| +| id | string | 是 | 否 | 表示媒体流的唯一标识符 | +| active | boolean | 是 | 否 | 表示当前媒体流是否处于活动状态 | + +### addTrack + +addTrack(track: MediaStreamTrack): void; + +动态管理和调整音频和视频轨道 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|-------|---------------------------------------|-----|-------------| +| track | [MediaStreamTrack](#mediastreamtrack) | 是 | 要添加的音视频轨道对象 | + +### removeTrack + +removeTrack(track: MediaStreamTrack): void; + +从媒体流中移除指定的 MediaStreamTrack + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|-------|---------------------------------------|-----|-------------| +| track | [MediaStreamTrack](#mediastreamtrack) | 是 | 要移除的音视频轨道对象 | + +### getTrackById + +getTrackById(trackId: string): MediaStreamTrack | null; + +提供的轨道 ID 在当前 MediaStream 对象中查找并返回对应的 MediaStreamTrack 对象 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|--------|-----|------------| +| trackId | string | 是 | 音视频轨道的唯一标识 | + +返回值 + +| 类型 | 说明 | +|---------------------------------------|-------------| +| [MediaStreamTrack](#mediastreamtrack) | 找到指定 ID 的轨道 | +| null | 未找到 | + +### getTracks + +getTracks(): MediaStreamTrack[]; + +获取当前 MediaStream 对象中包含的所有音频和视频轨道(MediaStreamTrack对象)的数组 + +返回值 + +| 类型 | 说明 | +|-----------------------------------------|-----------------| +| [MediaStreamTrack](#mediastreamtrack)[] | 包含当前媒体流中所有轨道的数组 | + +### getAudioTracks + +getAudioTracks(): MediaStreamTrack[]; + +获取当前媒体流 (MediaStream) 中所有的音频轨道 (MediaStreamTrack对象) 组成的数组 + +返回值 + +| 类型 | 说明 | +|-----------------------------------------|-------------------| +| [MediaStreamTrack](#mediastreamtrack)[] | 包含当前媒体流中所有音频轨道的数组 | + +### getVideoTracks + +getVideoTracks(): MediaStreamTrack[]; + +获取当前媒体流 (MediaStream) 中所有的视频轨道 (MediaStreamTrack 对象) 组成的数组 + +返回值 + +| 类型 | 说明 | +|-----------------------------------------|-------------------| +| [MediaStreamTrack](#mediastreamtrack)[] | 包含当前媒体流中所有视频轨道的数组 | + +### clone + +clone(): MediaStream; + +创建当前 MediaStream 对象的一个浅拷贝 + +返回值 + +| 类型 | 说明 | +|-----------------------------|-----------| +| [MediaStream](#mediastream) | 当前媒体流的浅拷贝 | + +## RTCRtpTransceiverInit + +用于在 PeerConnection 中添加 RTCRtpTransceiver ,指定传输方向、关联的媒体流以及发送编码参数。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------------- | ------------------------------------------------------------ |-----------| ---- | -------------------------- | +| direction | [RTCRtpTransceiverDirection](#rtcrtptransceiverdirection) \| undefined | 是 | 是 | 传输器的初始传输方向 | +| streams | [MediaStream](#mediastream)[] \| undefined | 是 | 是 | 与传输器关联的媒体流的数组 | +| sendEncodings | [RTCRtpEncodingParameters](#rtcrtpencodingparameters)[] \| undefined | 是 | 是 | 发送端编码参数的数组 | + +## RTCRtpTransceiver + +包含一个 RTCRtpSender 和一个 RTCRtpReceiver 。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|------------------|------------------------------------|------|-----|--------------| +| mid | string \| null | 是 | 否 | RTP 传输器的标识符 | +| sender | [RTCRtpSender](#rtcrtpsender) | 是 | 否 | 用来发送媒体数据 | +| receiver | [RTCRtpReceiver](#rtcrtpreceiver) | 是 | 否 | 用来接收远端发送的媒体数据 | +| direction | [RTCRtpTransceiverDirection](#rtcrtptransceiverdirection) | 是 | 是 | RTP 传输器的当前传输方向 | +| currentDirection | [RTCRtpTransceiverDirection](#rtcrtptransceiverdirection) \| null | 是 | 否 | 表示传输器当前的操作状态 | + +### stop + +stop(): void; + +停止关联的 RTCRtpSender 和 RTCRtpReceiver 。 + +### setCodecPreferences + +setCodecPreferences(codecs: RTCRtpCodec[]): void; + +配置收发器的首选编解码器列表。 + +参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|---------------------------------------------------|-----|----------| +| codecs | [RTCRtpCodec](#RTCRtpCodec)[] | 是 | 首选编解码器列表 | + +## RTCSessionDescriptionInit + +## RTCSessionDescription + +表示 RTCPeerConnection 中的会话描述信息 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| ---- | ---------- | ---- | ---- | ---- | ---------------------------------------- | +| sdp | string | 是 | 否 | 否 | SDP 描述信息,包含会话的详细配置和参数。 | +| type | RTCSdpType | 是 | 否 | 是 | 描述会话描述的类型 | + + +## RTCIceCandidateInit + +用于初始化ICE候选者的属性集合 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ---------------- | ------ | ---- | --------------------- | +| candidate | string | 否 | ICE 候选者的字符串表示 | +| sdpMLineIndex | number | 否 | SDP 媒体行的索引 | +| sdpMid | string | 否 | SDP 媒体的标识符 | +| usernameFragment | string | 否 | ICE 候选者的用户名片段 | + +## RTCIceCandidate + +描述 ICE 候选者的属性 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| ---------------- | ---------------------- | ---- | ---- | ---- | ---------------------- | +| candidate | string | 是 | 否 | 是 | ICE 候选者的字符串 | +| sdpMid | string | 是 | 否 | 否 | SDP 媒体的标识符 | +| sdpMLineIndex | number | 是 | 否 | 否 | SDP 媒体行的索引 | +| foundation | string | 是 | 否 | 否 | 候选者基础标识符 | +| component | RTCIceComponent | 是 | 否 | 否 | 候选者的组件 | +| priority | number | 是 | 否 | 否 | 候选者的优先级 | +| address | string | 是 | 否 | 否 | 候选者的 IP 地址 | +| protocol | RTCIceProtocol | 是 | 否 | 否 | 候选者的协议类型 | +| port | number | 是 | 否 | 否 | 候选者的端口号 | +| type | RTCIceCandidateType | 是 | 否 | 否 | 候选者的类型 | +| tcpType | RTCIceTcpCandidateType | 是 | 否 | 否 | TCP 候选者的类型 | +| relatedAddress | string | 是 | 否 | 否 | 相关候选者的地址 | +| relatedPort | number | 是 | 否 | 否 | 相关候选者的端口号 | +| usernameFragment | string | 是 | 否 | 否 | ICE 候选者的用户名片段 | + +### toJSON + +toJSON(): RTCIceCandidateInit; + +通常用于将 RTCIceCandidate 对象转换为 JSON 兼容格式 + +返回值: + +| 类型 | 说明 | +| ------------------- | ------------------------------------------------------------ | +| RTCIceCandidateInit | 返回一个 RTCIceCandidateInit 对象,表示 RTCIceCandidate 在 JSON 兼容格式下的表示。 | + + +## RTCDataChannelInit + +可以根据需要定义数据通道的特性和行为 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| ----------------- | ------- | ---- | ---- | +| ordered | boolean | 否 | 是否消息按顺序传递 | +| maxPacketLifeTime | number | 否 | 数据包的最大生命周期 | +| maxRetransmits | number | 否 | 最大重传次数 | +| protocol | string | 否 | 数据通道使用的协议 | +| negotiated | boolean | 否 | 数据通道是否由应用程序进行协商 | +| id | number | 否 | 数据通道的 ID | + + +## RTCDataChannel + +RTCPeerConnection 的数据通道 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| -------------------------- | ---------------- | ---- | ---- | -------------------------------------------------------- | +| label | string | 是 | 否 | 数据通道的标签 | +| ordered | boolean | 是 | 否 | 是否消息按顺序传递 | +| maxPacketLifeTime | number | 是 | 否 | 数据包的最大生命周期 | +| maxRetransmits | number | 是 | 否 | 最大重传次数 | +| protocol | string | 是 | 否 | 数据通道使用的协议 | +| negotiated | boolean | 是 | 否 | 数据通道是否由应用程序进行协商 | +| id | number | 是 | 否 | 数据通道的 ID | +| readyState | DataChannelState | 是 | 否 | 数据通道的当前状态 | +| bufferedAmount | number | 是 | 否 | 已发送但未确认的字节数 | +| bufferedAmountLowThreshold | number | 是 | 是 | 当 bufferedAmount 低于该值时,将触发 bufferedamountlow 事件 | +| binaryType | BinaryType | 是 | 是 | 指定二进制数据的类型(当前未使用) | +| onbufferedamountlow | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 bufferedamountlow | +| onclose | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 close | +| onclosing | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 closing | +| onopen | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 open | +| onmessage | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 message | +| onerror | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 error | + +### 事件 + +| 名称 | 接口 | 说明 | +| ----------------- | --------------- | ------------------------------------------------------------ | +| bufferedamountlow | Event | 当 RTCDataChannel 对象的 bufferedAmount 从高于其 bufferedAmountLowThreshold 降低到小于或等于其 bufferedAmountLowThreshold 时触发 | +| close | Event | 当 RTCDataChannel 对象的底层数据传输已经关闭时触发 | +| closing | Event | 当 RTCDataChannel 对象转换到 closing 状态时触发 | +| open | Event | 当 RTCDataChannel 对象的底层数据传输已经建立或重新建立时触发 | +| message | MessageEvent | 当成功接收到消息时触发(data为string或ArrayBuffer) | +| error | RTCErrorEvent | 当 RTCDataChannel 对象发生错误时触发 | + +### close + +close(): void; + +关闭数据通道 + +### send(string) + +send(data: string): void; + +发送数据。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ------ | ---- | ---- | +| data | string | 是 | 数据 | + +### send(buffer.Buffer) + +send(data: buffer.Buffer): void; + +发送数据。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ------------- | ---- | ---- | +| data | buffer.Buffer | 是 | 数据 | + +### send(ArrayBuffer) + +send(data: ArrayBuffer): void; + +发送数据。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ----------- | ---- | ---- | +| data | ArrayBuffer | 是 | 数据 | + +## RTCRtpReceiver + +用于表示 RTP 流的接收端点,允许应用程序通过网络接收媒体数据(音频或视频)。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-----------|----------------------------------------|------|-----|---------------------------| +| track | [MediaStreamTrack](#mediastreamtrack) | 是 | 否 | 表示与 RTCRtpReceiver 关联的媒体轨道 | +| transport | [RTCDtlsTransport](#rtcdtlstransport) \| null | 是 | 否 | 用于接收媒体数据的传输机制 | + +### getParameters + +getParameters(): RTCRtpReceiveParameters + +用于获取当前 RTCRtpReceiver 实例的接收参数 RTCRtpReceiveParameters。 + +返回值: + +| 类型 | 说明 | +|-----------------------------------------------------|----------------------------------------------------------------------------------------| +| [RTCRtpReceiveParameters](#rtcrtpreceiveparameters) | 返回一个 RTCRtpReceiveParameters 对象,该对象描述了当前 RTCRtpReceiver 实例的接收参数,包括编解码器、头部扩展和 RTCP 参数等。 | + +### getContributingSources + +getContributingSources(): RTCRtpContributingSource[]; + +为该接收器在过去10秒内接收到的每个唯一 CSRC 标识符返回一个 RTCRtpContributingSource ,按时间戳降序排列。 + +返回值 + +| 类型 | 说明 | +|-------------------------------------------------------|----------------| +| [RTCRtpContributingSource](#rtcrtpcontributingsource) | 包含关于 CSRC 的信息。 | + +### getSynchronizationSources + +getSynchronizationSources(): RTCRtpSynchronizationSource[]; + +为该接收器在过去10秒内接收到的每个唯一 SSRC 标识符返回一个 RTCRtpSynchronizationSource ,按时间戳降序排列。 + +返回值 + +| 类型 | 说明 | +|-------------------------------------------------------------|----------------| +| [RTCRtpSynchronizationSource](#rtcrtpsynchronizationsource) | 包含关于 SSRC 的信息。 | + +### getStats + +getStats(): Promise + +用于获取与该接收器相关的统计信息。 + +返回值 + +| 类型 | 说明 | +|---------------------------------------------|----------------| +| Promise\<[RTCStatsReport](#rtcstatsreport)> | 包含与接收器相关的统计信息。 | + +### getCapabilities + +static getCapabilities(kind: string): RTCRtpCapabilities | null + +获取特定媒体类型的 RTP 能力。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +|------|--------|-----|--------------------------| +| kind | string | 是 | 表示媒体类型,"audio" 或 "video" | + +返回值 + +| 类型 | 说明 | +|-------------------------------------------|----------------------------------| +| [RTCRtpCapabilities](#rtcrtpcapabilities) | 包含了对指定媒体类型的 RTP 支持的编解码器和 RTP 头扩展 | +| null | 没有支持的媒体类型 | + +## RTCRtpCodingParameters + +定义了用于配置 RTP 编码参数的结构。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|-----|----------------------|-----------|-----|--------| +| rid | string \| undefined | 是 | 是 | RTP 流的唯一标识符 | + +## RTCRtpEncodingParameters + +用于配置 RTP 编码参数的属性。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|-----------------------|----------|-----|---------------|-----| +| active | boolean \| undefined | 是 | 是 | 表示编码是否处于活动状态 | +| maxBitrate | number \| undefined | 是 | 是 | 表示编码的最大比特率 | +| maxFramerate | number \| undefined | 是 | 是 | 表示编码的最大帧率 | +| scaleResolutionDownBy | number \| undefined | 是 | 是 | 表示对编码分辨率的缩放比例 | + +## RTCRtpCodecParameters + +用于描述媒体流的编解码格式。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|-------------|--------|-----|-----|---------------------| +| clockRate | number | 是 | 是 | 表示编解码器的时钟速率,以赫兹为单位 | +| channels | number \| undefined | 是 | 是 | 表示音频编解码器的通道数 | +| mimeType | string | 是 | 是 | 表示编解码器的 SDP 格式化参数行 | +| sdpFmtpLine | string | 是 | 是 | 表示对编码分辨率的缩放比例 | +| payloadType | number | 是 | 是 | 表示编解码器的 RTP 载荷类型 | + +## RTCRtpHeaderExtensionParameters + +描述 RTP(实时传输协议)头部扩展的参数。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +| --------- | ------- | ---- | --------|---------------------------------| +| id | number | 是 | 是 | 表示 RTP 头部扩展的标识符(ID) | +| uri | string | 是 | 是 | 表示 RTP 头部扩展的统一资源标识符(URI) | +| encrypted | boolean \| undefined | 是 | 是 | 表示 RTP 头部扩展是否加密 | + +## RTCRtcpParameters + +述 RTCP(实时传输控制协议)的参数。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +| ----------- | ------- | ---- | ------|-----------------------| +| cname | string \| undefined | 是 | 是 | 表示 RTCP 的 CNAME | +| reducedSize | boolean \| undefined | 是 | 是 | 表示是否启用 RTCP 小封包模式 | + +## RTCRtpParameters + +描述 RTP 的参数,包括编解码器参数、头部扩展参数和 RTCP 参数。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|------------------|-----------------------------------------------------------------------|-----|-----|--------------------| +| codecs | [RTCRtpCodecParameters](#rtcrtpcodecparameters)[] | 是 | 是 | 表示 RTP 使用的编解码器参数数组 | +| headerExtensions | [RTCRtpHeaderExtensionParameters](#rtcrtpheaderextensionparameters)[] | 是 | 是 | 表示 RTP 使用的头部扩展参数数组 | +| rtcp | [RTCRtcpParameters](#rtcrtpparameters) | 是 | 是 | 表示 RTP 使用的 RTCP 参数 | + +## RTCRtpSendParameters + +发送 RTP 数据的特定参数。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|---------------|---------------------------------------------------------|-----|-----|----------------------| +| encodings | [RTCRtpEncodingParameters](#rtcrtpencodingparameters)[] | 是 | 是 | 描述了发送 RTP 数据时的编码参数数组 | +| transactionId | string | 是 | 是 | 用于唯一标识发送参数配置的事务 ID | + +## RTCRtpReceiveParameters + +描述接收 RTP 数据时的参数(当前为空)。 + +## RTCRtpContributingSource + +包含 CSRC 信息。 + +### 属性 + +| 参数名 | 类型 | 可读 | 可写 | 说明 | +|--------------|------------------|-----|-------|-----------------------------------------------------| +| timestamp | HighResTimeStamp | 是 | 是 | 表示 RTP 数据包中的帧从这个源发送到 RTCRtpReceiver 的 MediaStreamTrack 的最近时间 | +| source | string | 是 | 是 | CSRC 或 SSRC | +| audioLevel | string \| undefined | 是 | 是 | 仅适用于音频接收器 | +| rtpTimestamp | string | 是 | 是 | RTP 时间戳 | + + +## RTCRtpSynchronizationSource + +包含 SSRC 信息,继承自 RTCRtpContributingSource(当前为空)。 + +## RTCRtpCodec + +描述了在 RTCPeerConnection 中使用的 RTP 编解码器的能力和参数 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ----------- | ------ | ---- |------|----------------------------------------------------| +| mimeType | string | 是 | 是 | 表示编解码器的媒体类型和子类型 | +| clockRate | number | 是 | 是 | 表示编解码器的时钟速率,单位为 Hz | +| channels | number \| undefined | 是 | 是 | 表示音频编解码器的声道数目 | +| sdpFmtpLine | string \| undefined | 是 | 是 | 表示编解码器的 SDP 属性行,用于指定编解码器的额外参数和配置 | + +## RTCRtpHeaderExtensionCapability + +描述在 RTCPeerConnection 中使用的 RTP 头扩展的能力和参数 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-----|--------|-----|-----|--------------------| +| uri | string | 是 | 是 | 表示 RTP 头扩展的标识符 URI | + +## RTCRtpCapabilities + +描述在 RTCPeerConnection 中支持的 RTP 能力,包括支持的编解码器和头扩展能力。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|------------------|-------------------------------------------------------------|-----|-----|--------------------| +| codecs | [RTCRtpCodec](#rtcrtpcodec)[] | 是 | 是 | 表示支持的 RTP 编解码器能力列表 | +| headerExtensions | [RTCRtpHeaderExtensionCapability](#rtcrtpheaderextensioncapability)[] | 是 | 是 | 表示支持的 RTP 头扩展能力列表 | + +## RTCDTMFSender + +发送 DTMF 信号 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------------- | --------------------- | ---- | ---- | -------------------------------------------- | +| canInsertDTMF | boolean | 是 | 否 | 表示当前是否可以插入 DTMF 信号 | +| toneBuffer | string | 是 | 否 | 表示当前在发送队列中等待发送的 DTMF 信号音调 | +| ontonechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为tonechange | + +### 事件 + +| 名称 | 接口 | 说明 | +| ------------- | ---------------------- | ------------------------------------------------------------ | +| ontonechange | RTCDTMFToneChangeEvent | 当 RTCDTMFSender 对象要么刚刚开始播放音调,要么刚刚结束 toneBuffer 中音调的播放时触发 | + +### insertDTMF + +insertDTMF(tones: string, duration?: number, interToneGap?: number): void; + +在 RTCPeerConnection 中向发送队列中插入 DTMF(双音多频)信号 + +参数 + +| 名称 | 类型 | 必填 | 说明 | +| ------------ | ------ | ---- | ------------------------------ | +| tones | string | 是 | 表示要发送的 DTMF 信号音调序列 | +| duration | number | 否 | 表示每个音调的持续时间 | +| interToneGap | number | 否 | 表示音调之间的间隔时间 | + +## RTCRtpSender + +用于发送 RTP 数据的对象的属性和方法 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-----------|----------------------------------|------|---|-----| +| track | [MediaStreamTrack](#mediastreamtrack) \| null | 是 | 否 | 表示当前与该 RTP 发送器关联的 MediaStreamTrack 对象 | +| transport | [RTCDtlsTransport](#rtcdtlstransport) \| null | 是 | 否 | 表示可选的 RTCDtlsTransport 对象,用于指定 RTP 数据的传输 | +| dtmf | [RTCDTMFSender](#RTCDTMFSender) \| null | 是 | 否 | 表示可选的 RTCDTMFSender 对象,用于发送 DTMF(双音多频)信号 | + +### setParameters + +setParameters(parameters: RTCRtpSendParameters): Promise; + +设置当前 RTP 发送器的参数 + +参数 + +| 名称 | 类型 | 必填 | 说明 | +|------------|-----------------------------------------------|-----|--------------------------------| +| parameters | [RTCRtpSendParameters](#RTCRtpSendParameters) | 是 | 包含了发送器的编解码器、头部扩展和 RTCP 相关参数等信息 | + +返回值 + +| 类型 | 说明 | +|----------------|--------------| +| Promise\ | 当设置操作成功完成时解析 | + +### getParameters + +getParameters(): RTCRtpSendParameters; + +获取当前 RTP 发送器的参数 + +返回值 + +| 类型 | 说明 | +|-----------------------------------------------|----------------------------------------------| +| [RTCRtpSendParameters](#RTCRtpSendParameters) | 返回一个 RTCRtpSendParameters 对象,包含当前 RTP 发送器的参数 | + +### replaceTrack + +replaceTrack(withTrack: MediaStreamTrack | null): Promise; + +用于替换当前与发送器关联的媒体轨道 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +|-----------|-----------------------------------------|------|-----------------| +| withTrack | [MediaStreamTrack](#mediastreamtrack) \| null | 否 | 用于替换当前与发送器关联的轨道 | + +返回值 + +| 类型 | 说明 | +|----------------|-------------------------------------| +| Promise\ | 当替换操作成功完成时解析;如果替换操作失败,则会拒绝并返回相应的错误。 | + +### setStreams + +setStreams(...streams: MediaStream[]): void; + +用于将一个或多个 MediaStream 对象与该发送器关联 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +|---------|-------------------------------|--|----------------------------------------------------------| +| streams | [MediaStream](#mediastream)[] | 否 | 不定数量的 MediaStream 对象,用于关联到该发送器。可以传递一个或多个 MediaStream 对象。 | + +### getStats + +getStats(): Promise; + +用于获取与该发送器相关的统计信息 + +返回值 + +| 类型 | 说明 | +|---------------------------------------------|---------------| +| Promise\<[RTCStatsReport](#rtcstatsreport)> | 包含与发送器相关的统计信息 | + +### getCapabilities + +static getCapabilities(kind: string): RTCRtpCapabilities | null; + +获取特定媒体类型的 RTP 能力 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +|------|--------|-----|--------------------------| +| kind | string | 是 | 表示媒体类型,"audio" 或 "video" | + +返回值 + +| 类型 | 说明 | +|-------------------------------------------|----------------------------------| +| [RTCRtpCapabilities](#rtcrtpcapabilities) | 包含了对指定媒体类型的 RTP 支持的编解码器和 RTP 头扩展 | +| null | 没有支持的媒体类型 | + + +## RTCDtlsTransport + +用于管理 DTLS 连接。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------------- |-------------------------------------------------|--------------------------------| ---- | ----------------------------------- | +| iceTransport | [RTCIceTransport](#rtcicetransport) | 是 | 否 | 用于建立和管理底层传输的ICE传输对象 | +| state | [RTCDtlsTransportState](#rtcdtlstransportstate) | 是 | 否 | 表示当前的 DTLS 传输状态 | +| onstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 statechange | +| onerror | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 error | + +### 事件 + +| 名称 | 接口 | 说明 | +|-------------|---------------|------------------------------| +| statechange | Event | 当 RTCDtlsTransport 状态改变时触发 | +| error | RTCErrorEvent | 当在 RTCDtlsTransport 上发生错误时触发 | + +### getRemoteCertificates + +getRemoteCertificates(): ArrayBuffer[]; + +获取远程对等端提供的证书。 + +返回值: + +| 类型 | 说明 | +|---------------|--------------------------| +| ArrayBuffer[] | 每一个 ArrayBuffer 表示一个远程证书 | + +## RTCDtlsFingerprint + +用于描述 DTLS(Datagram Transport Layer Security)的指纹信息。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +| --------- | ------ | ---- | -------------------------------------------- | +| algorithm | string | 否 | 指定了用于计算指纹的算法 | +| value | string | 否 | 包含了实际的指纹值,是一个经过计算的唯一标识 | + +## RTCCertificate + +用于描述 RTCPeerConnection 中的证书信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ------- | -------------- | ---- | ---- | ---------------------------------------------- | +| expires | EpochTimeStamp | 是 | 否 | 指定了证书的过期时间,即证书将在此时间之后失效 | + +### getFingerprints + +getFingerprints(): RTCDtlsFingerprint[]; + +用于获取与当前 `RTCCertificate` 相关联的 DTLS 指纹信息数组。 + +返回值 + +| 类型 | 说明 | +| -------------------- | ------------------------------------------------------------ | +| RTCDtlsFingerprint[] | 包含多个 RTCDtlsFingerprint 对象,每个对象描述了证书的一个 DTLS 指纹信息。 | + + +## RTCIceCandidatePair + +表示 ICE 候选者对。 + +### 属性 + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | --------------- | ---- | --------------- | +| local | RTCIceCandidate | 否 | 本地 ICE 候选者 | +| remote | RTCIceCandidate | 否 | 远程 ICE 候选者 | + +## RTCIceParameters + +示 ICE 连接过程中使用的参数。 + +### 属性 + +| 参数名 | 类型 | 必填 | 说明 | +| ---------------- | ------ | ---- | -------------- | +| usernameFragment | string | 是 | ICE 用户名片段 | +| password | string | 是 | ICE 密码 | + +## RTCIceTransport + +表示 ICE 传输过程中的相关属性和方法,管理和监控 ICE 连接的状态,并提供了一些方法来获取本地和远程的候选者以及其他它接参数。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| -------------- | -------------------- | ---- | ---- | ------------------------ | +| role | RTCIceRole | 是 | 否 | 表示 ICE 角色 | +| component | RTCIceComponent | 是 | 否 | 表示 ICE 组件类型 | +| state | RTCIceTransportState | 是 | 否 | 表示当前的 ICE 传输状态 | +| gatheringState | RTCIceGathererState | 是 | 否 | 表示当前的候选者收集状态 | +| onstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 statechange | +| ongatheringstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 gatheringstatechange | +| onselectedcandidatepairchange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 selectedcandidatepairchange | + +### 事件 + +| 名称 | 接口 | 说明 | +| --------------------------- | ----- | ------------------------------------------------------------ | +| statechange | Event | 当 RTCIceTransport 状态改变时触发 | +| gatheringstatechange | Event | 当 RTCIceTransport 的收集状态改变时触发 | +| selectedcandidatepairchange | Event | 当 RTCIceTransport 选择的候选对改变时触发 | + +### getLocalCandidates + +getLocalCandidates(): RTCIceCandidate[] + +返回值: + +| 类型 | 说明 | +| ----------------- | ------------------------------------------- | +| RTCIceCandidate[] | 返回一个数组,包含本地收集到的 ICE 候选者。 | + +### getLocalParameters + +getLocalParameters(): RTCIceParameters | null; + +返回值: + +| 类型 | 说明 | +| ------------------------ | ------------------------------------------------------------ | +| RTCIceParameters \| null | 返回本地的 ICE 参数(usernameFragment和 password),如果没有可用的参数则返回 null。 | + +### getRemoteCandidates + +getRemoteCandidates(): RTCIceCandidate[] + +返回值: + +| 类型 | 说明 | +| ----------------- | ------------------------------------------- | +| RTCIceCandidate[] | 返回一个数组,包含远程对等方的 ICE 候选者。 | + +### getRemoteParameters + +getRemoteParameters(): RTCIceParameters | null; + +返回值: + +| 类型 | 说明 | +| ------------------------ | ------------------------------------------------------------ | +| RTCIceParameters \| null | 返回远程对等方的 ICE 参数(usernameFragment和password),如果没有可用的参数则返回null | + +### getSelectedCandidatePair + +getSelectedCandidatePair(): RTCIceCandidatePair | null; + +返回值: + +| 类型 | 说明 | +| --------------------------- | ------------------------------------------------------------ | +| RTCIceCandidatePair \| null | 返回当前选择的 ICE 候选者对,如果没有可用的候选者对则返回 null | + +## RTCSctpTransport + +用于 SCTP 数据传输的属性和关联的 DTLS 传输对象。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|----------------|-------------------------------------------------|-----|-----|-------------------------| +| maxChannels | number | 是 | 否 | SCTP 传输的最大通道数 | +| maxMessageSize | number | 是 | 否 | SCTP 传输的最大消息大小 | +| state | [RTCSctpTransportState](#rtcsctptransportstate) | 是 | 否 | SCTP 传输的当前状态 | +| transport | [RTCDtlsTransport](#rtcdtlstransport) | 是 | 否 | 用于传输 SCTP 数据的 DTLS 传输对象 | + +## RTCStats + +统计数据的基本结构,包括时间戳、统计类型和统计数据的唯一标识符。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| --------- | ---------------- | ---- | ---- | ---------------------------------------- | +| timestamp | HighResTimeStamp | 是 | 是 | 统计数据的时间戳 | +| type | RTCStatsType | 是 | 是 | 统计数据的类型,指示这组数据属于哪个类别 | +| id | string | 是 | 是 | 统计数据的唯一标识符 | + +## RTCStatsReport + +表示一组 RTCPeerConnection 的统计信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ----- | --------------------- | ---- | ---- | -------------------------------------------- | +| stats | Map | 是 | 否 | 包含有关 RTCPeerConnection 会话或连接特定方面的详细信息 | + +## RTCTransportStats + +描述 RTCPeerConnection 传输统计数据的结构。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| ---------------------------- | --------------------- | ---- | ---- | ---- | ----------------------------------------------------- | +| packetsSent | number | 是 | 是 | 否 | 发送的数据包数量 | +| packetsReceived | number | 是 | 是 | 否 | 接收的数据包数量 | +| bytesSent | number | 是 | 是 | 否 | 发送的字节数 | +| bytesReceived | number | 是 | 是 | 否 | 接收的字节数 | +| iceRole | RTCIceRole | 是 | 是 | 否 | ICE 连接的角色 | +| iceLocalUsernameFragment | string | 是 | 是 | 否 | ICE 本地用户名片段 | +| dtlsState | RTCDtlsTransportState | 是 | 是 | 是 | DTLS 连接的状态 | +| iceState | RTCIceTransportState | 是 | 是 | 否 | ICE 连接的状态 | +| selectedCandidatePairId | string | 是 | 是 | 否 | 选定的 ICE 候选对的唯一标识符 | +| localCertificateId | string | 是 | 是 | 否 | 本地证书的唯一标识符 | +| remoteCertificateId | string | 是 | 是 | 否 | 远端证书的唯一标识符 | +| tlsVersion | string | 是 | 是 | 否 | TLS 使用的版本号 | +| dtlsCipher | string | 是 | 是 | 否 | DTLS 使用的加密算法 | +| srtpCipher | string | 是 | 是 | 否 | SRTP 使用的加密算法 | +| selectedCandidatePairChanges | number | 是 | 是 | 是 | 选定的 ICE 候选对的更改次数,用于跟踪候选对的变化情况 | + +## RTCRtpStreamStats + +描述 RTCPeerConnection RTP 流的统计数据结构,包括流的基本信息和相关的统计指标。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| ----------- | ------ | ---- | ---- | ---- | ---------------------- | +| ssrc | number | 是 | 是 | 是 | RTP 流的同步信源标识符 | +| kind | string | 是 | 是 | 是 | 流的类型或媒体类型 | +| transportId | string | 是 | 是 | 否 | 传输的唯一标识符 | +| codecId | string | 是 | 是 | 否 | 编解码器的唯一标识符 | + +## RTCIceCandidatePairStats + +描述 ICE 候选对的统计信息,包括传输的状态、数据包和字节的发送接收情况、往返时延等关键指标。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 必填 | 说明 | +| --------------------------- | ----------------------------- | ---- | ---- | ---- | ------------------------------------------------------------ | +| transportId | string | 是 | 是 | 是 | ICE 候选对所使用的传输 ID,唯一标识该 ICE 候选对所属的传输通道 | +| localCandidateId | string | 是 | 是 | 是 | 表示本地 ICE 候选 ID,用于标识本地候选。 | +| remoteCandidateId | string | 是 | 是 | 是 | 表示远端 ICE 候选 ID,用于标识远端候选。 | +| state | RTCStatsIceCandidatePairState | 是 | 是 | 是 | 表示 ICE 候选对的状态 | +| nominated | boolean | 是 | 是 | 否 | 表示是否已经被选定作为当前通信的候选对。 | +| packetsSent | number | 是 | 是 | 否 | 表示从本地候选发送的数据包数量。 | +| packetsReceived | number | 是 | 是 | 否 | 表示接收到的数据包数量。 | +| bytesSent | number | 是 | 是 | 否 | 表示从本地候选发送的总字节数。 | +| bytesReceived | number | 是 | 是 | 否 | 表示接收到的总字节数。 | +| lastPacketSentTimestamp | HighResTimeStamp | 是 | 是 | 否 | 表示最后一个数据包从本地候选发送的时间戳。 | +| lastPacketReceivedTimestamp | HighResTimeStamp | 是 | 是 | 否 | 表示最后一个数据包接收到的时间戳。 | +| totalRoundTripTime | number | 是 | 是 | 否 | 表示总的往返时延,以毫秒为单位。 | +| currentRoundTripTime | number | 是 | 是 | 否 | 表示当前的往返时延,以毫秒为单位。 | +| availableOutgoingBitrate | number | 是 | 是 | 否 | 表示可用的出口比特率,以比特每秒(bps)为单位。 | +| availableIncomingBitrate | number | 是 | 是 | 否 | 表示可用的入口比特率,以比特每秒(bps)为单位。 | +| requestsReceived | number | 是 | 是 | 否 | 表示接收到的请求数量。 | +| requestsSent | number | 是 | 是 | 否 | 表示发送的请求数量。 | +| responsesReceived | number | 是 | 是 | 否 | 表示接收到的响应数量。 | +| responsesSent | number | 是 | 是 | 否 | 表示发送的响应数量。 | +| consentRequestsSent | number | 是 | 是 | 否 | 表示发送的同意请求数量。 | +| packetsDiscardedOnSend | number | 是 | 是 | 否 | 表示在发送过程中丢弃的数据包数量。 | +| bytesDiscardedOnSend | number | 是 | 是 | 否 | 表示在发送过程中丢弃的总字节数。 | + +## MediaSourceState + +描述媒体源(如音频或视频源)的状态。 + +### 属性 + +| 名称 | 类型 | 说明 | +| ------------ | ------ | -------------------------------------------------------- | +| initializing | string | 表示媒体源正在初始化,即将开始获取数据但尚未开始播放。 | +| live | string | 表示媒体源处于活动状态,正在正常播放中 | +| ended | string | 表示媒体源已经结束,不再提供新的数据。 | +| muted | string | 表示媒体源被静音,即便仍在活动状态,但无法正常播放声音。 | + +## MediaSource + +表示一个媒体源(如音频或视频源)。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-------|------------------|----|----|---------------| +| state | MediaSourceState | 是 | 否 | 表示了媒体源处于的不同状态 | + +## AudioSource + +继承自MediaSource,表示一个音频源。 + +### setVolume + +setVolume(volume: number); + +设置音频源的音量。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| -------- | -------- | ---- | ------------------------------------------------------ | +| volume | number | 是 | 音量 | + +## AudioTrack + +继承自 MediaStreamTrack,扩展音频轨道特性。 + + +## VideoSource + +继承自MediaSource,表示一个视频源。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +| ----- | ---------------- | ---- |----| -------------------------- | +| oncapturerstarted | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 capturerstarted | +| oncapturerstopped | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 capturerstopped | + +### 事件 + +| 名称 | 接口 | 说明 | +|-----------------|---------------------------|--------------------| +| capturerstarted | VideoCapturerStartedEvent | 该视频源开始采集(成功或失败)时触发 | +| capturerstopped | Event | 视频源停止采集时触发 | + +## VideoTrack + +继承自 MediaStreamTrack,表示一个视频轨道,配合VideoRenderController使用可实现视频的渲染。 + +## PeerConnectionFactory + +用于创建和管理 RTCPeerConnection 相关的对象。 + +### 构造函数 + +new(options?: PeerConnectionFactoryOptions): PeerConnectionFactory + +用于创建 PeerConnectionFactory 变量实例。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|---------|------------------------------|-----|----------------------------| +| options | PeerConnectionFactoryOptions | 否 | 创建PeerConnectionFactory的参数 | + +#### 返回值 + +| 类型 | 说明 | +|-----------------------|-------------------------| +| PeerConnectionFactory | PeerConnectionFactory实例 | + +### getDefault + +static getDefault(): PeerConnectionFactory + +用于获取默认的 PeerConnectionFactory 实例,默认实例可通过 setDefault 设置。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|-----------------------|-------------------------| +| PeerConnectionFactory | PeerConnectionFactory实例 | + +### setDefault + +static setDefault(factory: PeerConnectionFactory): void + +用于设置默认的 PeerConnectionFactory 实例,可以供其它模块获取使用,如 MediaDevices 的 C++ 层代码。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|---------|-----------------------|----|---------------------------| +| factory | PeerConnectionFactory | 是 | 默认的 PeerConnectionFactory | + +#### 返回值 + +无 + +### createPeerConnection + +createPeerConnection(config: RTCConfiguration): RTCPeerConnection; + +根据指定配置信息创建 RTCPeerConnection 对象。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------------------|-----|-------------------------| +| config | RTCConfiguration | 是 | 一个包含配置信息的对象,用于配置对等连接的行为 | + +返回值: + +| 类型 | 说明 | +|-------------------|--------------------------------------------------------| +| RTCPeerConnection | 表示一个 RTCPeerConnection 对象,用于在本地和远程对等端之间传输流数据、媒体和任何其它消息 | + +### createAudioSource + +createAudioSource(constraints?: MediaTrackConstraints): AudioSource; + +用于根据指定的媒体约束创建一个音频源对象。 + +| 参数名 | 类型 | 必填 | 说明 | +|-------------|-------------------------------------------------|-----|----------------------------------------------------------------------------------------------------------------------------------| +| constraints | [MediaTrackConstraints](#mediatrackconstraints) | 否 | 一个包含媒体约束条件的对象,用于配置音频源的行为。当前仅支持 echoCancellation、autoGainControl、noiseSuppression。
参阅 [AudioDeviceModule](#audiodevicemodule) | + +返回值: + +| 类型 | 说明 | +|-----------------------------|------------------------| +| [AudioSource](#audiosource) | 表示一个音频源对象,用于捕获和处理音频输入流 | + +> **说明:** +> 使用麦克风需要申请 ohos.permission.MICROPHONE 权限,具体请查阅相关文档。 + +### createAudioTrack + +createAudioTrack(id: string, source: AudioSource): AudioTrack; + +用于根据指定的音频源创建一个音频轨道对象。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|-----------------------------|-----|---------------------| +| id | string | 是 | 表示创建的音频轨道的唯一标识符或 ID | +| source | [AudioSource](#audiosource) | 是 | 表示用于音频输入的音频源对象 | + +返回值: + +| 类型 | 说明 | +|------------|-----------------------------| +| AudioTrack | 表示一个音频轨道对象,用于管理和处理音频流的发送或接收 | + +### createVideoSource + +createVideoSource(constraints?: MediaTrackConstraints, isScreencast?: boolean): VideoSource; + +用于根据指定的媒体约束创建一个视频源对象。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------------|-------------------------------------------------|----|----------------------------------------------------------------------------------------------------------------------------------------------------| +| constraints | [MediaTrackConstraints](#mediatrackconstraints) | 否 | 一个包含视频媒体约束条件的对象,用于指定期望的视频源,如设备ID、分辨率等。 | +| isScreencast | boolean | 否 | 是否是屏幕录像,true:使用屏幕录像作为视频源(当前仅支持 width、height约束条件,且不支持 advanced 属性);false:使用相机作为视频源(当前支持 width、height、aspectRatio、framRate、facingMode、deviceId约束条件)。 | + +#### 返回值 + +| 类型 | 说明 | +|-----------------------------|------------------------| +| [VideoSource](#videosource) | 表示一个视频源对象,用于捕获和处理视频输入流 | + +#### 示例 + +```typescript +this.localVideoSource = this.peerConnectionFactory.createVideoSource({width: 320, height: 240, frameRate:{min:15, max:30, ideal:25}}); +this.localVideoSource!.oncapturerstarted = (event: webrtc.VideoCapturerStartedEvent) => { + console.info(TAG, 'oncapturerstarted: ' + event.success ? "success" : "failed"); +}; +this.localVideoSource!.oncapturerstopped = (event: webrtc.Event) => { + console.info(TAG, 'oncapturerstopped'); +}; +``` + +> **说明:** +> 使用相机需要申请 ohos.permission.CAMERA 权限,屏幕录像需要申请 ohos.permission.CAPTURE_SCREEN 权限,具体请查阅相关文档。 + +### createVideoTrack + +createVideoTrack(id: string, source: VideoSource): VideoTrack; + +用于根据指定的视频源创建一个视频轨道对象。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| ------ |-------------| ---- | ----------------------------------------- | +| id | string | 是 | 表示创建的视频轨道的唯一标识符或 ID | +| source | [VideoSource](#videosource) | 是 | 表示用于视频输入的视频源对象 | + +返回值: + +| 类型 | 说明 | +| ---------- | ------------------------------------------------------ | +| AudioTrack | 表示一个音频轨道对象,用于管理和处理音频流的发送或接收 | + +### startAecDump + +startAecDump(fd: number, max_size_bytes: number): boolean; + +用于启动音频回声消除 (AEC) 的转储功能,将音频数据写入指定的文件描述符 (`fd`) 中,并限制最大文件大小 (`max_size_bytes`)。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +| -------------- | ------ | ---- | ------------------------------------------------------------ | +| fd | number | 是 | 表示一个文件描述符或标识符,用于指定将音频数据写入的文件 | +| max_size_bytes | number | 是 | 表示最大文件大小,以字节为单位,用于限制音频数据写入文件的大小 | + +返回值: + +| 类型 | 说明 | +| ------- | ------------------------------------------------------------ | +| boolean | 表示启动操作的结果,返回 true 表示成功启动音频回声消除的转储功能,返回 false 表示启动失败。 | + +## PeerConnectionFactoryOptions + +创建PeerConnectionFactory的参数。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|------------------------|---------------------|-----|----------| +| adm | AudioDeviceModule | 否 | 音频设备管理模块 | +| videoEncoderFactory | VideoEncoderFactory | 否 | 视频编码器工厂 | +| videoDecoderFactory | AudioDeviceModule | 否 | 视频解码器工厂 | +| audioProcessingFactory | AudioDeviceModule | 否 | 音频处理工厂 | + +## AudioDeviceModule + +音频设备模块接口。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|------------------------|---------------------|------|-----|-----| +| oncapturererror | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 capturererror | +| oncapturerstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 capturerstatechange | +| oncapturersamplesready | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 capturersamplesready | +| onrenderererror | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 renderererror | +| onrendererstatechange | EventHandler \| null | 是 | 是 | 该事件处理器的事件类型为 rendererstatechange | + +### 事件 + +| 名称 | 接口 | 说明 | +|----------------------|-------------------------------------------------------------------|--------------------------| +| capturererror | [AudioErrorEvent](#audioerrorevent) | 当音频采集器发生错误时触发 | +| capturerstatechange | [AudioStateChangeEvent](#audiostatechangeevent) | 当音频采集器的状态改变时触发 | +| capturersamplesready | [AudioCapturerSamplesReadyEvent](#audiocapturersamplesreadyevent) | 当新的音频样本准备好时调用,这应该只用于调试目的 | +| renderererror | [AudioErrorEvent](#audioerrorevent) | 当音频渲染器发生错误时触发 | +| rendererstatechange | [AudioStateChangeEvent](#audiostatechangeevent) | 当音频渲染器发生错误时触发 | + +### 构造函数 + +new(options?: AudioDeviceModuleOptions): AudioDeviceModule + +用于创建 AudioDeviceModule 变量实例。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|-------------------------------------------------------|-----|-------------| +| options | [AudioDeviceModuleOptions](#audiodevicemoduleoptions) | 否 | 构造参数,如采样率等。 | + +### setSpeakerMute + +setSpeakerMute(mute: boolean): void; + +开启/取消扬声器静音。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|------|---------|-----|--------------------| +| mute | boolean | 是 | true:静音,false:取消静音 | + +### setMicrophoneMute + +setMicrophoneMute(mute: boolean): void; + +开启/取消麦克风静音。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|------|---------|-----|----------------------| +| mute | boolean | 是 | true:开启静音,false:取消静音 | + +### setNoiseSuppressorEnabled + +setNoiseSuppressorEnabled(enabled: boolean): boolean; + +启用或禁用内置噪声抑制器。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|---------|-----|------------------| +| enabled | boolean | 是 | true:启用,false:禁用 | + +返回值 + +| 类型 | 说明 | +|---------|--------------------| +| boolean | true: 成功,false: 失败 | + +### isBuiltInAcousticEchoCancelerSupported + +static isBuiltInAcousticEchoCancelerSupported(): boolean + +获取设备是否支持内置回声消除。 + +返回值 + +| 类型 | 说明 | +|---------|---------------------| +| boolean | true: 支持,false: 不支持 | + +### isBuiltInNoiseSuppressorSupported + +static isBuiltInNoiseSuppressorSupported(): boolean + +获取设备是否支持内置噪声抑制。 + +返回值 + +| 类型 | 说明 | +|---------|---------------------| +| boolean | true: 支持,false: 不支持 | + +## VideoEncoderFactory + +视频编码器工厂接口。 + +### 属性 + +无 + +## HardwareVideoEncoderFactory + +硬件编码器工厂,实现了 VideoEncoderFactory 接口,使用平台提供的硬件编码器。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-----------------------|---------|----|----|----------------------------| +| enableH264HighProfile | boolean | 是 | 否 | 表示是否启用H264编码器的High Profile | + +### 构造函数 + +new(enableH264HighProfile?: boolean): HardwareVideoEncoderFactory + +用于创建 HardwareVideoEncoderFactory 的变量实例。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|-----------------------|---------|----|-----------------------------| +| enableH264HighProfile | boolean | 否 | true:启用H264编码器的High Profile | + +#### 返回值 + +| 类型 | 说明 | +|-----------------------------|-------------| +| HardwareVideoEncoderFactory | 硬件视频编码器工厂实例 | + +#### 示例 + +```typescript +let videoEncoderFactory = new webrtc.HardwareVideoEncoderFactory(false); +let peerConnectionFactory = new webrtc.PeerConnectionFactory({ videoDecoderFactory: videoEncoderFactory }); +``` + +## SoftwareVideoEncoderFactory + +软件视频编码器工厂,实现了 VideoEncoderFactory 接口。内部使用WebRTC内置的软件视频编码码器,包括 H264(可选)、VP8、VP9、AV1。 + +### 属性 + +无 + +### 构造函数 + +new(): SoftwareVideoEncoderFactory + +用于创建 SoftwareVideoEncoderFactory 的变量实例。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|-----------------------------|-------------| +| SoftwareVideoEncoderFactory | 软件视频编码器工厂实例 | + +## VideoDecoderFactory + +视频解码器工厂接口。 + +### 属性 + +无 + +## HardwareVideoDecoderFactory + +硬件视频解码器工厂,实现了 VideoDecoderFactory 接口,使用平台提供的硬件解码器。 + +### 属性 + +无 + +### 构造函数 + +new(): HardwareVideoDecoderFactory + +用于创建 HardwareVideoDecoderFactory 的变量实例。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|-----------------------------|-------------| +| HardwareVideoDecoderFactory | 硬件视频解码器工厂实例 | + +## SoftwareVideoDecoderFactory + +软件视频解码器工厂,实现了 VideoDecoderFactory 接口。内部使用WebRTC内置的软件视频解码器,包括 H264(可选)、VP8、VP9、AV1。 + +### 属性 + +无 + +### 构造函数 + +new(): SoftwareVideoDecoderFactory + +用于创建 SoftwareVideoDecoderFactory 的变量实例。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|-----------------------------|-------------| +| SoftwareVideoDecoderFactory | 软件视频解码器工厂实例 | + +## AudioProcessingFactory + +音频处理工厂接口。 + +### 属性 + +无 + +### 构造函数 + +new(): AudioProcessingFactory + +用于创建 AudioProcessingFactory 的变量实例。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|------------------------|----------| +| AudioProcessingFactory | 音频处理工厂实例 | + +### create + +create(): AudioProcessing + +创建 AudioProcessing 实例。 + +#### 参数 + +无 + +#### 返回值 + +| 类型 | 说明 | +|-----------------|----------| +| AudioProcessing | 音频处理模块实例 | + +#### 示例 + +```typescript +let audioProcessingFactory = new webrtc.AudioProcessingFactory(); +let peerConnectionFactory = new webrtc.PeerConnectionFactory({ audioProcessing: this.audioProcessingFactory.create() }); +``` + +## AudioProcessing + +音频处理模块。 + +## Loggable + +日志输出接口。 + +### logMessage + +logMessage(message: string, severity: number, tag: string): void; + +向日志系统中添加日志条目。 + +参数: + +| 名称 | 类型 | 必填 | 说明 | +|----------|--------|-----|--------------------------------| +| message | string | 是 | 要记录的日志消息 | +| severity | number | 是 | 日志消息的严重性或级别,参见 LoggingSeverity | +| tag | string | 是 | 日志消息的标签或分类 | + +## AudioErrorType + +用于表示音频错误类型。 + +| 名称 | 类型 | 说明 | +|----------------------|--------|-------------------| +| init | string | 音频采集器或渲染器初始化错误 | +| start-exception | string | 音频采集器或渲染器启动异常 | +| start-state-mismatch | string | 音频采集器或渲染器启动时状态不匹配 | +| general | string | 音频采集器或渲染器的其它错误 | + +## AudioState + +用于表示音频采集器或渲染器的状态。 + +| 名称 | 类型 | 说明 | +|-------|---------|--------------| +| start | string | 音频采集器或渲染器已启动 | +| stop | string | 音频采集器或渲染器已停止 | + +## AudioError + +继承自Error,用于描述音频相关的错误信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|------|-----------------------------------|-----|-----|----------------| +| type | [AudioErrorType](#audioerrortype) | 是 | 否 | 提供关于错误类型的明确描述 | + +## AudioErrorEvent + +继承自 Event,提供了关于发生的错误的详细描述。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-------|---------------------------|-----|-----|--------| +| error | [AudioError](#audioerror) | 是 | 否 | 错误详细信息 | + +## AudioStateChangeEvent + +继承自 Event,提供了关于音频采集器或渲染器状态变化的详细描述。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|-------|---------------------------|-----|-----|------| +| state | [AudioState](#audiostate) | 是 | 否 | 新的状态 | + +## AudioCapturerSamplesReadyEvent + +继承自 Event,提供了音频采集器新的样本的详细描述。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|---------|-------------------------------|-----|-----|--------| +| samples | [AudioSamples](#audiosamples) | 是 | 否 | 音频样本描述 | + +### AudioSamples + +表示音频样本信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|--------------|-------------|-----|-----|-------------------------------------------------| +| audioFormat | number | 是 | 否 | 采样格式,参见 ohos.multimedia.audio.AudioSampleFormat | +| channelCount | number | 是 | 否 | 通道数,参加 ohos.multimedia.audio.AudioChannel | +| sampleRate | number | 是 | 否 | 采样率,参加 ohos.multimedia.audio.AudioSamplingRate | +| data | ArrayBuffer | 否 | 是 | 采样数据 | + +## AudioDeviceModuleOptions + +创建AudioDeviceModule的参数。 + +### 属性 + +| 名称 | 类型 | 必填 | 说明 | +|---------------------------------|---------|-----|-----------------------------------------------------------------------------------| +| audioSource | number | 否 | 音频输入源,参见 ohos.multimedia.audio.SourceType,默认为 SOURCE_TYPE_VOICE_COMMUNICATION | +| audioFormat | number | 否 | 音频输入格式,参见 ohos.multimedia.audio.AudioSampleFormat, 默认为 SAMPLE_FORMAT_S16LE | +| inputSampleRate | number | 否 | 音频输入采样率,默认为 48000HZ。 | +| useStereoInput | boolean | 否 | 控制是否使用立体声输入,默认为 false,即单声道。 | +| outputSampleRate | number | 否 | 音频输出采样率,默认为 48000HZ。 | +| useStereoOutput | boolean | 否 | 控制是否使用立体声输出,默认为 false,即单声道。 | +| rendererUsage | number | 否 | 音频输出用途,参见 ohos.multimedia.audio.StreamUsage, 默认为 STREAM_USAGE_VOICE_COMMUNICATION | +| useLowLatency | boolean | 否 | 控制是否使用低延迟输入输出,默认为 false。 | +| useHardwareAcousticEchoCanceler | boolean | 否 | 控制是否使用内置的HW回声消除器,默认为 false。 | +| useHardwareNoiseSuppressor | boolean | 否 | 控制是否使用内置的HW噪声抑制器,默认为 false。 | + +## Logging + +封装了WebRTC的 logging 模块。 + +### injectLoggable + +static injectLoggable(injectedLoggable: Loggable, severity: LoggingSeverity): void + +注入一个实现了Loggable接口的自定义logger。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|------------------|-----------------|-----|---------------------| +| injectedLoggable | Loggable | 是 | 自定义logger | +| severity | LoggingSeverity | 是 | 日志级别,低于此级别的日志消息将被丢弃 | + +### deleteInjectedLoggable + +static deleteInjectedLoggable(): void + +删除注入的自定义logger。 + +### enableLogThreads + +static enableLogThreads(): void + +在日志中显示线程ID。 + +### enableLogTimeStamps + +static enableLogTimeStamps(): void + +在日志中显示程序运行的时间。 + +### enableLogToDebugOutput + +static enableLogToDebugOutput(severity: LoggingSeverity): void + +打开debug日志,并指定日志级别。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|----------|-----------------|-----|---------------------| +| severity | LoggingSeverity | 是 | 日志级别,低于此级别的日志消息将被丢弃 | + +### log + +static log(severity: LoggingSeverity, tag: string, message: string): void + +输出日志。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|----------|-----------------|-----|----------| +| severity | LoggingSeverity | 是 | 该日志消息的级别 | +| tag | string | 是 | 日志tag | +| message | string | 是 | 日志内容 | + +### d + +static d(tag: string, message: string): void + +输出日志,级别为 LoggingSeverity.INFO。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|--------|-----|-------| +| tag | string | 是 | 日志tag | +| message | string | 是 | 日志内容 | + +### e + +static e(tag: string, message: string, e?: Error): void + +输出日志,级别为 LoggingSeverity.ERROR。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|--------|-----|-------| +| tag | string | 是 | 日志tag | +| message | string | 是 | 日志内容 | +| e | Error | 否 | 错误信息 | + +### w + +static w(tag: string, message: string, e?: Error): void + +输出日志,级别为 LoggingSeverity.WARNING。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|--------|-----|-------| +| tag | string | 是 | 日志tag | +| message | string | 是 | 日志内容 | +| e | Error | 否 | 错误信息 | + +### v + +static v(tag: string, message: string): void + +输出日志,级别为 LoggingSeverity.VERBOSE。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|---------|--------|-----|-------| +| tag | string | 是 | 日志tag | +| message | string | 是 | 日志内容 | + +## LoggingSeverity + +日志级别枚举。 + +| 名称 | 值 | 说明 | +|---------|-----|--------------------------------------| +| VERBOSE | 0 | 此级别用于我们不希望出现在正常调试日志中,但应该出现在诊断日志中的数据。 | +| INFO | 1 | 用于调试。 | +| WARNING | 2 | 警告,需要注意的信息。 | +| ERROR | 3 | 错误,需要避免此类事件发生。 | +| NONE | 4 | 不输出。 | + +## VideoRenderController + +继承自 XComponentController,用于实现视频渲染,需配合XComponent组件及VideoTrack一起使用。 + +### setVideoTrack + +setVideoTrack(track: VideoTrack | null): void + +设置一个VideoTrack实例作为渲染源。 + +参数: + +| 参数名 | 类型 | 必填 | 说明 | +|-------|-------------------------------| ---- | ------------------------------------------------------ | +| track | [VideoTrack](#videotrack) \ | null | 是 | VideoTrack实例,使用PeerConnectionFactory创建,或从PeerConnection的ontrack事件回调获取。 | + + +## MediaDeviceInfo + +描述媒体设备信息。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|----------|-------------------------------------|-----|-----|---------------------------------------| +| deviceId | string | 是 | 否 | 设备标识符,通过deviceId和kind必须能唯一标识一个设备。 | +| kind | [MediaDeviceKind](#mediadevicekind) | 是 | 否 | 用于接收媒体数据的传输机制。 | +| label | string | 是 | 否 | 用于描述该设备(如”External USB Webcam“)。 | +| groupId | string | 是 | 否 | 设备的组ID,如果两个设备属于同一个物理设备,则它们的groupId相同。 | + +## MediaDevices + +用于检查和访问可用的媒体设备。 + +### enumerateDevices + +enumerateDevices(): Promise + +收集可用的媒体输入和输出设备的信息。 + +返回值 + +| 类型 | 说明 | +|-------------------------------------------------|------------| +| Promise\<[MediaDeviceInfo](#mediadeviceinfo)[]> | 媒体设备信息的数组。 | + +### getSupportedConstraints + +getSupportedConstraints(): MediaTrackSupportedConstraints + +获取媒体轨道支持的约束条件。 + +返回值 + +| 类型 | 说明 | +|-------------------------------------------------------------------|--------------| +| [MediaTrackSupportedConstraints](#mediatracksupportedconstraints) | 媒体轨道支持的约束条件。 | + +### getUserMedia + +getUserMedia(constraints?: MediaStreamConstraints): Promise + +根据约束条件,获取对应的音视频源,如麦克风或相机。 + +**参数** + +| 参数名 | 类型 | 必填 | 说明 | +|-------------|---------------------------------------------------|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| constraints | [MediaStreamConstraints](#mediastreamconstraints) | 否 | 一个包含媒体流约束条件的对象,用于配置音视频源的性质和设置。对于音频,当前不支持 advanced 属性,且仅支持 echoCancellation、autoGainControl、noiseSuppression 约束条件(表示是否开启WebRTC内置的回声消除、自动增益控制及噪声印制算法)。对于支持的约束条件,可通过 MediaDevices.getSupportedConstraints 接口查询。 | + +**返回值** + +| 类型 | 说明 | +|---------------------------------------|-------------------------| +| Promise\<[MediaStream](#mediastream)> | 如果满足约束条件,则返回包含对应媒体的媒体流。 | + +> **说明:** +> 当前可以通过两种方式获取音视频输入媒体: +> 1、MediaDevices.getUserMedia,即本接口,使用相对简单; +> 2、PeerConnectionFactory 中的相关接口,可通过 [AudioDeviceModule](#audiodevicemodule) 等施加更多的控制; + +**示例** + +```typescript +let mediaDevices = new webrtc.MediaDevices(); +// 获取音频流轨道,禁用回声消除;获取视频流轨道,指定理想的分辨率为640x480,并限制帧率在15到30之间 +let mediaStream = await mediaDevices.getUserMedia({audio:{echoCancellation:false}, video:{width:640, height:480, framerate:{min:15, max:30}}}); +``` + +### getDisplayMedia + +getDisplayMedia(options?: DisplayMediaStreamOptions): Promise + +根据约束条件,获取对应的音视频源,如麦克风或桌面。 + +**参数** + +| 参数名 | 类型 | 必填 | 说明 | +| ----------- | ------------------------------------------------------- | ---- | ------------------------------------------------------------ | +| constraints | [DisplayMediaStreamOptions](#displaymediastreamoptions) | 否 | 一个包含媒体流约束条件的对象,用于配置音视频源的性质和设置。对于视频(桌面),当前不支持 advanced 属性,且仅支持 width、height约束条件;
对于音频,当前不支持 advanced 属性,且仅支持 echoCancellation、autoGainControl、noiseSuppression 约束条件(表示是否开启WebRTC内置的回声消除、自动增益控制及噪声印制算法)。
可通过 MediaDevices.getSupportedConstraints 接口查询支持的约束条件。 | + +**返回值** + +| 类型 | 说明 | +|---------------------------------------|-------------------------| +| Promise\<[MediaStream](#mediastream)> | 如果满足约束条件,则返回包含对应媒体的媒体流。 | + +**示例** + +```typescript +let mediaDevices = new webrtc.MediaDevices(); +let mediaStream = await mediaDevices.getDisplayMedia({video: {width: 720, height:1080}}); +``` + +## NativeVideoRenderer + +用于在对应窗口中渲染本地或远端的视频流。 + +### 属性 + +| 名称 | 类型 | 可读 | 可写 | 说明 | +|----------|-------------------------------------------------|---|-----|---------------------------------------| +| surfaceId | string | 是 | 否 | 窗口对应的Surface Id,由 init 接口设置。 | +| videoTrack | [MediaDeviceKind](#mediadevicekind)\| undefined | 是 | 否 | 关联的视频轨道,由 setVideoTrack 接口设置。 | + +### init + +init(surfaceId: string): void + +使用窗口对应的 surfaceId 初始化,创建相关的渲染环境。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|-----------|--------|----|--------------------------------------------------------------| +| surfaceId | string | 是 | 窗口的Surface Id,如使用 XComponent,可通过对应的 XComponentController 获取。 | + +#### 返回值 + +无 + +#### 示例 + +```typescript +// 获取 surface id +let surfaceId = this.xComponentController.getXComponentSurfaceId(); + +let nativeVideoRenderer = new webrtc.NativeVideoRenderer(); +nativeVideoRenderer.init(surfaceId); +``` + +### setVideoTrack + +setVideoTrack(videoTrack: MediaStreamTrack | null): void + +设置关联的视频轨道,以接收视频流并渲染。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|-----------|--------|----|----------------------------------------------------------------------------------------------| +| videoTrack | [MediaDeviceKind](#mediadevicekind)\| null | 是 | 当 videoTrack 不为 null 且 kind 为 video 时,关联该视频轨道,并接收其视频流;当 videoTrack 为 null 时,解除与当前视频轨道的关联,停止接收视频流。 | + +#### 返回值 + +无 + +#### 示例 + +```typescript +// 创建视频轨道 +this.localVideoSource = this.peerConnectionFactory.createVideoSource({width: 320, height: 240}); +this.localVideoTrack = this.peerConnectionFactory.createVideoTrack("video", this.localVideoSource); + +let nativeVideoRenderer = new webrtc.NativeVideoRenderer(); +nativeVideoRenderer.setVideoTrack(this.localVideoTrack); +``` + +### release + +release(): void + +释放窗口对应的渲染环境,及其它资源。 + +#### 参数 + +无 + +#### 返回值 + +无 + +## VideoRenderController + +视频渲染控制器,继承自 XComponentController,对应的 XComponent 组件创建后,使用其 surface id 初始化内部的 NativeVideoRenderer实例,XComponent组件销毁时,则释放该 NativeVideoRenderer 实例。 + +### setVideoTrack + +setVideoTrack(track: MediaStreamTrack | null): void + +通过内部的 NativeVideoRenderer实例设置或解除关联的视频轨道。 + +#### 参数 + +| 参数名 | 类型 | 必填 | 说明 | +|-----------|--------|----|----------------------------------------------------------------------------------------------| +| videoTrack | [MediaDeviceKind](#mediadevicekind)\| null | 是 | 当 videoTrack 不为 null 且 kind 为 video 时,关联该视频轨道,并接收其视频流;当 videoTrack 为 null 时,解除与当前视频轨道的关联,停止接收视频流。 | + +#### 返回值 + +无 + +#### 示例 + +```typescript +// 初始化 XComponent 控制器 +mXComponentController: VideoRenderController = new VideoRenderController(); + +build() +{ + // ...... + // 声明 XComponent 组件,并指定 XComponent 控制器 + XComponent({ + id: "local-video", + type: XComponentType.SURFACE, + controller: this.mXComponentController + }) + .width('640px') + .height('480px') + .onLoad(() => { + }) + .onDestroy(() => { + }) + // ...... +} + +this.mXComponentController.setVideoTrack(this.localVideoTrack); +``` diff --git a/sdk/ohos/har_hap/.gitignore b/sdk/ohos/har_hap/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..483660b2c6e6ed8dd5a0b395fd334c4d7243e22d --- /dev/null +++ b/sdk/ohos/har_hap/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +**/oh-package-lock.json5 \ No newline at end of file diff --git a/sdk/ohos/har_hap/AppScope/app.json5 b/sdk/ohos/har_hap/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ded37fd4a3ac489feaae2dc45890d142926c1a1b --- /dev/null +++ b/sdk/ohos/har_hap/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.archermind.webrtc", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/sdk/ohos/har_hap/AppScope/resources/base/element/string.json b/sdk/ohos/har_hap/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..2e6f1bafbd3da30eba0f4ff285d6227f0c734bfa --- /dev/null +++ b/sdk/ohos/har_hap/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "WebRTC" + } + ] +} diff --git a/sdk/ohos/har_hap/AppScope/resources/base/media/app_icon.png b/sdk/ohos/har_hap/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/sdk/ohos/har_hap/AppScope/resources/base/media/app_icon.png differ diff --git a/sdk/ohos/har_hap/LICENSE b/sdk/ohos/har_hap/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/sdk/ohos/har_hap/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sdk/ohos/har_hap/README.md b/sdk/ohos/har_hap/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a7e8cdcd12b2482407409191c8af2683a777bbf0 --- /dev/null +++ b/sdk/ohos/har_hap/README.md @@ -0,0 +1,33 @@ +# DevEco Studio 示例工程 + +## 简介 + +此目录为OpenHarmony应用示例工程(基于DevEco Studio 5.0.0 Release),其中ohos_webrtc模块使用编译生成的`libohos_webrtc.so`及相关接口文件构建HAR包,entry模块依赖ohos_webrtc并提供应用示例。 + +## 准备 + +参考[文档](/docs/ohos/webrtc_build.md)编译生成`libohos_webrtc.so`动态库。 + +`arm64-v8a`版本放置在`ohos_webrtc/libs/arm64-v8a`目录下,可使用如下参数编译: +``` shell +gn gen out/arm64 --args='is_clang=true target_os="ohos" target_cpu="arm64" ohos_extra_ldflags="-static-libstdc++" is_official_build=true ohos_sdk_native_root="path/to/ohos/sdk"' +ninja -C out/arm64 +``` + +`armeabi-v7a`版本放置在`ohos_webrtc/libs/armeabi-v7a`目录下,可使用如下参数编译: +``` shell +gn gen out/arm32 --args='is_clang=true target_os="ohos" target_cpu="arm" arm_float_abi="softfp" ohos_extra_ldflags="-static-libstdc++" is_official_build=true ohos_sdk_native_root="path/to/ohos/sdk"' +ninja -C out/arm32 +``` + +**注意** +> 如果不指定`ohos_extra_ldflags="-static-libstdc++"`参数,则需要将sdk中的`libc++_shared.so`一起放到对应目录下。 + +## 运行调试 + +准备好两台运行 OpenHarmony 5.0 (或以上版本)的设备(也可以仅使用一台设备,然后与浏览器端连接,浏览器端测试地址:https://webrtc.youzhi.life/)。确保设备或浏览器上可正常使用摄像头和音频设备,并连接好网络。 + +使用 DevEco Studio 打开本工程,连接设备后点击运行。之后在应用界面的文本框输入对端的ID,点击 Connect 按钮。 + +**说明** +> 本工程所使用的信令服务器(wss://youzhi.life:8443),及浏览器端测试网页(https://webrtc.youzhi.life)仅用于测试,请勿作其它用途。 \ No newline at end of file diff --git a/sdk/ohos/har_hap/build-profile.json5 b/sdk/ohos/har_hap/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..625bc71fb0876b1d1ed898ad469155afda53f02c --- /dev/null +++ b/sdk/ohos/har_hap/build-profile.json5 @@ -0,0 +1,40 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compileSdkVersion": 12, + "compatibleSdkVersion": 12, + "runtimeOS": "OpenHarmony" + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "ohos_webrtc", + "srcPath": "./ohos_webrtc" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/.gitignore b/sdk/ohos/har_hap/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/sdk/ohos/har_hap/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/build-profile.json5 b/sdk/ohos/har_hap/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2db712a72e6245e7044d589363f3adc4b5bc5834 --- /dev/null +++ b/sdk/ohos/har_hap/entry/build-profile.json5 @@ -0,0 +1,13 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default", + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/hvigorfile.ts b/sdk/ohos/har_hap/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/sdk/ohos/har_hap/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/sdk/ohos/har_hap/entry/oh-package.json5 b/sdk/ohos/har_hap/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c1d7c0e5f0686b8bf18089468911c38564ba7967 --- /dev/null +++ b/sdk/ohos/har_hap/entry/oh-package.json5 @@ -0,0 +1,12 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "WebRTC demo for OpenHarmony/HarmonyOS", + "main": "", + "author": "", + "license": "", + "dependencies": { + "ohos_webrtc": "../ohos_webrtc" + } +} + diff --git a/sdk/ohos/har_hap/entry/src/main/ets/entryability/EntryAbility.ts b/sdk/ohos/har_hap/entry/src/main/ets/entryability/EntryAbility.ts new file mode 100644 index 0000000000000000000000000000000000000000..82a647b873191eb35deea4fead32649b5ae56bcf --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/ets/entryability/EntryAbility.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; +import hilog from '@ohos.hilog'; +import UIAbility from '@ohos.app.ability.UIAbility'; +import Want from '@ohos.app.ability.Want'; +import window from '@ohos.window'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/sdk/ohos/har_hap/entry/src/main/ets/pages/Index.ets b/sdk/ohos/har_hap/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..b4afc89c78433713fc2e2e8950708c4f5cf92f19 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,566 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import webrtc, { Logging, LoggingSeverity, VideoRenderController } from 'ohos_webrtc'; +import abilityAccessCtrl, { PermissionRequestResult } from '@ohos.abilityAccessCtrl'; +import webSocket from '@ohos.net.webSocket'; +import promptAction from '@ohos.promptAction'; +import buffer from '@ohos.buffer'; +import fs from '@ohos.file.fs'; + +const TAG: string = '[Index]'; + +// 用于测试的信令服务器 +const SIGNAL_SERVER = 'wss://youzhi.life:8443'; +// 用于测试的Web客户端地址 +// https://webrtc.youzhi.life/ +// 用于测试的STUN服务器地址 +const STUN_SERVER = "stun:stun.l.google.com:19302" + +interface MessageData { + sdp?: webrtc.RTCSessionDescriptionInit; + ice?: webrtc.RTCIceCandidateInit; +} + +@Entry +@Component +struct Index { + @State buttonTitle: string = 'Connect'; + @State statusText: string = ''; + @State private myId: string = ''; + @State private peerId: string = ''; + private mXComponentController1: VideoRenderController = new VideoRenderController(); + private mXComponentController2: VideoRenderController = new VideoRenderController(); + private ws: webSocket.WebSocket = webSocket.createWebSocket(); + private localAudioSource?: webrtc.AudioSource; + private localVideoSource?: webrtc.VideoSource; + private localAudioTrack?: webrtc.MediaStreamTrack; + private localVideoTrack?: webrtc.MediaStreamTrack; + private remoteAudioTrack?: webrtc.MediaStreamTrack; + private remoteVideoTrack?: webrtc.MediaStreamTrack; + private adm?: webrtc.AudioDeviceModule; + private pcf?: webrtc.PeerConnectionFactory; + private pc?: webrtc.RTCPeerConnection; + private recvChannel?: webrtc.RTCDataChannel; + private sendChannel?: webrtc.RTCDataChannel; + + private onDataChannelMessageReceived = (event: webrtc.MessageEvent) => { + // this is Index + console.info(TAG, "dataChannel.OnMessage: ", event); + console.info(TAG, "dataChannel.OnMessage: (typeof event.data) = ", typeof event.data); + console.info(TAG, "dataChannel.OnMessage: (event.data instanceof ArrayBuffer) = ", event.data instanceof ArrayBuffer); + + this.setStatus("Received data channel message"); + + if (typeof event.data === 'string' || event.data instanceof String) { + console.info(TAG, 'Incoming string message: ' + event.data); + } else { + console.info(TAG, 'Incoming data message'); + } + } + + // + build() { + Column() { + Row() { + Text("状态") + .fontSize(32) + .fontWeight(FontWeight.Normal) + .margin({ right: 24 }) + Text(this.statusText) + .fontSize(32) + .fontWeight(FontWeight.Bold) + } + .alignSelf(ItemAlign.Start) + + Row() { + Text("本机ID") + .fontSize(32) + .fontWeight(FontWeight.Normal) + .margin({ right: 24 }) + .align(Alignment.Start) + Text(this.myId) + .fontSize(32) + .fontWeight(FontWeight.Bold) + } + .alignSelf(ItemAlign.Start) + .padding({ top: 10 }) + + Row() { + TextInput({ placeholder: '请输入对端ID' }) + .width('300px') + .height('54px') + .fontSize(28) + .fontWeight(FontWeight.Normal) + .type(InputType.Number) + .layoutWeight('20%') + .onChange((value: string) => { + this.peerId = value; + }) + + Button(this.buttonTitle) + .width('240px') + .height('54px') + .fontSize(28) + .fontWeight(FontWeight.Bold) + .margin({ top: 20 }) + .onClick(() => { + if (this.buttonTitle == 'Disconnect') { + this.ws.close(); + this.buttonTitle = 'Connect'; + return; + } + + if (this.peerId.length < 2 || this.peerId.length > 4) { + promptAction.showToast({ message: "请输入2到4位数ID" }); + return; + } + + this.sendMessage('SESSION ' + this.peerId); + this.buttonTitle = 'Disconnect'; + }) + .margin({ left: 24, right: 24 }) + + Row() { + Checkbox({ name: 'remote_offerer', group: 'checkboxGroup' }) + .select(false) + .selectedColor(0x39a2db) + .onChange((value: boolean) => { + console.info('remote_offerer change is' + value) + + }).margin({ right: 8 }) + + Text("Remote offerer") + .fontSize(20) + .fontWeight(FontWeight.Normal) + }.layoutWeight('20%').visibility(Visibility.Hidden) + + } + .alignSelf(ItemAlign.Start) + .padding({ top: 24 }) + + Row() { + XComponent({ + id: "local-video", + type: XComponentType.SURFACE, + controller: this.mXComponentController1 + }) + .width('640px') + .height('480px') + .padding({right: 10}) + .onLoad((context?) => { + console.log(TAG, "context: " + context); + console.log(TAG, "surfaceId: " + this.mXComponentController1.getXComponentSurfaceId()); + }) + .onDestroy(() => { + console.log(TAG, "onDestroy"); + }) + + XComponent({ + id: "remote-video", + type: XComponentType.SURFACE, + controller: this.mXComponentController2 + }) + .width('640px') + .height('480px') + .onLoad((context?) => { + console.log(TAG, "context: " + JSON.stringify(context)); + console.log(TAG, "surfaceId: " + this.mXComponentController2.getXComponentSurfaceId()); + }) + .onDestroy(() => { + console.log(TAG, "onDestroy"); + }) + } + .alignSelf(ItemAlign.Start) + .padding({ top: 10 }) + + } + .width('100%') + .height('100%') + .margin({ left: 24, right: 24, top: 24, bottom: 24 }) + .justifyContent(FlexAlign.Start) + } + + aboutToAppear(): void { + Logging.enableLogThreads(); + Logging.enableLogTimeStamps(); + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + + abilityAccessCtrl.createAtManager() + .requestPermissionsFromUser(getContext(), ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE']) + .then(async (result: PermissionRequestResult) => { + let granted = true; + for (let index = 0; index < result.permissions.length; index++) { + console.info(TAG, "request permission [" + result.permissions[index] + "] with result: " + result.authResults[index]); + if (result.authResults[index] != abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { + granted = false; + } + } + + if (!granted) { + this.setError("Permission Not Granted"); + return; + } + + console.info(TAG, "Connecting to server"); + this.setStatus("Connecting to server"); + this.ws.connect(SIGNAL_SERVER, (err, success) => { + }) + }); + + this.ws.on('open', (err, data) => { + console.info(TAG, 'websocket open'); + this.myId = this.getRandomId(); + this.sendMessage("HELLO " + this.myId); + this.setStatus("Registering with server"); + }); + + this.ws.on('close', (err, data: webSocket.CloseResult) => { + console.info(TAG, 'websocket closed'); + this.setStatus('Disconnected from server'); + + this.pc?.close(); + this.pc = undefined; + + setTimeout(() => { + this.ws.connect(SIGNAL_SERVER); + }, 1000); + }); + + this.ws.on('error', (err) => { + console.info(TAG, 'websocket err: ' + err.code); + this.setError("Unable to connect to server: " + err.message); + + setTimeout(() => { + this.ws.connect(SIGNAL_SERVER); + }, 1000); + }); + + this.ws.on('message', (err, data: string | ArrayBuffer) => { + console.info(TAG, 'websocket receive message: ' + data); + if (data === 'HELLO') { + this.setStatus("Registered with server, waiting for call"); + this.createPeerConnection(); + } else if (data === 'SESSION_OK') { + this.setStatus("Starting negotiation"); + this.sendOffer(); + } else if (data === 'OFFER_REQUEST') { + this.sendOffer(); + } else { + if (data.toString().startsWith('ERROR')) { + this.handleIncomingError(data.toString()); + } else { + try { + let msg = JSON.parse(data.toString()) as MessageData; + if (msg && msg.sdp) { + this.receiveSdp(msg.sdp); + } else if (msg && msg.ice) { + this.receiveIce(msg.ice); + } else { + console.error(TAG, 'receive unknown message: ' + data); + this.handleIncomingError("Unknown incoming JSON"); + } + } catch (error) { + console.error(TAG, 'receive invalid message: ' + data); + } + } + } + }); + + this.createAudioDeviceModule(); + this.createPeerConnectionFactory(); + } + + aboutToDisappear(): void { + this.pcf?.stopAecDump(); + Logging.deleteInjectedLoggable(); + + this.ws.close(); + + this.ws.off('open'); + this.ws.off('close'); + this.ws.off('error'); + this.ws.off('message'); + } + + onPageShow(): void { + console.info(TAG, 'onPageShow'); + } + + onPageHide(): void { + console.info(TAG, 'onPageHide'); + } + + getRandomId() { + return Math.floor(Math.random() * (9000 - 10) + 10).toString(); + } + + setStatus(text: string) { + console.info(TAG, text); + this.statusText = text; + } + + setError(error: string) { + console.error(TAG, error); + this.statusText = error; + } + + handleIncomingError(error: string) { + this.setError('ERROR: ' + error); + this.ws.close(); + } + + async createAudioDeviceModule() { + this.adm = new webrtc.AudioDeviceModule({ useStereoInput: true, useStereoOutput: true }); + this.adm.oncapturererror = (event) => { + console.error(TAG, 'oncapturererror: ' + JSON.stringify(event)); + }; + this.adm.oncapturerstatechange = (event) => { + console.info(TAG, 'oncapturerstatechange: ' + JSON.stringify(event)); + }; + this.adm.oncapturersamplesready = (event) => { + console.debug(TAG, 'oncapturersamplesready: ' + JSON.stringify(event)); + }; + this.adm.onrenderererror = (event) => { + console.error(TAG, 'onrenderererror: ' + JSON.stringify(event)); + }; + this.adm.onrendererstatechange = (event) => { + console.info(TAG, 'onrendererstatechange: ' + JSON.stringify(event)); + }; + Logging.d(TAG, 'this.adm: ' + JSON.stringify(this.adm)); + } + + async createPeerConnectionFactory() { + this.pcf = new webrtc.PeerConnectionFactory(); + Logging.d(TAG, 'this.pcf: ' + JSON.stringify(this.pcf)); + + let filesDir = getContext().filesDir; + let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + this.pcf.startAecDump(file.fd, -1); + } + + async prepareLocalMedia() { + if (!this.localAudioTrack) { + this.localAudioSource = this.pcf?.createAudioSource({ echoCancellation: true, noiseSuppression: true }); + console.info(TAG, 'audioSource: ' + JSON.stringify(this.localAudioSource)); + + this.localAudioTrack = this.pcf?.createAudioTrack("audio", this.localAudioSource); + console.info(TAG, 'audioTrack: ' + JSON.stringify(this.localAudioTrack)); + } + + if (!this.localVideoTrack) { + this.localVideoSource = this.pcf?.createVideoSource({width: 1280, height: 720, facingMode: 'user'}, false); + console.info(TAG, 'videoSource: ' + JSON.stringify(this.localVideoSource)); + this.localVideoSource!.oncapturerstarted = (event: webrtc.VideoCapturerStartedEvent) => { + console.info(TAG, 'oncapturerstarted: event=' + JSON.stringify(event)); + }; + this.localVideoSource!.oncapturerstopped = (event: webrtc.Event) => { + console.info(TAG, 'oncapturerstopped: event=' + JSON.stringify(event)); + }; + + this.localVideoTrack = this.pcf?.createVideoTrack("video", this.localVideoSource); + console.info(TAG, 'videoTrack: ' + JSON.stringify(this.localVideoTrack)); + + if (this.localVideoTrack) { + this.mXComponentController1.setVideoTrack(this.localVideoTrack); + } + } + } + + async createPeerConnection() { + await this.prepareLocalMedia(); + + this.pc = this.pcf?.createPeerConnection({ + iceServers: [{ urls: STUN_SERVER }] + }); + + if (!this.pc) { + console.error(TAG, 'failed to create RTCPeerConnection'); + return; + } + + this.pc.ontrack = (event: webrtc.RTCTrackEvent) => { + console.info(TAG, 'ontrack: ' + JSON.stringify(event)); + if (event.track.kind == 'video') { + this.remoteVideoTrack = event.track; + this.mXComponentController2.setVideoTrack(event.track); + } else if (event.track.kind == 'audio') { + this.remoteAudioTrack = event.track; + } + }; + this.pc.onsignalingstatechange = (event) => { + console.info(TAG, 'onsignalingstatechange: ' + this.pc?.signalingState); + } + this.pc.onconnectionstatechange = (event) => { + console.info(TAG, 'onconnectionstatechange: ' + this.pc?.connectionState); + } + this.pc.onnegotiationneeded = (event) => { + // this is Index + console.info(TAG, 'onnegotiationneeded: this=' + JSON.stringify(this.pc)); + console.info(TAG, 'onnegotiationneeded: event=' + JSON.stringify(event)); + } + + this.pc.onicecandidate = (event) => { + console.info(TAG, 'onicecandidate: this=' + JSON.stringify(this)); + console.info(TAG, 'onicecandidate: event=' + JSON.stringify(event)); + if (event.candidate) { + this.sendMessage(JSON.stringify({ ice: event.candidate })); + } + }; + this.pc.onicecandidateerror = (event) => { + console.error(TAG, 'onicecandidateerror: event=' + JSON.stringify(event)); + }; + this.pc.oniceconnectionstatechange = (event) => { + console.error(TAG, 'oniceconnectionstatechange: ' + this.pc?.iceConnectionState); + }; + this.pc.onicegatheringstatechange = (event) => { + console.info(TAG, 'onicegatheringstatechange: ' + this.pc?.iceGatheringState); + switch (this.pc?.iceGatheringState) { + case "new": + console.info(TAG, 'onicegatheringstatechange: gathering is either just starting or has been reset'); + break; + case "gathering": + console.info(TAG, 'onicegatheringstatechange: gathering has begun or is ongoing'); + break; + case "complete": + console.info(TAG, 'onicegatheringstatechange: gathering has ended'); + break; + } + }; + this.pc.ondatachannel = (event) => { + console.info(TAG, 'ondatachannel: this=' + JSON.stringify(this)); + console.info(TAG, 'ondatachannel: event=' + JSON.stringify(event)); + this.setStatus("Data channel created"); + this.recvChannel = event.channel; + this.recvChannel.onopen = this.onDataChannelOpen; + this.recvChannel.onmessage = this.onDataChannelMessageReceived; + this.recvChannel.onerror = this.onDataChannelError; + this.recvChannel.onclose = this.onDataChannelClose; + } + + let rtpSender1 = this.pc.addTrack(this.localAudioTrack); + console.info(TAG, 'rtpSender1: ' + JSON.stringify(rtpSender1)); + let rtpSender2 = this.pc.addTrack(this.localVideoTrack); + console.info(TAG, 'rtpSender2: ' + JSON.stringify(rtpSender2)); + + this.sendChannel = this.pc.createDataChannel("send"); + console.info(TAG, 'sendChannel: ' + JSON.stringify(this.sendChannel)); + this.sendChannel.onopen = (event) => { + // this is Index + console.info(TAG, '=dataChannel.OnOpen: this=', JSON.stringify(this)); + console.info(TAG, "=dataChannel.OnOpen: ", event); + + this.sendChannel?.send("Hi! (from ArkTS)"); + let data1 = buffer.alloc(20, 0); + this.sendChannel?.send(data1.buffer); + + let data2 = new Int32Array(new ArrayBuffer(20)); + data2.fill(1); + this.sendChannel?.send(data2.buffer); + + let data3 = new Uint8Array(20); + data3.fill(2); + this.sendChannel?.send(data3.buffer); + }; + this.sendChannel.onmessage = this.onDataChannelMessageReceived; + this.sendChannel.onerror = this.onDataChannelError; + this.sendChannel.onclose = this.onDataChannelClose; + } + + onDataChannelOpen(event: webrtc.Event) { + // this is webrtc.RTCDataChannel + console.info(TAG, "dataChannel.OnOpen: ", event); + if (this instanceof webrtc.RTCDataChannel) { + this.send("Hi! (from ArkTS)"); + } + } + + async onDataChannelError(event: webrtc.Event) { + // this is webrtc.RTCDataChannel + console.info(TAG, "dataChannel.OnError: ", event); + } + + async onDataChannelClose(event: webrtc.Event) { + // this is webrtc.RTCDataChannel + console.info(TAG, 'dataChannel.OnClose: this=', JSON.stringify(this)); + console.info(TAG, "dataChannel.OnClose", event); + } + + async sendOffer() { + try { + let offer = await this.pc?.createOffer(); + console.info(TAG, 'createOffer: ' + JSON.stringify(offer)); + + this.pc?.setLocalDescription(offer); + + this.sendMessage(JSON.stringify({ sdp: offer })); + } catch (e) { + console.error(TAG, 'sendOffer: ' + JSON.stringify(e)); + } + } + + async sendAnswer() { + this.setStatus("Got SDP offer, creating answer"); + + this.pc?.createAnswer().then((desc) => { + console.info(TAG, 'Success to create answer: ' + JSON.stringify(desc)); + this.pc?.setLocalDescription(desc).then(() => { + this.setStatus("Sending SDP answer"); + this.sendMessage(JSON.stringify({ sdp: this.pc?.localDescription })); + }); + }).catch((e: Error) => { + console.error(TAG, 'Failed to create answer: ' + e.message); + this.setError(e.message); + }); + } + + async sendMessage(message: string) { + console.info(TAG, 'websocket send: ' + message); + this.ws.send(message, (error, success) => { + if (success) console.info(TAG, 'websocket send success: ' + message); + else console.error(TAG, 'websocket send error: ' + error); + }); + } + + async receiveSdp(sdp: webrtc.RTCSessionDescriptionInit) { + if (sdp.type === 'offer') { + console.info(TAG, 'receive offer'); + this.setStatus(''); + this.pc?.setRemoteDescription(sdp).then(() => { + this.setStatus("Remote SDP set"); + this.sendAnswer(); + }).catch((e: Error) => { + console.error(TAG, "Set remote description error: " + e); + this.setError(e.message); + }); + } else if (sdp.type === 'answer') { + console.info(TAG, 'receive answer'); + this.pc?.setRemoteDescription(sdp).then(() => { + this.setStatus("Remote SDP set"); + }).catch((e: Error) => { + console.error(TAG, "Set remote description error: " + e); + this.setError(e.message); + }); + } + } + + async receiveIce(ice: webrtc.RTCIceCandidateInit) { + console.info(TAG, 'receive icecandidate'); + this.pc?.addIceCandidate(ice).catch((e: Error) => { + console.error(TAG, "add ice candidate error: " + e); + this.setError(e.message); + }); + } +} diff --git a/sdk/ohos/har_hap/entry/src/main/module.json5 b/sdk/ohos/har_hap/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e94c6c4aad6276cfe192daf0ab995bbf886150a9 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/module.json5 @@ -0,0 +1,63 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ts", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET", + "reason": "$string:internet_permission_reason" + }, + { + "name" : "ohos.permission.CAMERA", + "reason": "$string:camera_permission_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:microphone_permission_reason", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "inuse" + } + } + ] + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/src/main/resources/base/element/color.json b/sdk/ohos/har_hap/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/src/main/resources/base/element/string.json b/sdk/ohos/har_hap/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..36fac19e8b493a639156e0332d00a0243bfa4fd0 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/resources/base/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "WebRTC" + }, + { + "name": "internet_permission_reason", + "value": "internet permission" + }, + { + "name": "camera_permission_reason", + "value": "camera permission" + }, + { + "name": "microphone_permission_reason", + "value": "microphone permission" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/src/main/resources/base/media/icon.png b/sdk/ohos/har_hap/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/sdk/ohos/har_hap/entry/src/main/resources/base/media/icon.png differ diff --git a/sdk/ohos/har_hap/entry/src/main/resources/base/media/startIcon.png b/sdk/ohos/har_hap/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..366f76459ffd4494ec40d0ddd5c59385b9c5da11 Binary files /dev/null and b/sdk/ohos/har_hap/entry/src/main/resources/base/media/startIcon.png differ diff --git a/sdk/ohos/har_hap/entry/src/main/resources/base/profile/main_pages.json b/sdk/ohos/har_hap/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/sdk/ohos/har_hap/entry/src/main/resources/en_US/element/string.json b/sdk/ohos/har_hap/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..fcc064c8061c18ccd903e434cf5a5b5b5fd965f3 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + }, + { + "name": "internet_permission_reason", + "value": "internet permission" + }, + { + "name": "camera_permission_reason", + "value": "camera permission" + }, + { + "name": "microphone_permission_reason", + "value": "microphone permission" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/entry/src/main/resources/zh_CN/element/string.json b/sdk/ohos/har_hap/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..667a360bb0132276be3fac47596c4e27b8adfd61 --- /dev/null +++ b/sdk/ohos/har_hap/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,28 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "WebRTC" + }, + { + "name": "internet_permission_reason", + "value": "网络权限" + }, + { + "name": "camera_permission_reason", + "value": "相机权限" + }, + { + "name": "microphone_permission_reason", + "value": "麦克风权限" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/hvigor/hvigor-config.json5 b/sdk/ohos/har_hap/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8c56bf6a23806e1ce92f72ed34a38613e27c494f --- /dev/null +++ b/sdk/ohos/har_hap/hvigor/hvigor-config.json5 @@ -0,0 +1,21 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "default", /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */ + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/hvigorfile.ts b/sdk/ohos/har_hap/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/sdk/ohos/har_hap/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/sdk/ohos/har_hap/oh-package.json5 b/sdk/ohos/har_hap/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..19d29f652b465e651fd7e099bb75953dc6eca39a --- /dev/null +++ b/sdk/ohos/har_hap/oh-package.json5 @@ -0,0 +1,14 @@ +{ + "modelVersion": "5.0.0", + "name": "webrtc", + "version": "1.0.0", + "description": "WebRTC for OpenHarmony/HarmonyOS", + "main": "", + "author": "", + "license": "", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.13" + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/.gitignore b/sdk/ohos/har_hap/ohos_webrtc/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..dd5671aaf1947b9116191c5554ef430426545607 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/.gitignore @@ -0,0 +1,7 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +/BuildProfile.ets diff --git a/sdk/ohos/har_hap/ohos_webrtc/Index.ets b/sdk/ohos/har_hap/ohos_webrtc/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..21ce64cd67a14f94b89365240df7437f0f424c25 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/Index.ets @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import webrtc from 'libohos_webrtc.so' + +export default webrtc; + +export { Logging, LoggingSeverity } from './src/main/ets/log/Logging'; +export { VideoRenderController } from './src/main/ets/xcomponent/VideoRenderController'; diff --git a/sdk/ohos/har_hap/ohos_webrtc/build-profile.json5 b/sdk/ohos/har_hap/ohos_webrtc/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d5f94ff059e8cbb860c70f1ef484f4bcb3fff717 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/build-profile.json5 @@ -0,0 +1,13 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/hvigorfile.ts b/sdk/ohos/har_hap/ohos_webrtc/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..42187071482d292588ad40babeda74f7b8d97a23 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/hvigorfile.ts @@ -0,0 +1,6 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/oh-package.json5 b/sdk/ohos/har_hap/ohos_webrtc/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5c88cd090294cb4c7b79fe054126ad50401bc9d6 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "ohos_webrtc", + "version": "0.1.0", + "description": "ArkTS interfaces of webrtc for OHOS.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "libohos_webrtc.so": "file:./src/main/libohos_webrtc" + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/log/Logging.ets b/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/log/Logging.ets new file mode 100644 index 0000000000000000000000000000000000000000..567234a2b51d502dfaf2c19cb7b8d8a0629ef9d7 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/log/Logging.ets @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Loggable, NativeLogging} from 'libohos_webrtc.so' + +export enum LoggingSeverity { VERBOSE, INFO, WARNING, ERROR, NONE } + +export class Logging { + private static loggingEnabled: boolean = false; + private static loggable: Loggable | null; + private static loggableSeverity: LoggingSeverity = LoggingSeverity.ERROR; + + static injectLoggable(injectedLoggable: Loggable, severity: LoggingSeverity) { + if (injectedLoggable != null) { + Logging.loggable = injectedLoggable; + Logging.loggableSeverity = severity; + NativeLogging.injectLoggable(injectedLoggable, severity); + } + } + + static deleteInjectedLoggable() { + Logging.loggable = null; + NativeLogging.deleteLoggable(); + } + + static enableLogThreads() { + NativeLogging.enableLogThreads(); + } + + static enableLogTimeStamps() { + NativeLogging.enableLogTimeStamps(); + } + + static enableLogToDebugOutput(severity: LoggingSeverity) { + if (Logging.loggable != null) { + throw new Error("Logging to native debug output not supported while Loggable is injected." + + "Delete the Loggable before calling this method."); + } + NativeLogging.enableLogToDebugOutput(severity); + Logging.loggingEnabled = true; + } + + static log(severity: LoggingSeverity, tag: string, message: string) { + if (Logging.loggable) { + // Filter log messages below loggableSeverity. + if (severity < Logging.loggableSeverity) { + return; + } + + Logging.loggable.logMessage(message, severity, tag); + return; + } + + // Try native logging if no loggable is injected. + if (Logging.loggingEnabled) { + NativeLogging.log(message, severity, tag); + return; + } + + // Fallback to system + switch (severity) { + case LoggingSeverity.ERROR: + console.error(tag, message); + break; + case LoggingSeverity.WARNING: + console.warn(tag, message); + break; + case LoggingSeverity.INFO: + console.info(tag, message); + break; + default: + console.debug(tag, message); + break; + } + } + + static d(tag: string, message: string) { + Logging.log(LoggingSeverity.INFO, tag, message); + } + + static e(tag: string, message: string, e?: Error) { + Logging.log(LoggingSeverity.ERROR, tag, message); + if (e != undefined) { + Logging.log(LoggingSeverity.ERROR, tag, e.message); + // Logging.log(LoggingSeverity.ERROR, tag, e.stack); + } + } + + static w(tag: string, message: string, e: Error) { + Logging.log(LoggingSeverity.WARNING, tag, message); + if (e != undefined) { + Logging.log(LoggingSeverity.WARNING, tag, e.message); + // Logging.log(LoggingSeverity.WARNING, tag, e.stack); + } + } + + static v(tag: string, message: string) { + Logging.log(LoggingSeverity.VERBOSE, tag, message); + } + +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/xcomponent/VideoRenderController.ets b/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/xcomponent/VideoRenderController.ets new file mode 100644 index 0000000000000000000000000000000000000000..03bdd02a7b75a65094998dcd9c395fb768d3872b --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/ets/xcomponent/VideoRenderController.ets @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NativeVideoRenderer, MediaStreamTrack } from 'libohos_webrtc.so'; +import { Logging } from '../log/Logging' + +const TAG: string = '[XComponentController1]'; + +export class VideoRenderController extends XComponentController { + private renderer: NativeVideoRenderer = new NativeVideoRenderer(); + + setVideoTrack(track: MediaStreamTrack | null): void { + this.renderer.setVideoTrack(track); + } + + onSurfaceCreated(surfaceId: string): void { + Logging.d(TAG, 'onSurfaceCreated surfaceId: ' + surfaceId); + this.renderer.init(surfaceId); + } + + onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void + { + Logging.d(TAG, 'onSurfaceChanged surfaceId: ' + surfaceId); + Logging.d(TAG, 'onSurfaceChanged rect: ' + rect); + } + + onSurfaceDestroyed(surfaceId: string): void + { + Logging.d(TAG, 'onSurfaceDestroyed surfaceId: ' + surfaceId); + this.renderer.release(); + } +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/index.d.ts b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..9164e828c019ed25c6325b8554e9e130e5e330b3 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/index.d.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './webrtc' diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/oh-package.json5 b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..044495fa4a5f44e2a15bd45808c46899ed91e8ad --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/oh-package.json5 @@ -0,0 +1,6 @@ +{ + "name": "libohos_webrtc.so", + "types": "./index.d.ts", + "version": "1.0.0", + "description": "Exported ArkTS interface for native c++ webrtc." +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/webrtc.d.ts b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/webrtc.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..309f4910bde5797c311e8d372d8e934c96a6dd85 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/libohos_webrtc/webrtc.d.ts @@ -0,0 +1,1022 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type RTCErrorDetailType = "data-channel-failure" | "dtls-failure" | "fingerprint-failure" | "hardware-encoder-error" | "hardware-encoder-not-available" | "sctp-failure" | "sdp-syntax-error"; +export type RTCIceProtocol = "tcp" | "udp"; +export type RTCIceCandidateType = "host" | "prflx" | "relay" | "srflx"; +export type RTCIceTcpCandidateType = "active" | "passive" | "so"; +export type RTCIceComponent = "rtp" | "rtcp"; +export type RTCIceGathererState = "complete" | "gathering" | "new"; +export type RTCIceTransportState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new"; +export type RTCIceRole = "unknown" | "controlling" | "controlled"; +export type RTCSdpType = 'offer' | 'answer' | "pranswer" | "rollback"; +export type BinaryType = "blob" | "arraybuffer"; +export type DataChannelState = "closed" | "closing" | "connecting" | "open"; +export type RTCDtlsTransportState = "new" | "connecting" | "connected" | "closed" | "failed"; +export type RTCIceGatheringState = "new" | "gathering" | "complete"; +export type RTCIceConnectionState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new"; +export type RTCSignalingState = "closed" | "have-local-offer" | "have-local-pranswer" | "have-remote-offer" | "have-remote-pranswer" | "stable"; +export type RTCPeerConnectionState = "closed" | "connected" | "connecting" | "disconnected" | "failed" | "new"; +export type RTCBundlePolicy = "balanced" | "max-bundle" | "max-compat"; +export type RTCRtcpMuxPolicy = "require"; +export type RTCIceTransportPolicy = "all" | "relay"; +export type DegradationPreference = "balanced" | "maintain-framerate" | "maintain-resolution"; +// export type RTCPriorityType = "high" | "low" | "medium" | "very-low"; +export type RTCRtpTransceiverDirection = "inactive" | "recvonly" | "sendonly" | "sendrecv" | "stopped"; +export type RTCSctpTransportState = "connecting" | "connected" | "closed"; +export type RTCStatsType = "candidate-pair" | "certificate" | "codec" | "data-channel" | "inbound-rtp" | "local-candidate" | "media-playout" | "media-source" | "outbound-rtp" | "peer-connection" | "remote-candidate" | "remote-inbound-rtp" | "remote-outbound-rtp" | "transport"; +export type RTCStatsIceCandidatePairState = "failed" | "frozen" | "in-progress" | "inprogress" | "succeeded" | "waiting"; +export type MediaStreamTrackState = 'live' | 'ended'; +export type MediaDeviceKind = 'audioinput' | 'audiooutput' | 'videoinput'; +export type MediaSourceState = 'initializing' | 'live' | 'ended' | 'muted'; +export type VideoFacingModeEnum = "user" | "environment" | "left" | "right"; +export type VideoResizeModeEnum = "none" | "crop-and-scale"; +export type AudioErrorType = 'init' | 'start-exception' | 'start-state-mismatch' | 'general'; +export type AudioState = 'start' | 'stop'; + +export type AlgorithmIdentifier = Algorithm | string; +export type HighResTimeStamp = number; +export type EpochTimeStamp = number; +export type ConstrainBoolean = boolean | ConstrainBooleanParameters; +export type ConstrainULong = number | ConstrainULongRange; +export type ConstrainDouble = number | ConstrainDoubleRange; +export type ConstrainString = string | string[] | ConstrainStringParameters; + +export interface ULongRange { + max?: number; + min?: number; +} + +export interface DoubleRange { + max?: number; + min?: number; +} + +export interface ConstrainBooleanParameters { + exact?: boolean; + ideal?: boolean; +} + +export interface ConstrainStringParameters { + exact?: string | string[]; + ideal?: string | string[]; +} + +export interface ConstrainDoubleRange extends DoubleRange { + exact?: number; + ideal?: number; +} + +export interface ConstrainULongRange extends ULongRange { + exact?: number; + ideal?: number; +} + +// event +// base class of events +export interface Event { + readonly type: string; +} + +// declare var Event: { +// prototype: Event; +// new(): Event; +// }; + +export interface EventTarget { + // empty for now +} + +// error +// https://www.w3.org/TR/webrtc/#rtcerrorinit-dictionary +export interface RTCErrorInit { + errorDetail: RTCErrorDetailType; + sdpLineNumber?: number; + sctpCauseCode?: number; + receivedAlert?: number; + sentAlert?: number; +} + +// https://www.w3.org/TR/webrtc/#rtcerror-interface +export interface RTCError extends /*DOMException*/ Error { + readonly errorDetail: RTCErrorDetailType; + readonly sdpLineNumber?: number; + readonly sctpCauseCode?: number; + readonly receivedAlert?: number; + readonly sentAlert?: number; +} + +// https://www.w3.org/TR/webrtc/#rtcerrorevent-interface +export interface RTCErrorEvent extends Event { + readonly error: RTCError; +} + +// https://www.w3.org/TR/webrtc/#rtctrackevent +export interface RTCTrackEvent extends Event { + readonly receiver: RTCRtpReceiver; + readonly track: MediaStreamTrack; + readonly streams: ReadonlyArray; + readonly transceiver: RTCRtpTransceiver; +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceevent +export interface RTCPeerConnectionIceEvent extends Event { + readonly candidate?: RTCIceCandidate; + readonly url?: string | null; +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnectioniceerrorevent +export interface RTCPeerConnectionIceErrorEvent extends Event { + readonly address?: string; + readonly port?: number; + readonly url?: string; + readonly errorCode?: number; + readonly errorText?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcdatachannelevent +export interface RTCDataChannelEvent extends Event { + readonly channel: RTCDataChannel; +} + +// https://www.w3.org/TR/webrtc/#rtcdtmftonechangeevent +export interface RTCDTMFToneChangeEvent extends Event { + readonly tone: string; +} + +// https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interface +export interface MessageEvent extends Event { + readonly data: T; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastreamtrackevent +export interface MediaStreamTrackEvent extends Event { + readonly track: MediaStreamTrack; +} + +export interface VideoCapturerStartedEvent extends Event { + readonly success: boolean; +} + +// https://www.w3.org/TR/WebCryptoAPI/#algorithm +export interface Algorithm { + name: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtptransceiverinit +export interface RTCRtpTransceiverInit { + direction?: RTCRtpTransceiverDirection; + streams?: MediaStream[]; + sendEncodings?: RTCRtpEncodingParameters[]; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcsessiondescriptioninit +export interface RTCSessionDescriptionInit { + sdp?: string; + type: RTCSdpType; +} + +// https://www.w3.org/TR/webrtc/#rtcsessiondescription-class +export interface RTCSessionDescription { + readonly sdp: string; + readonly type: RTCSdpType; + + toJSON(): RTCSessionDescriptionInit; +} + +declare var RTCSessionDescription: { + prototype: RTCSessionDescription; + new(descriptionInitDict: RTCSessionDescriptionInit): RTCSessionDescription; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcicecandidateinit +export interface RTCIceCandidateInit { + candidate: string; + sdpMLineIndex?: number; + sdpMid?: string; + usernameFragment?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcicecandidate-interface +export interface RTCIceCandidate { + readonly candidate: string; + readonly sdpMid?: string; + readonly sdpMLineIndex?: number; + readonly foundation?: string; + readonly component?: RTCIceComponent; + readonly priority?: number; + readonly address?: string; + readonly protocol?: RTCIceProtocol; + readonly port?: number; + readonly type?: RTCIceCandidateType; + readonly tcpType?: RTCIceTcpCandidateType; + readonly relatedAddress?: string; + readonly relatedPort?: number; + readonly usernameFragment?: string; + + toJSON(): RTCIceCandidateInit; +} + +declare var RTCIceCandidate: { + prototype: RTCIceCandidate; + new(candidateInitDict?: RTCIceCandidateInit): RTCIceCandidate; +}; + +// https://www.w3.org/TR/webrtc/#rtcdatachannel +export interface RTCDataChannel { + readonly label: string; + readonly ordered: boolean; + readonly maxPacketLifeTime?: number; + readonly maxRetransmits?: number; + readonly protocol: string; + readonly negotiated: boolean; + readonly id?: number; + readonly readyState: DataChannelState; + readonly bufferedAmount: number; + bufferedAmountLowThreshold: number; + binaryType: BinaryType; + + onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null; + onclose: ((this: RTCDataChannel, ev: Event) => any) | null; + onclosing: ((this: RTCDataChannel, ev: Event) => any) | null; + onopen: ((this: RTCDataChannel, ev: Event) => any) | null; + onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null; + onerror: ((this: RTCDataChannel, ev: RTCErrorEvent) => any) | null; + + close(): void; + send(data: string): void; + send(data: ArrayBuffer): void; +} + +declare var RTCDataChannel: { + prototype: RTCDataChannel; + new(): RTCDataChannel; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit +export interface RTCDataChannelInit { + ordered?: boolean; + maxPacketLifeTime?: number; + maxRetransmits?: number; + protocol?: string; + negotiated?: boolean; + id?: number; +} + +// https://www.w3.org/TR/webrtc/#configuration +export interface RTCConfiguration { + iceServers?: RTCIceServer[]; + iceTransportPolicy?: RTCIceTransportPolicy; + bundlePolicy?: RTCBundlePolicy; + rtcpMuxPolicy?: RTCRtcpMuxPolicy; + certificates?: RTCCertificate[]; + iceCandidatePoolSize?: number; + // tcpCandidatePolicy + // continualGatheringPolicy +} + +// https://www.w3.org/TR/webrtc/#dom-rtciceserver +export interface RTCIceServer { + urls: string | string[]; + username?: string; + credential?: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcofferansweroptions +export interface RTCOfferAnswerOptions { +} + +// https://www.w3.org/TR/webrtc/#dom-rtcofferoptions +export interface RTCOfferOptions extends RTCOfferAnswerOptions { + iceRestart?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcansweroptions +export interface RTCAnswerOptions extends RTCOfferAnswerOptions { +} + +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions-0 +// https://www.w3.org/TR/webrtc/#rtcpeerconnection-interface-extensions-1 +export interface RTCPeerConnection extends EventTarget { + readonly canTrickleIceCandidates?: boolean; + readonly signalingState: RTCSignalingState; + readonly iceGatheringState: RTCIceGatheringState; + readonly iceConnectionState: RTCIceConnectionState; + readonly connectionState: RTCPeerConnectionState; + readonly localDescription?: RTCSessionDescription; + readonly remoteDescription?: RTCSessionDescription; + readonly currentLocalDescription?: RTCSessionDescription; + readonly currentRemoteDescription?: RTCSessionDescription; + readonly pendingLocalDescription?: RTCSessionDescription; + readonly pendingRemoteDescription?: RTCSessionDescription; + readonly sctp?: RTCSctpTransport; + + onnegotiationneeded: ((this: RTCPeerConnection, ev: Event) => any) | null; + onicecandidate: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceEvent) => any) | null; + onicecandidateerror: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceErrorEvent) => any) | null; + oniceconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onicegatheringstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onsignalingstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + onconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null; + ontrack: ((this: RTCPeerConnection, ev: RTCTrackEvent) => any) | null; + ondatachannel: ((this: RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null; + + addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender; + removeTrack(sender: RTCRtpSender): void; + setLocalDescription(description?: RTCSessionDescriptionInit): Promise; + setRemoteDescription(description: RTCSessionDescriptionInit): Promise; + createOffer(options?: RTCOfferOptions): Promise; + createAnswer(options?: RTCAnswerOptions): Promise; + createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel; + addIceCandidate(candidate?: RTCIceCandidateInit): Promise; + getSenders(): RTCRtpSender[]; + getReceivers(): RTCRtpReceiver[]; + getTransceivers(): RTCRtpTransceiver[]; + getConfiguration(): RTCConfiguration; + restartIce(): void; + setConfiguration(configuration?: RTCConfiguration): void; + addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver; + close(): void; + getStats(selector?: MediaStreamTrack): Promise; +} + +declare var RTCPeerConnection: { + prototype: RTCPeerConnection; + new(configuration?: RTCConfiguration): RTCPeerConnection; + // https://www.w3.org/TR/webrtc/#sec.cert-mgmt + generateCertificate(keygenAlgorithm: AlgorithmIdentifier): Promise; +}; + +// https://www.w3.org/TR/webrtc/#rtcrtpreceiver-interface +export interface RTCRtpReceiver { + readonly track: MediaStreamTrack; + readonly transport: RTCDtlsTransport | null; + + getParameters(): RTCRtpReceiveParameters; + getStats(): Promise; + getContributingSources(): RTCRtpContributingSource[]; + getSynchronizationSources(): RTCRtpSynchronizationSource[]; +} + +declare var RTCRtpReceiver: { + prototype: RTCRtpReceiver; + new(): RTCRtpReceiver; + getCapabilities(kind: string): RTCRtpCapabilities | null; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodingparameters +export interface RTCRtpCodingParameters { + rid?: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters +export interface RTCRtpEncodingParameters extends RTCRtpCodingParameters { + active?: boolean; + maxBitrate?: number; + maxFramerate?: number; + scaleResolutionDownBy?: number; + // networkPriority?: RTCPriorityType; + // priority?: RTCPriorityType; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodecparameters +export interface RTCRtpCodecParameters { + clockRate: number; + channels?: number; + mimeType: string; + sdpFmtpLine: string; + payloadType: number; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpheaderextensionparameters +export interface RTCRtpHeaderExtensionParameters { + id: number; + uri: string; + encrypted?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtcpparameters +export interface RTCRtcpParameters { + cname?: string; + reducedSize?: boolean; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpparameters +export interface RTCRtpParameters { + codecs: RTCRtpCodecParameters[]; + headerExtensions: RTCRtpHeaderExtensionParameters[]; + rtcp: RTCRtcpParameters; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpsendparameters +export interface RTCRtpSendParameters extends RTCRtpParameters { + // degradationPreference?: DegradationPreference; + encodings: RTCRtpEncodingParameters[]; + transactionId: string; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpreceiveparameters +export interface RTCRtpReceiveParameters extends RTCRtpParameters { +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcontributingsource +export interface RTCRtpContributingSource { + timestamp: HighResTimeStamp; + source: number; + audioLevel?: number; + rtpTimestamp: number; +} + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpsynchronizationsource +export interface RTCRtpSynchronizationSource extends RTCRtpContributingSource { +} + +// https://www.w3.org/TR/webrtc/#rtcrtpsender-interface +// https://www.w3.org/TR/webrtc/#rtcrtpsender-interface-extensions +export interface RTCRtpSender { + readonly track: MediaStreamTrack | null; + readonly transport: RTCDtlsTransport | null; + readonly dtmf: RTCDTMFSender | null; + + setParameters(parameters: RTCRtpSendParameters): Promise; + getParameters(): RTCRtpSendParameters; + replaceTrack(withTrack: MediaStreamTrack | null): Promise; + setStreams(...streams: MediaStream[]): void; + getStats(): Promise; +} + +declare var RTCRtpSender: { + prototype: RTCRtpSender; + new(): RTCRtpSender; + /** + * get the most optimistic view of the capabilities of the system for sending media of the given kind. + * @param kind 'audio' or 'video'. + * @returns instance of RTCRtpCapabilities, or null if has no capabilities corresponding to the value of the kind argument. + */ + getCapabilities(kind: string): RTCRtpCapabilities | null; +}; + +// https://www.w3.org/TR/webrtc/#dom-rtcrtpcodec +export interface RTCRtpCodec { + mimeType: string; + clockRate: number; + channels?: number; + sdpFmtpLine?: string; +} + +// https://www.w3.org/TR/webrtc/#rtcrtpheaderextensioncapability +export interface RTCRtpHeaderExtensionCapability { + uri: string; +} + +// https://www.w3.org/TR/webrtc/#rtcrtpcapabilities +export interface RTCRtpCapabilities { + codecs: RTCRtpCodec[]; + headerExtensions: RTCRtpHeaderExtensionCapability[]; +} + +// https://www.w3.org/TR/webrtc/#rtcdtmfsender +export interface RTCDTMFSender extends EventTarget { + readonly canInsertDTMF: boolean; + readonly toneBuffer: string; + + ontonechange: ((this: RTCDTMFSender, ev: RTCDTMFToneChangeEvent) => any) | null; + + insertDTMF(tones: string, duration?: number, interToneGap?: number): void; +} + +declare var RTCDTMFSender: { + prototype: RTCDTMFSender; + new(): RTCDTMFSender; +}; + +// https://www.w3.org/TR/webrtc/#rtcrtptransceiver-interface +export interface RTCRtpTransceiver { + readonly mid: string | null; + readonly sender: RTCRtpSender; + readonly receiver: RTCRtpReceiver; + direction: RTCRtpTransceiverDirection; + readonly currentDirection: RTCRtpTransceiverDirection | null; + + stop(): void; + setCodecPreferences(codecs: RTCRtpCodec[]): void; +} + +declare var RTCRtpTransceiver: { + prototype: RTCRtpTransceiver; + new(): RTCRtpTransceiver; +}; + +// https://www.w3.org/TR/webrtc/#rtcdtlstransport-interface +export interface RTCDtlsTransport extends EventTarget { + readonly iceTransport: RTCIceTransport; + readonly state: RTCDtlsTransportState; + + onstatechange: ((this: RTCDtlsTransport, ev: Event) => any) | null; + onerror: ((this: RTCDtlsTransport, ev: RTCErrorEvent) => any) | null; + + getRemoteCertificates(): ArrayBuffer[]; +} + +declare var RTCDtlsTransport: { + prototype: RTCDtlsTransport; + new(): RTCDtlsTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcdtlsfingerprint +export interface RTCDtlsFingerprint { + algorithm?: string; + value?: string; +} + +// https://www.w3.org/TR/webrtc/#rtccertificate-interface +export interface RTCCertificate { + readonly expires: EpochTimeStamp; + + getFingerprints(): RTCDtlsFingerprint[]; +} + +declare var RTCCertificate: { + prototype: RTCCertificate; + new(): RTCCertificate; +}; + +// https://www.w3.org/TR/webrtc/#rtcicetransport +export interface RTCIceTransport extends EventTarget { + readonly role: RTCIceRole; + readonly component: RTCIceComponent; + readonly state: RTCIceTransportState; + readonly gatheringState: RTCIceGathererState; + + onstatechange: ((this: RTCIceTransport, ev: Event) => any) | null; + ongatheringstatechange: ((this: RTCIceTransport, ev: Event) => any) | null; + onselectedcandidatepairchange: ((this: RTCIceTransport, ev: Event) => any) | null; + + getSelectedCandidatePair(): RTCIceCandidatePair | null; +} + +declare var RTCIceTransport: { + prototype: RTCIceTransport; + new(): RTCIceTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcicecandidatepair +export interface RTCIceCandidatePair { + local?: RTCIceCandidate; + remote?: RTCIceCandidate; +} + +// https://www.w3.org/TR/webrtc/#rtcsctptransport-interface +export interface RTCSctpTransport extends EventTarget { + readonly maxChannels?: number; + readonly maxMessageSize: number; + readonly state: RTCSctpTransportState; + readonly transport: RTCDtlsTransport; + + onstatechange: ((this: RTCSctpTransport, ev: Event) => any) | null; +} + +declare var RTCSctpTransport: { + prototype: RTCSctpTransport; + new(): RTCSctpTransport; +}; + +// https://www.w3.org/TR/webrtc/#rtcstats-dictionary +export interface RTCStats { + timestamp: HighResTimeStamp; + type: RTCStatsType; + id: string; +} + +// https://www.w3.org/TR/webrtc/#rtcstatsreport-object +export interface RTCStatsReport { + readonly stats: Map; + // readonly timestamp: HighResTimeStamp; // android + // forEach(callback: (value: RTCStats, key: string, parent: RTCStatsReport) => void): void; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtctransportstats +export interface RTCTransportStats extends RTCStats { + packetsSent?: number; + packetsReceived?: number; + bytesSent?: number; + bytesReceived?: number; + iceRole?: RTCIceRole; + iceLocalUsernameFragment?: string; + dtlsState: RTCDtlsTransportState; + iceState?: RTCIceTransportState; + selectedCandidatePairId?: string; + localCertificateId?: string; + remoteCertificateId?: string; + tlsVersion?: string; + dtlsCipher?: string; + // dtlsRole?: RTCDtlsRole; + srtpCipher?: string; + selectedCandidatePairChanges: number; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtcrtpstreamstats +export interface RTCRtpStreamStats extends RTCStats { + ssrc: number; + kind: string; + transportId?: string; + codecId?: string; +} + +// https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats +export interface RTCIceCandidatePairStats extends RTCStats { + transportId: string; + localCandidateId: string; + remoteCandidateId: string; + state: RTCStatsIceCandidatePairState; + nominated?: boolean; + packetsSent?: number; + packetsReceived?: number; + bytesSent?: number; + bytesReceived?: number; + lastPacketSentTimestamp?: HighResTimeStamp; + lastPacketReceivedTimestamp?: HighResTimeStamp; + totalRoundTripTime?: number; + currentRoundTripTime?: number; + availableOutgoingBitrate?: number; + availableIncomingBitrate?: number; + requestsReceived?: number; + requestsSent?: number; + responsesReceived?: number; + responsesSent?: number; + consentRequestsSent?: number; + packetsDiscardedOnSend?: number; + bytesDiscardedOnSend?: number; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-capabilities +export interface MediaTrackCapabilities { + width?: ULongRange; + height?: ULongRange; + aspectRatio?: DoubleRange; + frameRate?: DoubleRange; + facingMode?: string[]; + resizeMode?: string[]; + sampleRate?: ULongRange; + sampleSize?: ULongRange; + echoCancellation?: boolean[]; + autoGainControl?: boolean[]; + noiseSuppression?: boolean[]; + latency?: DoubleRange; + channelCount?: ULongRange; + deviceId?: string; + groupId?: string; +} + +export interface MediaTrackConstraintSet { + width?: ConstrainULong; + height?: ConstrainULong; + aspectRatio?: ConstrainDouble; + frameRate?: ConstrainDouble; + facingMode?: ConstrainString; + resizeMode?: ConstrainString; + sampleRate?: ConstrainULong; + sampleSize?: ConstrainULong; + echoCancellation?: ConstrainBoolean; + autoGainControl?: ConstrainBoolean; + noiseSuppression?: ConstrainBoolean; + latency?: ConstrainDouble; + channelCount?: ConstrainULong; + deviceId?: ConstrainString; + groupId?: ConstrainString; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-constraints +export interface MediaTrackConstraints extends MediaTrackConstraintSet { + advanced?: MediaTrackConstraintSet[]; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-settings +export interface MediaTrackSettings { + width?: number; + height?: number; + aspectRatio?: number; + frameRate?: number; + facingMode?: string; + resizeMode?: string; + sampleRate?: number; + sampleSize?: number; + echoCancellation?: boolean; + autoGainControl?: boolean; + noiseSuppression?: boolean; + latency?: number; + channelCount?: number; + deviceId?: string; + groupId?: string; +} + +// https://www.w3.org/TR/mediacapture-streams/#media-track-supported-constraints +export interface MediaTrackSupportedConstraints { + width?: boolean; + height?: boolean; + aspectRatio?: boolean; + frameRate?: boolean; + facingMode?: boolean; + resizeMode?: boolean; + sampleRate?: boolean; + sampleSize?: boolean; + echoCancellation?: boolean; + autoGainControl?: boolean; + noiseSuppression?: boolean; + latency?: boolean; + channelCount?: boolean; + deviceId?: boolean; + groupId?: boolean; +} + +export interface MediaStreamConstraints { + // default is false + video?: boolean | MediaTrackConstraints; + // default is false + audio?: boolean | MediaTrackConstraints; +} + +// https://www.w3.org/TR/screen-capture/#displaymediastreamoptions +export interface DisplayMediaStreamOptions { + video?: boolean | MediaTrackConstraints; + audio?: boolean | MediaTrackConstraints; +} + +export interface MediaSource { + readonly state: MediaSourceState; +} + +export interface AudioSource extends MediaSource { + setVolume(volume: number); +} + +export interface VideoSource extends MediaSource { + oncapturerstarted: ((this: VideoSource, ev: VideoCapturerStartedEvent) => any) | null; + oncapturerstopped: ((this: VideoSource, ev: Event) => any) | null; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastreamtrack +export interface MediaStreamTrack extends EventTarget { + readonly kind: string; + readonly id: string; + enabled: boolean; + readonly readyState: MediaStreamTrackState; + + stop(): void; +} + +export declare var MediaStreamTrack: { + prototype: MediaStreamTrack; + new(): MediaStreamTrack; +}; + +export interface AudioTrack extends MediaStreamTrack { +} + +export interface VideoTrack extends MediaStreamTrack { +} + +// https://www.w3.org/TR/mediacapture-streams/#mediastream +export interface MediaStream extends EventTarget { + readonly id: string; + readonly active: boolean; + + addTrack(track: MediaStreamTrack): void; + removeTrack(track: MediaStreamTrack): void; + getTrackById(trackId: string): MediaStreamTrack | null; + getTracks(): MediaStreamTrack[]; + getAudioTracks(): MediaStreamTrack[]; + getVideoTracks(): MediaStreamTrack[]; +} + +declare var MediaStream: { + prototype: MediaStream; + new(): MediaStream; + new(stream: MediaStream): MediaStream; + new(tracks: MediaStreamTrack[]): MediaStream; +}; + +export interface MediaDeviceInfo { + readonly deviceId: string; + readonly kind:MediaDeviceKind; + readonly label: string; + readonly groupId: string; +} + +export interface DeviceChangeEvent extends Event { + readonly devices: ReadonlyArray; + readonly userInsertedDevices: ReadonlyArray; +} + +// https://www.w3.org/TR/mediacapture-streams/#mediadevices +export interface MediaDevices extends EventTarget { + ondevicechange: (this: MediaDevices, event: DeviceChangeEvent) => void | null; + + enumerateDevices(): Promise; + getSupportedConstraints(): MediaTrackSupportedConstraints; + getUserMedia(constraints?: MediaStreamConstraints): Promise; + getDisplayMedia(options?: DisplayMediaStreamOptions): Promise; +} + +declare var MediaDevices: { + prototype: MediaDevices; + new(): MediaDevices; +}; + +export interface NativeVideoRenderer { + readonly surfaceId?: string; + readonly videoTrack?: MediaStreamTrack; + + init(surfaceId: string): void; + setVideoTrack(videoTrack: MediaStreamTrack | null): void; + release(): void; +} + +declare var NativeVideoRenderer: { + prototype: NativeVideoRenderer; + new(): NativeVideoRenderer; +}; + +export interface AudioError extends Error { + readonly type: AudioErrorType; +} + +export interface AudioErrorEvent extends Event { + readonly error: AudioError; +} + +export interface AudioStateChangeEvent extends Event { + readonly state: AudioState; +} + +export interface AudioCapturerSamplesReadyEvent extends Event { + readonly samples: AudioSamples; +} + +export interface AudioDeviceModuleOptions { + // input source. see ohos.multimedia.audio.SourceType, default is SOURCE_TYPE_VOICE_COMMUNICATION. + audioSource?: number; + + // input format. see ohos.multimedia.audio.AudioSampleFormat, default SAMPLE_FORMAT_S16LE. + audioFormat?: number; + + // input sample rate, default is 48000. + inputSampleRate?: number; + + // Control if stereo input should be used or not. The default is mono. + useStereoInput?: boolean; + + // output sample rate, default is 48000. + outputSampleRate?: number; + + // Control if stereo output should be used or not. The default is mono. + useStereoOutput?: boolean; + + // output audio usage. see ohos.multimedia.audio.StreamUsage, default is STREAM_USAGE_VOICE_COMMUNICATION + rendererUsage?: number; + + // enable low latency capturing and rendering, default is false + useLowLatency?: number; + + // Control if the built-in HW acoustic echo canceler should be used or not, default is false. + // It is possible to query support by calling AudioDeviceModule.isBuiltInAcousticEchoCancelerSupported() + useHardwareAcousticEchoCanceler?: boolean; + + // Control if the built-in HW noise suppressor should be used or not, default is false. + // It is possible to query support by calling AudioDeviceModule.isBuiltInNoiseSuppressorSupported() + useHardwareNoiseSuppressor?: boolean; +} + +export interface AudioSamples { + // See ohos.multimedia.audio.AudioSampleFormat + readonly audioFormat: number; + + // See ohos.multimedia.audio.AudioChannel + readonly channelCount: number; + + // See ohos.multimedia.audio.AudioSamplingRate + readonly sampleRate: number; + + // audio data + readonly data: ArrayBuffer; +} + +export interface AudioDeviceModule { + oncapturererror: ((this: any, event: AudioErrorEvent) => void) | null; + oncapturerstatechange: ((this: any, event: AudioStateChangeEvent) => void) | null; + // Called when new audio samples are ready. This should only be set for debug purposes + oncapturersamplesready: ((this: any, event: AudioCapturerSamplesReadyEvent) => void) | null; + onrenderererror: ((this: any, event: AudioErrorEvent) => void) | null; + onrendererstatechange: ((this: any, event: AudioStateChangeEvent) => void) | null; + + setSpeakerMute(mute: boolean): void; + setMicrophoneMute(mute: boolean): void; + setNoiseSuppressorEnabled(enabled: boolean): boolean; +} + +declare var AudioDeviceModule: { + prototype: AudioDeviceModule; + new(options?: AudioDeviceModuleOptions): AudioDeviceModule; + isBuiltInAcousticEchoCancelerSupported(): boolean; + isBuiltInNoiseSuppressorSupported(): boolean; +}; + +// Hold a native webrtc.AudioProcessing instance +export interface AudioProcessing {} + +export interface AudioProcessingFactory { + create(): AudioProcessing; +} + +declare var AudioProcessingFactory: { + prototype: AudioProcessingFactory; + new(): AudioProcessingFactory; +}; + +export interface VideoEncoderFactory {} + +export interface VideoDecoderFactory {} + +export interface HardwareVideoEncoderFactory extends VideoEncoderFactory { + readonly enableH264HighProfile: boolean; +} + +declare var HardwareVideoEncoderFactory: { + prototype: HardwareVideoEncoderFactory; + new(enableH264HighProfile?: boolean): HardwareVideoEncoderFactory; +}; + +export interface SoftwareVideoEncoderFactory extends VideoEncoderFactory { +} + +declare var SoftwareVideoEncoderFactory: { + prototype: SoftwareVideoEncoderFactory; + new(): SoftwareVideoEncoderFactory; +}; + +export interface HardwareVideoDecoderFactory extends VideoDecoderFactory { +} + +declare var HardwareVideoDecoderFactory: { + prototype: HardwareVideoDecoderFactory; + new(): HardwareVideoDecoderFactory; +}; + +export interface SoftwareVideoDecoderFactory extends VideoDecoderFactory { +} + +declare var SoftwareVideoDecoderFactory: { + prototype: SoftwareVideoDecoderFactory; + new(): SoftwareVideoDecoderFactory; +}; + +export interface PeerConnectionFactoryOptions { + adm?: AudioDeviceModule; + videoEncoderFactory?: VideoEncoderFactory; + videoDecoderFactory?: VideoDecoderFactory; + audioProcessing?: AudioProcessing; +} + +export interface PeerConnectionFactory { + createPeerConnection(config: RTCConfiguration): RTCPeerConnection; + createAudioSource(constraints?: MediaTrackConstraints): AudioSource; + createAudioTrack(id: string, source: AudioSource): AudioTrack; + createVideoSource(constraints?: MediaTrackConstraints, isScreencast?: boolean): VideoSource; + createVideoTrack(id: string, source: VideoSource): VideoTrack; + startAecDump(fd: number, max_size_bytes: number): boolean; + stopAecDump(): void; +} + +declare var PeerConnectionFactory: { + prototype: PeerConnectionFactory; + new(options?: PeerConnectionFactoryOptions): PeerConnectionFactory; + getDefault(): PeerConnectionFactory; + setDefault(factory: PeerConnectionFactory): void; +}; + +export interface Loggable { + logMessage(message: string, severity: number, tag: string): void; +} + +export class NativeLogging { + static injectLoggable(loggable: Loggable, severity: number): void; + static deleteLoggable(): void; + static enableLogToDebugOutput(severity): void; + static enableLogThreads(): void; + static enableLogTimeStamps(): void; + static log(message: string, severity: number, tag: string): void; +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/module.json5 b/sdk/ohos/har_hap/ohos_webrtc/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5d42eecfee25a50d2eb14ca93ef6f7f53b96f968 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/module.json5 @@ -0,0 +1,24 @@ +{ + "module": { + "name": "ohos_webrtc", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET", + "reason": "$string:internet" + }, + { + "name" : "ohos.permission.CAMERA", + "reason": "$string:camera" + }, + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:microphone" + } + ] + } +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/base/element/string.json b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..683bb3826b3e4616fe1109e8c1128cc07ab3d92c --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/base/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "internet", + "value": "internet permission" + }, + { + "name": "camera", + "value": "camera permission" + }, + { + "name": "microphone", + "value": "microphone permission" + } + ] +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/en_US/element/string.json b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..683bb3826b3e4616fe1109e8c1128cc07ab3d92c --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/en_US/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "internet", + "value": "internet permission" + }, + { + "name": "camera", + "value": "camera permission" + }, + { + "name": "microphone", + "value": "microphone permission" + } + ] +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/zh_CN/element/string.json b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..683bb3826b3e4616fe1109e8c1128cc07ab3d92c --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + }, + { + "name": "internet", + "value": "internet permission" + }, + { + "name": "camera", + "value": "camera permission" + }, + { + "name": "microphone", + "value": "microphone permission" + } + ] +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Ability.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f184f37713f315b53be28b77edc0be40d8102458 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/DataChannel.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/DataChannel.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f8b9e437ab75e51e5dd06d680635b85b3db29f45 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/DataChannel.test.ets @@ -0,0 +1,501 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const TAG: string = '[DataChannelTest]'; +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +let dataChannel: webrtc.RTCDataChannelInit = { + ordered: true, // 设置为 true 表示消息是有序的 + maxPacketLifeTime: 3000, // 可选,设置数据包的最大生存时间(毫秒) + // maxRetransmits: 5, // 可选,设置最大重传次数 + protocol: "sctp-protocol", // 可选,设置协议 + // negotiated: false, // 默认为 false,表示数据通道是由 SDP 协商创建的 + // id: 1, // 可选,设置数据通道 ID +}; + +async function addTracks() { + const stream = await getMediaStream(); + const tracks = stream.getTracks(); + callee.addTrack(tracks[0], stream); + caller.addTransceiver('audio'); +} + +async function getMediaStream() { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + return new webrtc.MediaStream([audioTrack]); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +// 等待pc.connectionState变为'connected'. +async function listenToConnected(pc: webrtc.RTCPeerConnection) { + while (pc.connectionState != 'connected') { + await new Promise((resolve) => { + pc.onconnectionstatechange = resolve; + }); + pc.onconnectionstatechange = null; + } +} + +async function didRemotePcClose(flag: Boolean) { + addTracks(); + let channel1 = caller.createDataChannel(""); + channel1.onopen = () => { + console.info(TAG, "channel.readyState:", channel1.readyState); + } + let closed = false; + channel1.onclose = () => { + closed = true; + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(5000); + if (flag) { + callee.close(); + } + await sleep(1000); + return closed; +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export default function DataChannelTest() { + describe('DataChannelTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('attribute', 0, async () => { + let channel = caller.createDataChannel("audio", dataChannel); + console.debug(TAG, "channel attribute: " + JSON.stringify(channel)); + await sleep(1000); + expect(channel.label).assertEqual("audio"); + expect(channel.ordered).assertEqual(true); + expect(channel.maxPacketLifeTime).assertEqual(3000); + expect(channel.maxRetransmits).not().assertNull(); + expect(channel.protocol).assertEqual("sctp-protocol"); + expect(channel.negotiated).not().assertNull(); + expect(channel.id).not().assertNull(); + expect(channel.readyState).not().assertNull(); + expect(channel.bufferedAmount).not().assertNull(); + expect(channel.bufferedAmountLowThreshold).not().assertNull(); + expect(channel.binaryType).assertEqual("blob"); + }) + + it("onBufferedAmountLow", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("onBufferedAmountLow"); + console.debug(TAG, "onBufferedAmountLow: " + JSON.stringify(channel)); + let flag: Boolean = false; + channel.onbufferedamountlow = () => { + flag = true; + console.info(TAG, "onBufferedAmountLow on"); + }; + channel.bufferedAmountLowThreshold = 1024; + channel.onopen = () => { + const largeData = new Array(1000).join('A'); + channel.send(largeData); + }; + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(1000); + expect(flag).assertEqual(true); + }) + + it("onclose", 0, async () => { + let channel = caller.createDataChannel("onclose", dataChannel); + console.debug(TAG, "onclose: " + JSON.stringify(channel)); + channel.onclose = () => { + console.info(TAG, "onclose on", channel.readyState); + }; + channel.close(); + await sleep(1000); + console.info(TAG, "channel.readyState:", channel.readyState); + expect(channel.readyState).assertEqual("closed"); + }) + + it("onclosing", 0, async () => { + let channel = caller.createDataChannel("onclosing", dataChannel); + console.debug(TAG, "onclosing: " + JSON.stringify(channel)); + channel.onclosing = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + }; + channel.close(); + }) + + it("onopen", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("onopen"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + expect(channel.readyState).assertEqual("open"); + } + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(5000); + }) + + it("onmessage", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("onmessage"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + channel.send("message from caller"); + } + channel.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "message from callee", event.data); + expect(event.data).assertEqual("channel from callee") + }; + callee.ondatachannel = (event) => { + console.info(TAG, "ondatachannel"); + let receiveDataChannel = event.channel; + receiveDataChannel.onopen = () => { + console.info(TAG, "The channel from callee is open"); + receiveDataChannel.send("channel from callee"); + } + receiveDataChannel.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "message from caller", event.data); + expect(event.data).assertEqual("message from caller") + } + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(5000); + }) + + it("sendWhenClosed", 0, async () => { + let channel = caller.createDataChannel("17", dataChannel); + let onclose = new Promise((resolve, reject) => { + channel.onclose = resolve; + // channel.onerror = reject; + }); + channel.close(); + await onclose; + console.info(TAG, "channel.readyState:", channel.readyState); + try { + channel.send("a"); + } catch (e) { + expect(e).assertInstanceOf("Error"); + } + }) + + it("close", 0, async () => { + let channel = caller.createDataChannel("18", dataChannel); + console.debug(TAG, "close: " + JSON.stringify(channel)); + let flag: Boolean = false; + channel.onclose = () => { + flag = true; + console.info(TAG, "onclose on"); + }; + channel.close(); + await sleep(5000); + expect(flag).assertEqual(true); + }) + + it("sendString", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("sendString"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + channel.send("a"); + } + callee.ondatachannel = (event) => { + let receiveDataChannel = event.channel; + receiveDataChannel.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "message from caller", event.data); + expect(event.data).assertEqual("a") + } + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(5000); + }) + + it("sendArrayBuffer", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("sendArrayBuffer"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + const buffer = new ArrayBuffer(1); + const uint8View = new Uint8Array(buffer); + uint8View[0] = 97; + try { + channel.send(buffer); + } catch (e) { + expect().assertFail() + } + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(5000); + }) + + it("onerror", 0, () => { + expect(1).assertEqual(1); + }) + + it("bufferedAmountChange", 0, async () => { + addTracks(); + let channel = caller.createDataChannel(""); + let buffer = "a"; + channel.onopen = () => { + channel.send(buffer); + expect(channel.bufferedAmount).assertEqual(buffer.toString().length); + }; + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + expect(channel.bufferedAmount).assertEqual(0); + }) + + it("sendWhenReadyStateIsConnecting", 0, () => { + let channel = caller.createDataChannel("sendWhenReadyStateConnecting"); + try { + console.info(TAG, "channel.readyState:", channel.readyState); + channel.send("a"); + } catch (e) { + expect(e).assertInstanceOf("Error"); + } + }) + + it("sendWhenBufferIsEmpty", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("sendWhenBufferIsEmpty"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + const buffer = new ArrayBuffer(10); + channel.send(buffer); + } + callee.ondatachannel = (event) => { + let receiveDataChannel = event.channel; + receiveDataChannel.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "message from caller:", event.data); + if (event.data instanceof ArrayBuffer) { + const uint8View = new Uint8Array(event.data); + console.info(TAG, "ArrayBuffer length:", uint8View.length); + expect(uint8View.length).assertEqual(10); + } + } + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(1000); + }) + + it("sendUnicodeString", 0, async () => { + addTracks(); + let channel = caller.createDataChannel("sendUnicodeString"); + channel.onopen = () => { + console.info(TAG, "channel.readyState:", channel.readyState); + const unicodeString = "你好"; + channel.send(unicodeString); + } + callee.ondatachannel = (event) => { + let receiveDataChannel = event.channel; + receiveDataChannel.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "message from caller: ", event.data); + expect(event.data).assertEqual("你好"); + } + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(1000); + }) + + it("sendBeforeCloseChannel", 0, async () => { + addTracks(); + let channel1 = caller.createDataChannel("sendBeforeCloseChannel"); + let channel2: webrtc.RTCDataChannel; + let receivedSize = 0, sentSize = 0; + const largeString = " ".repeat(64 * 1024); + // channel2.binaryType = "arraybuffer"; + + callee.ondatachannel = async (event) => { + channel2 = event.channel; + channel2.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "receivedSize: " + receivedSize, " sentSize: " + sentSize); + receivedSize += event.data.length; + } + channel2.onclose = () => { + console.info(TAG, "channel.readyState:", channel2.readyState); + } + } + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await sleep(1000); + channel1.onopen = async () => { + try { + channel1.send(largeString); + sentSize += largeString.length; + channel1.close(); + } catch (e) { + console.info(TAG, "e: ", e); + } + } + await sleep(1000); + console.info(TAG, "after close receivedSize: " + receivedSize, " sentSize: " + sentSize); + expect(receivedSize).assertEqual(sentSize); + }) + + it("setNegotiatedFalse", 0, async () => { + let channel1 = caller.createDataChannel('', { + negotiated: false, + id: 42 + }); + + channel1.onopen = () => { + channel1.send("a"); + } + + let channel2 = callee.createDataChannel('', { + negotiated: false, + id: 42 + }); + + expect(channel1.id).assertUndefined(); + expect(channel2.id).assertUndefined(); + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + expect(channel1.id).not().assertEqual(42); + expect(channel2.id).not().assertEqual(42); + }) + + it("restartIceChannelIsOpen", 0, async () => { + addTracks(); + let channel1 = caller.createDataChannel(""); + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + + caller.restartIce(); + + channel1.onopen = () => { + channel1.send("a"); + } + + callee.ondatachannel = (event) => { + event.channel.onmessage = (e: webrtc.MessageEvent) => { + console.info(TAG, "e: ", e.data.toString()); + expect(e.data.toString()).assertEqual("a"); + } + } + await sleep(1000); + }) + + it("ChannelWhenPcClose", 0, async () => { + let res = await didRemotePcClose(true); + expect(res).assertEqual(true); + }) + + it("ChannelWhenPcOpen", 0, async () => { + let res = await didRemotePcClose(false); + expect(res).assertEqual(false); + }) + + it("changeBinaryType", 0, () => { + let channel = caller.createDataChannel(""); + console.info(TAG, "BinaryType: ", channel.binaryType); + channel.binaryType = "arraybuffer"; + expect(channel.binaryType).assertEqual("arraybuffer"); + }) + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Helper.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Helper.ets new file mode 100644 index 0000000000000000000000000000000000000000..9e3e8f38478fc7456c91a01a14fc45006a44f3c8 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/Helper.ets @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@ohos/hypium'; + +export type ValueType = number | string | boolean | bigint | object | null | undefined; + +export function assertOptionalBoolean(value: ValueType) { + if(value !== undefined) { + assertBoolean(value); + } +} + +export function assertOptionalNumber(value: ValueType) { + if(value !== undefined) { + assertNumber(value); + } +} + +export function assertOptionalInt(value: ValueType) { + if(value !== undefined) { + assertInt(value); + } +} + +export function assertOptionalUnsignedInt(value: ValueType) +{ + if(value !== undefined) { + assertUnsignedInt(value); + } +} + +export function assertOptionalString(value: ValueType) { + if(value !== undefined) { + assertString(value); + } +} + +export function assertBoolean(value: ValueType) { + expect(typeof value).assertEqual('boolean'); +} + +function assertNumber(value: ValueType) { + expect(typeof value).assertEqual('number'); +} + +function assertInt(value: ValueType) { + expect(Number.isInteger(value)).assertTrue(); +} + +export function assertUnsignedInt(value: ValueType) { + expect(Number.isInteger(value) && (value! >= 0)).assertTrue(); +} + +export function assertString(value: ValueType) { + expect(typeof value).assertEqual('string'); +} + +export function assertArray(value: ValueType) { + expect(Array.isArray(value)).assertTrue(); +} + +export function assertObject(value: ValueType) { + expect(typeof value).assertEqual('object'); + expect(value).not().assertNull(); +} + +export function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/List.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..7fe645a060e98d651f1a47a6901fce634ed86786 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import abilityTest from './Ability.test'; +import PeerConnectionTest from './PeerConnection.test'; +import RTCPeerConnectionGetStatsTest from './RTCPeerConnection-getStats.test'; +import RTCRtpReceiverTest from './RTCRtpReceiver.test'; +import RTCRtpSenderTest from './RTCRtpSender.test'; +import RTCRtpTransceiverTest from './RTCRtpTransceiver.test'; +import DataChannelTest from './DataChannel.test'; +import RTCDtmfSenderTest from './RTCDtmfSender.test'; +import RTCSctpTransportTest from './RTCSctpTransport.test'; +import RTCCertificateTest from './RTCCertificate.test'; +import RTCIceTransportTest from './RTCIceTransport.test'; +import RTCDtlsTransportTest from './RTCDtlsTransport.test'; + +export default function testsuite() { + abilityTest(); + PeerConnectionTest(); + RTCRtpSenderTest(); + RTCRtpReceiverTest(); + RTCRtpTransceiverTest(); + RTCPeerConnectionGetStatsTest(); + DataChannelTest(); + RTCDtmfSenderTest(); + RTCSctpTransportTest(); + RTCCertificateTest(); + RTCIceTransportTest(); + RTCDtlsTransportTest(); +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/PeerConnection.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/PeerConnection.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45210fdadea4d7a17d66b9552368398e4f9a4ef6 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/PeerConnection.test.ets @@ -0,0 +1,1406 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[PeerConnectionTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +const sdp = 'v=0\r\n' + + 'o=- 166855176514521964 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n' + + 'a=ice-options:trickle\r\n' + + 'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' + + 'c=IN IP4 0.0.0.0\r\n' + + 'a=rtcp:9 IN IP4 0.0.0.0\r\n' + + 'a=ice-ufrag:someufrag\r\n' + + 'a=ice-pwd:somelongpwdwithenoughrandomness\r\n' + + 'a=fingerprint:sha-256 8C:71:B3:8D:A5:38:FD:8F:A4:2E:A2:65:6C:86:52:BC:E0:6E:94:F2:9F:7C:4D:B5:DF:AF:AA:6F:44:90:8D:F4\r\n' + + 'a=setup:actpass\r\n' + + 'a=rtcp-mux\r\n' + + 'a=mid:mid1\r\n' + + 'a=sendonly\r\n' + + 'a=msid:stream1 track1\r\n' + + 'a=ssrc:1001 cname:some\r\n' + + 'a=rtpmap:111 opus/48000/2\r\n'; + +let offer = { + sdp: "v=0\r\no=- 8926124112408190582 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\n", + type: "offer" +} as webrtc.RTCSessionDescriptionInit; + +let answer = { + sdp: "v=0\r\no=- 1824345164880213868 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS\r\n", + type: "answer" +} as webrtc.RTCSessionDescriptionInit; + +let transceiverInit = { + direction: 'sendrecv', + streams: [], + sendEncodings: [{ + maxBitrate: 100000, + }], +} as webrtc.RTCRtpTransceiverInit; + +let dataChannel: webrtc.RTCDataChannelInit = { + ordered: true, // 设置为 true 表示消息是有序的 + maxPacketLifeTime: 3000, // 可选,设置数据包的最大生存时间(毫秒) + protocol: "sctp-protocol", // 可选,设置协议 +}; + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function generateAudioReceiveOnlyOffer(pc: webrtc.RTCPeerConnection) { + try { + pc.addTransceiver('audio', { + direction: 'recvonly' + }); + return pc.createOffer(); + } catch (e) { + return pc.createOffer({ + iceRestart: true + }); + } +} + +async function generateAnswer(offer: webrtc.RTCSessionDescriptionInit) { + let pc = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + await pc.setRemoteDescription(offer); + let answer = await pc.createAnswer(); + pc.close(); + return answer; +} + +async function generateOffer() { + let pc = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + let offer = await pc.createOffer(); + pc.close(); + return offer; +} + +async function CreatePeerConnection() { + if (!pcf) { + console.error(TAG, 'pcf is not initialized'); + return; + } + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); +} + +async function createSetOffer(options?: webrtc.RTCOfferOptions) { + try { + let offer = await caller?.createOffer(options); + console.info(TAG, 'createOffer: ' + JSON.stringify(offer)); + expect(offer.type).assertEqual("offer"); + } catch (e) { + console.error(TAG, 'createOffer: ' + JSON.stringify(e)); + expect().assertFail(); + } +} + +async function createSetAnswer(options?: webrtc.RTCAnswerOptions) { + try { + let answer = await caller?.createAnswer(options); + console.info(TAG, 'createAnswer: ' + JSON.stringify(answer)); + expect(answer.type).assertEqual("answer"); + } catch (e) { + console.error(TAG, 'createAnswer: ' + JSON.stringify(e)); + expect().assertFail(); + } +} + +export default function PeerConnectionTest() { + + describe('PeerConnectionTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + CreatePeerConnection(); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it('addAndRemoveTrack', DEFAULT, () => { + let audioSource = pcf?.createAudioSource(); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + expect(audioTrack.kind).assertEqual("audio"); + let rtpSender = caller.addTrack(audioTrack); + + expect(caller.getSenders().length).assertEqual(1); + console.info(TAG, 'getSenders: ' + caller.getSenders().length); + try { + // removeTrack后,不能通过senders的长度来判断,因为长度不变 + caller?.removeTrack(rtpSender); + expect(caller.getSenders().length).assertEqual(1); + expect(caller.getSenders()[0].track).assertNull(); + } catch (e) { + console.error(TAG, 'Error while removing track: ', JSON.stringify(e)); + expect().assertFail(); + } + }) + + it("addIceCandidate", DEFAULT, async () => { + let candidate: webrtc.RTCIceCandidate | undefined = undefined; + caller.onicecandidate = (event) => { + console.info(TAG, 'onicecandidate: ', event.candidate); + candidate = event.candidate; + }; + caller.onicegatheringstatechange = (event) => { + console.info(TAG, 'onicegatheringstatechange: ', event); + }; + + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("id", audioSource); + + caller.addTrack(audioTrack); + + let offer = await caller?.createOffer(); + await caller?.setRemoteDescription(offer); + console.info(TAG, 'caller.signalingState changed:', caller.signalingState); + + let answer = await caller?.createAnswer(); + await caller?.setLocalDescription(answer); + console.info(TAG, 'caller.signalingState changed:', caller.signalingState); + + await sleep(1000); + expect(candidate).not().assertUndefined(); + + try { + await caller.addIceCandidate(candidate); + } catch (e) { + console.error(TAG, 'addIceCandidate error :', JSON.stringify(e)); + expect().assertFail(); + } + }) + + it("AddTransceiverWithStringNoInit", DEFAULT, () => { + let tranSceiver = caller?.addTransceiver("audio"); + console.info(TAG, 'addTransceiver: ' + JSON.stringify(tranSceiver)); + expect(caller.getTransceivers().length).assertEqual(1); + }) + + it("AddTransceiverWithTrackNoInit", DEFAULT, () => { + let audioSource = pcf?.createAudioSource({ + echoCancellation: false + }); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + let tranSceiver = caller?.addTransceiver(audioTrack); + console.info(TAG, 'addTransceiver: ' + JSON.stringify(tranSceiver)); + expect(caller.getTransceivers().length).assertEqual(1); + }) + + it("AddTransceiverWithTrackAndInit", DEFAULT, () => { + let audioSource = pcf?.createAudioSource({ + echoCancellation: false + }); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + let tranSceiver = caller?.addTransceiver(audioTrack, transceiverInit); + console.info(TAG, 'addTransceiver: ' + JSON.stringify(tranSceiver)); + expect(caller.getTransceivers().length).assertEqual(1); + }) + + it("AddTransceiverWithStringAndInit", DEFAULT, () => { + let tranSceiver = caller?.addTransceiver("audio", transceiverInit); + console.info(TAG, 'addTransceiver: ' + JSON.stringify(tranSceiver)); + expect(caller.getTransceivers().length).assertEqual(1); + }) + + it("createOffer", DEFAULT, async () => { + createSetOffer(); + }) + + it("createOfferAddOptions", DEFAULT, async () => { + let options: webrtc.RTCOfferOptions = { + iceRestart: true + } + createSetOffer(options); + }) + + it("createAnswer", DEFAULT, async () => { + await caller?.setRemoteDescription(offer); + createSetAnswer(); + }) + + it("createAnswerAddOptions", DEFAULT, async () => { + await caller?.setRemoteDescription(offer); + let options: webrtc.RTCAnswerOptions = {}; + createSetAnswer(options); + }) + + it("setLocalDescriptionWithOffer", DEFAULT, async () => { + await caller?.setLocalDescription(offer); + expect(caller.signalingState).assertEqual("have-local-offer"); + }) + + it("setLocalDescriptionNoOffer", DEFAULT, async () => { + await caller?.setLocalDescription(); + expect(caller.signalingState).assertEqual("have-local-offer"); + expect(caller.pendingLocalDescription).not().assertNull(); + }) + + it("setLocalDescriptionWithAnswer", DEFAULT, async () => { + await caller?.setRemoteDescription(offer); + await caller?.setLocalDescription(answer); + expect(caller.signalingState).assertEqual("stable"); + }) + + it("setLocalDescriptionNoAnswer", DEFAULT, async () => { + await callee.setRemoteDescription(await caller.createOffer()); + await callee.setLocalDescription(); + expect(callee.currentLocalDescription).not().assertNull(); + expect(caller.signalingState).assertEqual("stable"); + }) + + it("setRemoteDescription", DEFAULT, async () => { + expect(caller.signalingState).assertEqual("stable"); + caller.onsignalingstatechange = () => { + console.log(TAG, 'Signaling state change: ', caller.signalingState); + }; + await caller?.setRemoteDescription(offer); + expect(caller.signalingState).assertEqual("have-remote-offer"); + }) + + it("getReceivers", DEFAULT, () => { + let receiver = caller.getReceivers(); + expect(receiver.length).assertEqual(0); + + let audioSource = pcf?.createAudioSource(); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + + let rtpSender = caller.addTrack(audioTrack); + expect(caller.getSenders().length).assertEqual(1); + + let newReceiver = caller.getReceivers(); + expect(newReceiver.length).assertEqual(1); + }) + + it("createDataChannelWithInit", DEFAULT, () => { + let channel = caller.createDataChannel("1", dataChannel); + + expect(channel.label).assertEqual("1"); + expect(channel.ordered).assertTrue(); + expect(channel.maxPacketLifeTime).assertEqual(3000); + expect(channel.protocol).assertEqual("sctp-protocol"); + }) + + it("createDataChannelNoInit", DEFAULT, () => { + let channel = caller.createDataChannel("1"); + + console.info(TAG, 'channel: ', JSON.stringify(channel)); + + expect(channel.label).assertEqual("1"); + expect(channel.ordered).assertTrue(); + }) + + it("setAndGetConfiguration", DEFAULT, async () => { + expect(caller.getConfiguration().iceCandidatePoolSize).assertEqual(0) + caller.setConfiguration(); + let config = caller.getConfiguration(); + + expect(config.iceCandidatePoolSize).assertEqual(0); + + let newConfig: webrtc.RTCConfiguration = { + iceServers: [{ + urls: "stun:stun1.l.google.com:19302" + },], + iceCandidatePoolSize: 10 + }; + caller.setConfiguration(newConfig); + + caller.getConfiguration(); + expect(caller.getConfiguration().iceCandidatePoolSize).assertEqual(10); + + try { + let stat = await caller.getStats(); + console.info(TAG, 'getStats: ' + JSON.stringify(stat)); + } catch (e) { + console.error(TAG, 'getStats error : ', JSON.stringify(e)) + expect().assertFail() + } + }) + + it("restartIce", DEFAULT, async () => { + try { + let flag = false; + caller.onnegotiationneeded = (event) => { + console.info(TAG, "onnegotiationneeded"); + flag = true; + }; + caller.restartIce(); + await sleep(1000); + expect(flag).assertEqual(true); + } catch (e) { + console.error(TAG, "ICE connection state before restart: ", caller.iceConnectionState); + expect().assertFail() + } + }) + + it("close", DEFAULT, () => { + caller.close() + try { + caller.setConfiguration() + } catch (e) { + console.info(TAG, "close:", e) + } + }) + + it("orderOfOnicecandidateAndSetLocalDescription", DEFAULT, async () => { + caller.addTransceiver('audio'); + let events: string[] = []; + let pendingPromises: Promise[] = []; + + caller.onicecandidate = (event) => { + console.info(TAG, 'onicecandidate: ', event.candidate); + }; + let onCandidatePromise = new Promise((resolve, reject) => { + caller.onicecandidate = resolve; + }); + await sleep(1000); + pendingPromises.push(onCandidatePromise.then(() => { + events.push('candidate generated'); + })); + pendingPromises.push(caller.setLocalDescription().then(() => { + events.push('setLocalDescription'); + })); + await Promise.all(pendingPromises); + expect(events).assertDeepEquals(['setLocalDescription', 'candidate generated']); + }) + + it("directionWillChanged", DEFAULT, async () => { + let calleePromise = callee.createOffer().then((offer) => { + return caller.setRemoteDescription(offer); + }).then(() => { + return caller.createAnswer(); + }); + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + + let callerPromise = caller.createOffer().then(() => { + caller.getTransceivers()[0].direction = 'inactive'; + }); + await Promise.all([callerPromise, calleePromise]); + expect(caller.getTransceivers()[0].direction).assertEqual('inactive'); + }) + + it("transceiverSenderTrackDoesNotRevertToAnOldState", DEFAULT, async () => { + let offer = await callee.createOffer(); + await caller.setRemoteDescription(offer); + let answer = await caller.createAnswer(); + await caller.setLocalDescription(answer); + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + await caller.createOffer(); + caller.addTrack(caller.getReceivers()[0].track); + expect(caller.getSenders()[0].track!.id).assertEqual(caller.getReceivers()[0].track.id); + }) + + /*it("orderOfSignalingState", DEFAULT, async () => { + let signalingStates: webrtc.RTCSignalingState[] = []; + + caller.onsignalingstatechange = () => { + console.info(TAG, "add signalingStates: ", caller.signalingState); + signalingStates.push(caller.signalingState); + } + + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer = await caller.createOffer(); + let sldPromise = caller.setLocalDescription(offer); + // offer state为stable or have-remote-offer + // https://www.w3.org/TR/webrtc/#dom-peerconnection-setremotedescription + let srdPromise = caller.setRemoteDescription(offer); + await Promise.all([sldPromise, srdPromise]); + + console.info(TAG, "signalingStates: ", signalingStates.toString()) + // expect(signalingStates).assertDeepEquals(['have-local-offer', 'stable', 'have-remote-offer']); + })*/ + + /*it("stateChanged", DEFAULT, async () => { + let eventCount = 0; + const states = [ + 'stable', 'have-local-offer', 'stable', 'have-remote-offer', + ]; + + caller.onsignalingstatechange = () => { + expect(caller.signalingState).assertEqual(states[++eventCount]); + } + let offer = await generateAudioReceiveOnlyOffer(caller); + console.info(TAG, "signalingStates: ", caller.signalingState) + // expect(caller.signalingState).assertEqual('stable'); + await caller.setLocalDescription(offer); + console.info(TAG, "signalingStates: ", caller.signalingState) + // expect(caller.signalingState).assertEqual('have-local-offer'); + + // offer state为stable or have-remote-offer + // https://www.w3.org/TR/webrtc/#dom-peerconnection-setremotedescription + await caller.setRemoteDescription(offer); + await exchangeAnswer(caller, callee); + console.info(TAG, "signalingStates: ", caller.signalingState) + // expect(caller.signalingState).assertEqual('stable'); + await exchangeOffer(caller, callee); + console.info(TAG, "signalingStates: ", caller.signalingState) + // expect(caller.signalingState).assertEqual('have-remote-offer'); + })*/ + + it("inValidSdp", DEFAULT, async () => { + try { + await caller.setRemoteDescription({ + sdp: 'bogus', type: 'offer' + }); + } catch (e) { + console.error(TAG, "e: ", e); + expect(e).assertInstanceOf("Error"); + } + }) + + it("rollbackInvalidSdp", DEFAULT, async () => { + try { + const offer = await generateAudioReceiveOnlyOffer(caller); + await caller.setRemoteDescription(offer); + await caller.setRemoteDescription({ + type: 'rollback', + sdp: '!;' + }); + } catch (e) { + console.error(TAG, "e: ", e); + expect(e).assertInstanceOf("Error"); + } + }); + + it("localOfferCreatedBeforeSetRemoteDescriptionThenRollbackShouldStillBeUsable", DEFAULT, async () => { + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer1 = await caller.createOffer(); + callee.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer2 = await callee.createOffer(); + try { + await caller.setRemoteDescription(offer2); + await caller.setRemoteDescription({ + type: "rollback" + }); + await caller.setLocalDescription(offer1); + } catch (e) { + console.error(TAG, "e: ", e); + expect().assertFail(); + } + }) + + it("removeTrackWithASenderBeingRolledBackDoesNotCrashOrThrow", DEFAULT, async () => { + callee.addTransceiver('audio'); + let offer = await callee.createOffer(); + await caller.setRemoteDescription(offer); + let transceiver: webrtc.RTCRtpTransceiver[] = caller.getTransceivers(); + caller.setRemoteDescription({ + type: 'rollback' + }); + caller.removeTrack(transceiver[0].sender); + }) + + it("setRemoteDescriptionPranswerWhenStable", DEFAULT, async () => { + let offer = await caller.createOffer(); + console.info(TAG, "signalingState: ", caller.signalingState); + try { + caller.setRemoteDescription({ + type: 'pranswer', sdp: offer.sdp + }); + } catch (e) { + console.error(TAG, "e: ", e); + expect(e).assertEqual("InvalidStateError"); + } + }) + + it("setRemoteDescriptionWithValidOfferShouldSucceed", DEFAULT, async () => { + caller.createDataChannel('datachannel'); + let states: webrtc.RTCSignalingState[] = []; + callee.onsignalingstatechange = () => { + console.info(TAG, "callee.signalingState: ", callee.signalingState); + states.push(callee.signalingState); + }; + let offer = await caller.createOffer(); + await callee.setRemoteDescription(offer); + await sleep(1000); + expect(callee.signalingState).assertEqual('have-remote-offer'); + // expect(callee.remoteDescription).assertDeepEquals(offer); + // expect(callee.pendingRemoteDescription).assertDeepEquals(offer); + expect(callee.currentRemoteDescription).assertUndefined(); + expect(states).assertDeepEquals(['have-remote-offer']); + }) + + it("setRemoteDescriptionWithInvalidSdp", DEFAULT, async () => { + try { + await caller.setRemoteDescription({ + type: 'offer', + sdp: 'Invalid SDP' + }); + console.error(TAG, 'Expect promise to be rejected'); + expect().assertFail(); + } catch (e) { + console.info(TAG, "errorDetail: ", e); + // expect(e.errorDetail).assertEqual('sdp-syntax-error'); + expect(e).assertInstanceOf("Error"); + } + }); + + it("onsignalingstatechangeBeforeSetLocalDescription", DEFAULT, async () => { + const offer = await caller.createOffer(); + let eventSequence = ''; + caller.onsignalingstatechange = () => { + eventSequence += 'onsignalingstatechange;'; + console.info(TAG, "signalingState: ", caller.signalingState); + }; + + await caller.setLocalDescription(offer); + await sleep(1000); + eventSequence += 'setLocalDescription;'; + expect(eventSequence).assertEqual('onsignalingstatechange;setLocalDescription;'); + }); + + it("setLocalDescriptionRollbackFromLocalOffer", DEFAULT, async () => { + let states: webrtc.RTCSignalingState [] = []; + caller.onsignalingstatechange = () => { + console.info(TAG, "caller.signalingState: ", caller.signalingState); + states.push(caller.signalingState); + } + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await sleep(1000); + expect(caller.signalingState).assertEqual('have-local-offer'); + expect(caller.localDescription).not().assertNull(); + expect(caller.pendingLocalDescription).assertDeepEquals(caller.localDescription); + expect(caller.currentLocalDescription).assertUndefined(); + await caller.setLocalDescription({ + type: 'rollback' + }); + await sleep(1000); + expect(caller.signalingState).assertEqual('stable'); + expect(caller.localDescription).assertUndefined(); + expect(caller.pendingLocalDescription).assertUndefined(); + expect(caller.currentLocalDescription).assertUndefined(); + expect(states).assertDeepEquals(['have-local-offer', 'stable']); + }) + + it("setLocalDescriptionRollbackFromState", DEFAULT, async () => { + try { + await caller.setLocalDescription({ + type: 'rollback' + }) + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("setLocalDescriptionRollbackAfterSettingAnswerDescriptionShouldRejectWithInvalidStateError", DEFAULT, + async () => { + let offer = await generateAudioReceiveOnlyOffer(caller); + await caller.setRemoteDescription(offer); + let answer = await caller.createAnswer(); + await caller.setLocalDescription(answer); + try { + await caller.setLocalDescription({ + type: 'rollback' + }); + expect().assertFail(); + } catch (e) { + console.info(TAG, "e: ", e); + } + }) + + it("setLocalDescriptionRollbackIgnoreInvalidSdp", DEFAULT, async () => { + let offer = await caller.createOffer() + caller.setLocalDescription(offer) + try { + caller.setLocalDescription({ + type: 'rollback', + sdp: '!;' + }); + } catch (e) { + expect().assertFail(); + } + }) + + it("setLocalDescriptionRollbackShouldUpdateInternalStateWithAQueuedTaskInTheRightOrder", DEFAULT, async () => { + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + await caller.setLocalDescription(await caller.createOffer()); + expect(caller.signalingState).assertEqual("have-local-offer"); + expect(caller.pendingLocalDescription).not().assertNull(); + // expect(caller.pendingLocalDescription.type).assertEqual("offer"); + expect(caller.pendingLocalDescription).assertDeepEquals(caller.localDescription); + expect(caller.pendingRemoteDescription).assertUndefined() + const sldPromise = caller.setLocalDescription({ + type: "rollback" + }); + const stablePromise = new Promise((resolve, reject) => { + caller.onsignalingstatechange = () => { + resolve(caller.signalingState); + } + }); + let raceValue = await Promise.race([stablePromise, sldPromise]); + expect(caller.signalingState).assertEqual("stable"); + expect(caller.pendingLocalDescription).assertUndefined(); + expect(caller.pendingRemoteDescription).assertUndefined(); + + await sldPromise; + }) + + it("setLocalDescriptionPranswerFromStable", DEFAULT, async () => { + let offer = await caller.createOffer(); + try { + await caller.setLocalDescription({ + type: 'pranswer', sdp: offer.sdp + }); + } catch (e) { + console.error(TAG, "e: ", e); + expect(e).assertInstanceOf("Error"); + } + }) + + it("ParameterlessSldStable", DEFAULT, async () => { + let transceiver = caller.addTransceiver('audio'); + expect(transceiver.mid).assertNull(); + await caller.setLocalDescription(); + expect(transceiver.mid).not().assertNull(); + }) + + it("ParameterlessRemoteoffer", DEFAULT, async () => { + caller.addTransceiver('audio'); + callee.ontrack = () => { + console.info(TAG, "currentDirection: ", callee.getTransceivers()[0].currentDirection); + } + await callee.setRemoteDescription(await caller.createOffer()); + await callee.setLocalDescription(); + + await sleep(5000); + expect(callee.getTransceivers()[0].currentDirection).assertEqual('recvonly'); + }) + + it("exchangeOA", DEFAULT, async () => { + try { + await caller.setLocalDescription(); + await callee.setRemoteDescription(caller.pendingLocalDescription); + + await callee.setLocalDescription(); + await caller.setRemoteDescription(callee.currentLocalDescription); + } catch (e) { + console.error(TAG, "e: ", e); + expect().assertFail(); + } + }) + + it("setLocalDescriptionWithValidOfferShouldSucceed", DEFAULT, async () => { + let states: String = ""; + + let signalingStateChangePromise = new Promise((resolve) => { + caller.onsignalingstatechange = () => { + states = caller.signalingState; + resolve(); + }; + }); + + let offer = await generateAudioReceiveOnlyOffer(caller); + await caller.setLocalDescription(offer); + expect(caller.signalingState).assertEqual('have-local-offer'); + // localDescription与offer相似的比较没有对于的方式 + // expect(caller.localDescription).assertDeepEquals(offer); + expect(JSON.stringify(caller.pendingLocalDescription)).assertEqual(JSON.stringify(caller.localDescription)); + expect(caller.currentLocalDescription).assertUndefined(); + + await signalingStateChangePromise; + + expect(states).assertEqual('have-local-offer'); + }) + + it("localOfferCreatedBeforeSetRemoteDescriptionRemoteOfferThenRollbackShouldStillBeUsable", DEFAULT, async () => { + try { + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer = await caller.createOffer(); + callee.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer2 = await callee.createOffer(); + + await caller.setRemoteDescription(offer2); + await caller.setRemoteDescription({ + type: "rollback" + }); + await caller.setLocalDescription(offer); + } catch (e) { + console.error(TAG, "e: ", e); + expect().assertFail(); + } + }) + + it("offerSetRepeatedly", DEFAULT, async () => { + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + await caller.setLocalDescription(await caller.createOffer()); + + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + const answer = await callee.createAnswer(); + await callee.setLocalDescription(answer); + await caller.setRemoteDescription(answer); + + expect(caller.getTransceivers().length).assertEqual(1); + expect(caller.getTransceivers()[0].receiver.track.kind).assertEqual("audio"); + expect(callee.getTransceivers().length).assertEqual(1); + expect(callee.getTransceivers()[0].receiver.track.kind).assertEqual("audio"); + }) + + it("setLocalDescriptionAnswerStateInvalidStateError", DEFAULT, async () => { + let offer = await caller.createOffer() + console.info(TAG, "signalingState: ", caller.signalingState); + try { + await caller.setLocalDescription({ + type: 'answer', sdp: offer.sdp + }); + expect().assertFail() + } catch (e) { + console.error(TAG, "error: ", e); + } + }) + + it("setLocalDescriptionAnswerWhenLocalOffer", DEFAULT, async () => { + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let answer = await generateAnswer(offer); + console.info(TAG, "signalingState: ", caller.signalingState); + try { + await caller.setLocalDescription(answer); + expect().assertFail() + } catch (e) { + console.error(TAG, "error: ", e); + } + }) + + it("setPreviouslyAnswerCreateOfferShouldWork", DEFAULT, async () => { + try { + caller.addTransceiver('audio', { + direction: 'recvonly' + }); + let offer = await caller.createOffer(); + await callee.setRemoteDescription(offer); + let answer = await callee.createAnswer(); + await callee.setRemoteDescription({ + type: "rollback" + }); + callee.addTransceiver('video', { + direction: 'recvonly' + }); + await callee.createOffer(); + await callee.setRemoteDescription(offer); + await callee.setLocalDescription(answer); + } catch (e) { + console.error(TAG, "e: ", e); + expect().assertFail(); + } + }) + + it("setTransceiverInactive", DEFAULT, async () => { + caller.addTransceiver('audio'); + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + + await callee.setRemoteDescription(offer); + callee.getTransceivers()[0].stop(); + const answer = await callee.createAnswer(); + + await caller.setRemoteDescription(answer); + + expect(caller.getTransceivers()[0].currentDirection).assertEqual('inactive'); + }) + + it("inStateOnnegotiationneededNotPremature", DEFAULT, async () => { + caller.addTransceiver("audio"); + caller.onnegotiationneeded = () => { + console.info(TAG, "signalingState: ", caller.signalingState); + } + await caller.setLocalDescription(await caller.createOffer()); + caller.restartIce(); + caller.onnegotiationneeded = () => { + console.info(TAG, "signalingState: ", caller.signalingState); + } + await callee.setRemoteDescription(caller.localDescription); + await callee.setLocalDescription(await callee.createAnswer()); + await caller.setRemoteDescription(callee.localDescription); + }) + + it("addTransceiverWithInactiveDirectionNotCauseOntrack", DEFAULT, async () => { + caller.addTransceiver('audio', { + direction: 'inactive' + }); + let flag: Boolean = false; + callee.ontrack = () => { + flag = true; + } + await callee.setRemoteDescription(await caller.createOffer()); + await sleep(1000); + expect(flag).assertEqual(false); + }) + + it("negotiationneededFireWhenSignalingBackStateAfterSetRemoteDescription", DEFAULT, async () => { + caller.addTransceiver('audio'); + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let fired = false; + caller.onnegotiationneeded = e => { + fired = true; + } + caller.createDataChannel('test'); + + await caller.setRemoteDescription(await generateAnswer(offer)); + console.info(TAG, "onnegotiationneeded", caller.signalingState); + await sleep(1000); + expect(fired).assertEqual(true); + }) + + it("negotiationneededFireWhenSignalingBackStateAfterSetLocalDescription", DEFAULT, async () => { + caller.addTransceiver('audio'); + let fired = false; + caller.onnegotiationneeded = e => { + fired = true; + } + await caller.setRemoteDescription(await generateOffer()); + caller.createDataChannel('test'); + await caller.setLocalDescription(await caller.createAnswer()); + console.info(TAG, "signalingState: ", caller.signalingState); + await sleep(1000); + expect(fired).assertEqual(true); + }) + + it("UpdatingDirectionTransceiverShouldCauseNegotiationneeded", DEFAULT, async () => { + let transceiver = caller.addTransceiver('audio', { + direction: 'sendrecv' + }); + let onNegotiationNeededPromise = new Promise((resolve, reject) => { + caller.onnegotiationneeded = resolve; + }); + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let answer = await generateAnswer(offer); + await caller.setRemoteDescription(answer); + transceiver.direction = 'recvonly'; + + await onNegotiationNeededPromise; + console.info(TAG, "direction: ", transceiver.direction); + expect(transceiver.direction).assertEqual('recvonly'); + }) + + it("setStreamsShouldCauseNegotiationneeded", DEFAULT, async () => { + let transceiver = caller.addTransceiver('audio', { + direction: 'sendrecv' + }); + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let answer = await generateAnswer(offer); + await caller.setRemoteDescription(answer); + + let stream = new webrtc.MediaStream(); + let flag = false; + caller.onnegotiationneeded = e => { + flag = true; + } + transceiver.sender.setStreams(stream); + await sleep(1000); + expect(flag).assertEqual(true); + }) + + it("setStreamsDifferentStreamShouldCauseNegotiationneeded", DEFAULT, async () => { + let transceiver = caller.addTransceiver('audio', { + direction: 'sendrecv' + }); + let flag = false; + let stream = new webrtc.MediaStream(); + transceiver.sender.setStreams(stream); + await sleep(1000); + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let answer = await generateAnswer(offer); + await caller.setRemoteDescription(answer); + + let stream2 = new webrtc.MediaStream(); + caller.onnegotiationneeded = e => { + flag = true; + } + transceiver.sender.setStreams(stream2); + await sleep(1000); + expect(flag).assertEqual(true); + }) + + it("setStreamsWithAdditionalStreamShouldCauseNegotiationneeded", DEFAULT, async () => { + let transceiver = caller.addTransceiver('audio', { + direction: 'sendrecv' + }); + let flag = false; + let stream = new webrtc.MediaStream(); + transceiver.sender.setStreams(stream); + + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + let answer = await generateAnswer(offer); + await caller.setRemoteDescription(answer); + + let stream2 = new webrtc.MediaStream(); + caller.onnegotiationneeded = e => { + flag = true; + } + transceiver.sender.setStreams(stream, stream2); + await sleep(1000); + expect(flag).assertEqual(true); + }) + + it("sendDataInDatachannelEvent", DEFAULT, async () => { + let message = 'meow meow!'; + + callee.ondatachannel = (event) => { + console.info(TAG, "send data"); + let dc2 = event.channel; + dc2.send(message); + }; + + const dc1 = caller.createDataChannel('fire-me!'); + dc1.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "data: ", event.data); + expect(event.data).assertEqual(message); + }; + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await sleep(1000); + }) + + it("closeChannelNotCauseOnopenInDatachannelEvent", DEFAULT, async () => { + let flag = false; + callee.ondatachannel = async (event) => { + console.info(TAG, "ondatachannel in"); + const dc = event.channel; + dc.onopen = () => { + flag = true; + expect().assertFail(); + }; + dc.close(); + await sleep(500); + }; + caller.createDataChannel('fire-me!'); + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + expect(flag).assertEqual(false); + await sleep(1000); + }) + + it("closeChannelWhenSendNotCauseOnopenInDataChannelEvent", DEFAULT, async () => { + let message = 'meow meow!'; + + callee.ondatachannel = async (event) => { + let dc2 = event.channel; + dc2.onopen = () => { + expect().assertFail(); + }; + dc2.send(message); + dc2.close(); + await sleep(500); + }; + + let dc1 = caller.createDataChannel('fire-me!'); + dc1.onmessage = (event: webrtc.MessageEvent) => { + console.info(TAG, "data: ", event.data); + expect(event.data).assertEqual(message); + }; + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await sleep(1000); + }) + + it("inBandNegotiatedChannelMatchSameConfigurationAsLocalPeer", DEFAULT, async () => { + let dc1 = caller.createDataChannel('test', { + ordered: false, + maxRetransmits: 1, + protocol: 'custom' + }); + expect(dc1.label).assertEqual('test'); + expect(dc1.ordered).assertEqual(false); + expect(dc1.maxPacketLifeTime).assertUndefined(); + expect(dc1.maxRetransmits).assertEqual(1); + expect(dc1.protocol).assertEqual('custom'); + expect(dc1.negotiated).assertEqual(false); + + callee.ondatachannel = async (event) => { + const dc2 = event.channel; + expect(dc2.label).assertEqual('test'); + expect(dc2.ordered).assertEqual(false); + expect(dc2.maxPacketLifeTime).assertUndefined(); + expect(dc2.maxRetransmits).assertEqual(1); + expect(dc2.protocol).assertEqual('custom'); + expect(dc2.negotiated).assertEqual(false); + expect(dc2.id).assertEqual(dc1.id); + await sleep(1000); + }; + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await sleep(1000); + }) + + it("inBandNegotiatedChannelMatchSameDefaultConfigurationAsLocalPeer", DEFAULT, async () => { + let dc1 = caller.createDataChannel(''); + + expect(dc1.label).assertEqual(''); + expect(dc1.ordered).assertEqual(true); + expect(dc1.maxPacketLifeTime).assertUndefined(); + expect(dc1.maxRetransmits).assertUndefined(); + expect(dc1.protocol).assertEqual(''); + expect(dc1.negotiated).assertEqual(false); + + callee.ondatachannel = async (event) => { + let dc2 = event.channel; + + expect(dc2.label).assertEqual(''); + expect(dc2.ordered).assertEqual(true); + expect(dc2.maxPacketLifeTime).assertUndefined(); + expect(dc2.maxRetransmits).assertUndefined(); + expect(dc2.protocol).assertEqual(''); + expect(dc2.negotiated).assertEqual(false); + expect(dc2.id).assertEqual(dc1.id); + await sleep(1000); + } + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await sleep(1000); + }) + + it("negotiatedChannelShouldNotFireDatachannelEventOnRemotePeer", DEFAULT, async () => { + callee.ondatachannel = () => { + console.error(TAG, "datachannel event should not be fired"); + expect().assertFail(); + } + + caller.createDataChannel('test', { + negotiated: true, + id: 42 + }); + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await sleep(500); + }) + + it("initialIceGatheringStateShouldBeNew", DEFAULT, () => { + expect(caller.iceGatheringState).assertEqual('new'); + }) + + it("stateInIceConnectionState", DEFAULT, () => { + expect(caller.iceConnectionState).assertEqual('new'); + caller.close(); + expect(caller.iceConnectionState).assertEqual('closed'); + }) + + it("initialPeerConnectionShouldHaveListOfZeroSendersReceiversTransceivers", DEFAULT, async () => { + let senders = caller.getSenders(); + expect(senders).assertDeepEquals([]); + let receivers = caller.getReceivers(); + expect(receivers).assertDeepEquals([]); + let transceivers = caller.getTransceivers(); + expect(transceivers).assertDeepEquals([]); + }) + + it("pendingLocalDescriptionIsSurfacedAtTheRightTime", DEFAULT, async () => { + let offer = await caller.createOffer(); + expect(caller.pendingLocalDescription).assertUndefined(); + await caller.setLocalDescription(offer); + expect(caller.pendingLocalDescription).not().assertUndefined(); + expect(caller.pendingLocalDescription).assertDeepEquals(caller.localDescription); + }) + + it("pendingRemoteDescriptionIsSurfacedAtTheRightTime", DEFAULT, async () => { + let offer = await caller.createOffer(); + expect(caller.pendingRemoteDescription).assertUndefined(); + await caller.setRemoteDescription(offer); + expect(caller.pendingRemoteDescription).not().assertUndefined(); + expect(caller.pendingRemoteDescription).assertDeepEquals(caller.remoteDescription); + }) + + it("currentLocalDescriptionIsSurfacedAtTheRightTime", DEFAULT, async () => { + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + let answer = await callee.createAnswer(); + + expect(callee.currentLocalDescription).assertUndefined(); + await callee.setLocalDescription(answer); + expect(callee.currentLocalDescription).not().assertNull(); + expect(callee.currentLocalDescription).assertDeepEquals(callee.localDescription); + }) + + it("currentRemoteDescriptionIsSurfacedAtTheRightTime", DEFAULT, async () => { + let offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + let answer = await callee.createAnswer(); + + expect(caller.currentRemoteDescription).assertUndefined(); + await caller.setRemoteDescription(answer); + expect(caller.currentRemoteDescription).not().assertNull(); + expect(caller.currentRemoteDescription).assertDeepEquals(caller.remoteDescription); + }) + + it("createDataChannelAttributeDefaultValues", DEFAULT, () => { + let dc = caller.createDataChannel(''); + + expect(dc instanceof webrtc.RTCDataChannel).assertTrue(); + expect(dc.label).assertEqual(''); + expect(dc.ordered).assertEqual(true); + expect(dc.maxPacketLifeTime).assertUndefined(); + expect(dc.maxRetransmits).assertUndefined(); + expect(dc.protocol).assertEqual(''); + expect(dc.negotiated).assertFalse() + expect(dc.id).assertUndefined() + expect(dc.readyState).assertEqual('connecting'); + expect(dc.bufferedAmount).assertEqual(0); + expect(dc.bufferedAmountLowThreshold).assertEqual(0); + expect(dc.binaryType).assertEqual('blob'); + }) + + it("createDataChannelWithProvidedParametersShouldInitializeAttributesToProvidedValues", DEFAULT, async () => { + let dc = caller.createDataChannel('test', { + ordered: false, + maxRetransmits: 1, + // Note: maxPacketLifeTime is not set in this test. + protocol: 'custom', + negotiated: true, + id: 3 + }); + expect(dc instanceof webrtc.RTCDataChannel).assertTrue(); + expect(dc.label).assertEqual('test'); + expect(dc.ordered).assertEqual(false); + expect(dc.maxPacketLifeTime).assertUndefined(); + expect(dc.maxRetransmits).assertEqual(1); + expect(dc.protocol).assertEqual('custom'); + expect(dc.negotiated).assertEqual(true); + expect(dc.id).assertEqual(3); + expect(dc.readyState).assertEqual('connecting'); + expect(dc.bufferedAmount).assertEqual(0); + expect(dc.bufferedAmountLowThreshold).assertEqual(0); + // 创建RTCDataChannel对象时,binaryType属性必须初始化为字符串"blob" + expect(dc.binaryType).assertEqual('blob'); + + let dc2 = caller.createDataChannel('test2', { + ordered: false, + maxPacketLifeTime: 42 + }); + expect(dc2.label).assertEqual('test2'); + expect(dc2.maxPacketLifeTime).assertEqual(42); + expect(dc2.maxRetransmits).assertUndefined(); + }) + + it("createDataChannelWithOrderedUndefinedShouldSucceed", DEFAULT, () => { + let dc2 = caller.createDataChannel('', { + ordered: undefined + }); + expect(dc2.ordered).assertTrue(); + }) + + it("createDataChannelWithMaxPacketLifeTime0ShouldSucceed", DEFAULT, () => { + let dc = caller.createDataChannel('', { + maxPacketLifeTime: 0 + }); + expect(dc.maxPacketLifeTime).assertEqual(0); + }) + + it("createDataChannelWithMaxRetransmits0ShouldSucceed", DEFAULT, () => { + const dc = caller.createDataChannel('', { + maxRetransmits: 0 + }); + expect(dc.maxRetransmits).assertEqual(0); + }) + + it("createDataChannelWithBothMaxPacketLifeTimeAndMaxRetransmitsUndefinedShouldSucceed", DEFAULT, () => { + try { + caller.createDataChannel('', { + maxPacketLifeTime: undefined, + maxRetransmits: undefined + }); + } catch (e) { + expect().assertFail() + } + }) + + it("channelsCreatedAfterSetRemoteDescriptionShouldHaveIdAssigned", DEFAULT, async () => { + let negotiatedDc = caller.createDataChannel('negotiated-channel', { + negotiated: true, + id: 42, + }); + expect(negotiatedDc.id).assertEqual(42); + + let dc1 = caller.createDataChannel('channel'); + expect(dc1.id).assertUndefined(); + + let offer = await caller.createOffer(); + await Promise.all([caller.setLocalDescription(offer), callee.setRemoteDescription(offer)]); + let answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + + expect(dc1.id).not().assertUndefined(); + expect(dc1.id).assertLargerOrEqual(0); + expect(dc1.id).assertLess(65535); + + let dc2 = caller.createDataChannel('channel'); + + expect(dc2.id).not().assertUndefined(); + expect(dc2.id).assertLarger(0); + expect(dc2.id).assertLess(65535); + expect(dc2).not().assertEqual(dc1); + expect(dc2.label).assertEqual(dc1.label); + expect(dc2.id).not().assertEqual(dc1.id); + expect(negotiatedDc.id).assertEqual(42); + }) + + it("createAnswerReturnsRTCSessionDescriptionInit", DEFAULT, async () => { + let offer = await caller.createOffer(); + await caller.setRemoteDescription(offer); + let answer = await caller.createAnswer(); + expect(typeof answer).assertEqual('object'); + expect(answer instanceof webrtc.RTCSessionDescription).assertFalse(); + }) + + it("canTrickleIceCandidatesPropertyIsTrueAfterSetRemoteDescriptionWithA", DEFAULT, () => { + caller.setRemoteDescription(new webrtc.RTCSessionDescription({ + type: 'offer', sdp: sdp + })); + expect(caller.canTrickleIceCandidates).assertTrue(); + }) + + it("addTransceiverAudioShouldReturnAnAudioTransceiver", DEFAULT, () => { + let transceiver = caller.addTransceiver('audio'); + expect(transceiver instanceof webrtc.RTCRtpTransceiver).assertTrue(); + + expect(transceiver.mid).assertNull(); + + expect(transceiver.direction).assertEqual('sendrecv'); + expect(transceiver.currentDirection).assertNull(); + expect([transceiver]).assertDeepEquals(caller.getTransceivers()); + + let sender = transceiver.sender; + + expect(sender instanceof webrtc.RTCRtpSender).assertTrue(); + expect(sender.track).assertNull(); + expect([sender]).assertDeepEquals(caller.getSenders()); + + let receiver = transceiver.receiver; + expect(receiver instanceof webrtc.RTCRtpReceiver); + + let track = receiver.track; + expect(track instanceof webrtc.MediaStreamTrack).assertTrue(); + expect(track.kind).assertEqual('audio'); + expect(track.readyState).assertEqual('live'); + expect([receiver]).assertDeepEquals(caller.getReceivers()); + }) + + } + + ) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCCertificate.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCCertificate.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..80240a3a0f246df144e7f19623c4531bdc360464 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCCertificate.test.ets @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[RTCCertificateTest]'; + +export default function RTCCertificateTest() { + describe('RTCCertificateTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it("generateCertificateWithString", DEFAULT, async () => { + let Certificate = await webrtc.RTCPeerConnection.generateCertificate("RSA"); + expect(Certificate.expires).not().assertUndefined(); + expect(Certificate.getFingerprints()[0].algorithm).not().assertUndefined(); + expect(Certificate.getFingerprints()[0].value).not().assertUndefined(); + expect(Certificate.getFingerprints().length).assertEqual(1); + }) + + it("generateCertificateWithObject", DEFAULT, async () => { + const keygenAlgorithm: webrtc.AlgorithmIdentifier = { + name: "RSA", + }; + try { + await webrtc.RTCPeerConnection.generateCertificate(keygenAlgorithm); + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("generateCertificateWithErrorString", DEFAULT, async () => { + let Certificate = await webrtc.RTCPeerConnection.generateCertificate("1"); + expect(Certificate.expires).not().assertUndefined(); + expect(Certificate.getFingerprints()[0].algorithm).not().assertUndefined(); + expect(Certificate.getFingerprints()[0].value).not().assertUndefined(); + }) + + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtlsTransport.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtlsTransport.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..64bfa7065fb374c3000ef2d5fca87537e87e70f2 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtlsTransport.test.ets @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[RTCDtlsTransportTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function createDtlsTransport(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + let audioSource = pcf?.createAudioSource(); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + caller.addTrack(audioTrack); + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); +} + +export default function RTCDtlsTransportTest() { + describe('RTCDtlsTransportTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it("transportGoesToConnectedState", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(300); + let transport1 = caller.getTransceivers()[0].sender.transport; + let transport2 = callee.getTransceivers()[0].sender.transport; + let flag1: string | undefined = ""; + let flag2: string | undefined = ""; + transport1!.onstatechange = () => { + console.info(TAG, "state: ", transport1?.state); + if (transport1?.state == "connected") { + flag1 = transport1?.state; + } + } + transport2!.onstatechange = () => { + console.info(TAG, "state: ", transport2?.state); + if (transport2?.state == "connected") { + flag2 = transport2?.state; + } + } + await sleep(5000); + expect(flag1).assertEqual("connected"); + expect(flag2).assertEqual("connected"); + }) + + it("closeCausesTheLocalTransportToCloseImmediately", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(300); + let transport1 = caller.getTransceivers()[0].sender.transport; + let transport2 = callee.getTransceivers()[0].sender.transport; + let flag: string | undefined = ""; + transport1!.onstatechange = () => { + console.info(TAG, "state: ", transport1?.state); + if (transport1?.state == "closed") { + flag = transport1?.state; + } + } + caller.close(); + await sleep(1000); + expect(flag).assertEqual("closed"); + }) + + it("closeCausesTheOtherEndTransportToClose", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(300); + let transport1 = caller.getTransceivers()[0].sender.transport; + let transport2 = callee.getTransceivers()[0].sender.transport; + let flag: string | undefined = ""; + transport1!.onstatechange = () => { + console.info(TAG, "state1: ", transport1?.state); + } + transport2!.onstatechange = () => { + console.info(TAG, "state2: ", transport2?.state); + if (transport2?.state == "closed") { + flag = transport2?.state; + } + } + await sleep(1000); + caller.close(); + await sleep(1000); + expect(flag).assertEqual("closed"); + }) + + it("iceTransportIsNotNullAfterCreateDtlsTransport", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(1000); + expect(caller.getSenders()[0]!.transport).not().assertNull(); + }) + + it("stateIsNotNullAfterCreateDtlsTransport", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(1000); + console.info(TAG, "state: ", caller.getSenders()[0]!.transport!.state); + expect(caller.getSenders()[0]!.transport!.state).not().assertNull(); + }) + + it("getRemoteCertificatesIsNotNullAfterCreateDtlsTransport", DEFAULT, async () => { + createDtlsTransport(caller, callee); + await sleep(1000); + console.info(TAG, "getRemoteCertificates: ", + JSON.stringify(caller.getSenders()[0]?.transport?.getRemoteCertificates())); + expect(caller.getSenders()[0]?.transport?.getRemoteCertificates()).not().assertNull(); + }) + + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtmfSender.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtmfSender.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..5d443e366273f6bfc867bcfb339775bdbde2c93f --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCDtmfSender.test.ets @@ -0,0 +1,221 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[RTCDtmfSenderTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + const answer = await callee.createAnswer(); + console.info(TAG, "answer: ", JSON.stringify(answer)); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + let offer = await caller.createOffer() + await caller.setLocalDescription(offer); + console.info(TAG, "offer: ", JSON.stringify(offer)); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function CreatePeerConnection() { + if (!pcf) { + console.error(TAG, 'pcf is not initialized'); + return; + } + + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }], + bundlePolicy: "balanced" + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }], + bundlePolicy: "balanced" + }); +} + +async function createDtmfSender(caller: webrtc.RTCPeerConnection, + callee: webrtc.RTCPeerConnection): Promise { + let audioSource = pcf?.createAudioSource(); + let track = pcf?.createAudioTrack("audio", audioSource); + let sender = caller.addTrack(track); + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + return sender; +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export default function RTCDtmfSenderTest() { + describe('RTCDtmfSenderTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + CreatePeerConnection(); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it("canInsertDTMFAfterCreateDtmfSender", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + await sleep(1000); + expect(dtmfSender!.canInsertDTMF).assertTrue(); + }) + + it("toneBufferAfterCreateDtmfSender", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + await sleep(1000); + expect(dtmfSender!.toneBuffer).assertEqual(""); + }) + + it("durationShouldLessThan6000", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + try { + dtmfSender!.insertDTMF('123', 10000, 100); + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("durationShouldMoreThan40", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + try { + dtmfSender!.insertDTMF('123', 30, 100); + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("interToneGapShouldLessThan6000", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + try { + dtmfSender!.insertDTMF('123', 70, 6001); + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("interToneGapShouldMoreThan40", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + try { + dtmfSender!.insertDTMF('123', 70, 29); + expect().assertFail(); + } catch (e) { + console.error(TAG, "e: ", e); + } + }) + + it("ontonechangeWhenInsertDTMF", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + let res: string; + dtmfSender!.ontonechange = async (event) => { + if (event.tone == 'A') { + res = event.tone; + } + console.info(TAG, "Tone changed:", event.tone); + }; + await sleep(1000); + dtmfSender?.insertDTMF('A'); + await sleep(1000); + expect(res!).assertEqual('A') + }) + + it("ontonechangeWhenInsertDTMFErrorString", DEFAULT, async () => { + let sender = await createDtmfSender(caller, callee); + let dtmfSender = sender?.dtmf; + let res: string = ""; + dtmfSender!.ontonechange = async (event) => { + if (event.tone == "F") { + res = event.tone; + } + console.info(TAG, "event.tone:", event.tone); + }; + await sleep(1000); + dtmfSender?.insertDTMF('F'); + await sleep(1000); + expect(res).not().assertEqual('F') + }) + + } + + ) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCIceTransport.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCIceTransport.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..fc805c5f93fb7164441f3ae1a5fda9fd837abda6 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCIceTransport.test.ets @@ -0,0 +1,197 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[RTCIceTransportTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function createIceTransport(caller: webrtc.RTCPeerConnection, + callee: webrtc.RTCPeerConnection): Promise { + let audioSource = pcf?.createAudioSource(); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + + caller.addTrack(audioTrack); + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + let transport = caller.getSenders()[0]!.transport!.iceTransport; + return transport; +} + +export default function RTCIceTransportTest() { + describe('RTCIceTransportTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it("unconnectedIceTransportShouldHaveEmptySelectedPair", DEFAULT, async () => { + caller.createDataChannel(''); + await exchangeOfferAnswer(caller, callee); + let iceTransport = caller.sctp?.transport.iceTransport; + expect(iceTransport?.getSelectedCandidatePair()?.local?.usernameFragment).assertEqual(""); + expect(iceTransport?.getSelectedCandidatePair()?.remote?.usernameFragment).assertEqual(""); + }) + + it("iceTransportShouldBeInStateNewInitially", DEFAULT, async () => { + const transceiver = caller.addTransceiver('audio'); + await caller.setLocalDescription(); + const iceTransport = transceiver.sender.transport?.iceTransport; + console.info(TAG, "gatheringState: ", iceTransport!.gatheringState); + + iceTransport!.ongatheringstatechange = () => { + console.info(TAG, "gatheringState: ", iceTransport!.gatheringState); + } + expect(iceTransport?.state).assertEqual('new'); + }) + + it("iceTransportShouldTransitionToGatheringThenCompleteAfterSLD", DEFAULT, async () => { + const transceiver = caller.addTransceiver('audio'); + await caller.setLocalDescription(); + const iceTransport = transceiver.sender.transport?.iceTransport; + + iceTransport!.ongatheringstatechange = () => { + console.info(TAG, 'gatheringState: ', iceTransport?.gatheringState); + }; + await sleep(1000); + expect(iceTransport?.gatheringState).not().assertEqual("new"); + }) + + it("afterInitializationAllAttributesAreNotNull", DEFAULT, async () => { + let transport = await createIceTransport(caller, callee); + await sleep(1000); + expect(transport.role).not().assertUndefined(); + expect(transport.component).not().assertUndefined(); + expect(transport.state).not().assertUndefined(); + expect(transport.gatheringState).not().assertUndefined(); + expect(transport.getSelectedCandidatePair()).not().assertUndefined(); + }) + + it("onstatechange", DEFAULT, async () => { + let transport = await createIceTransport(caller, callee); + let events: string[] = []; + transport.onstatechange = () => { + events.push(transport.state); + console.info(TAG, 'state change: ', transport.state); + }; + await sleep(5000); + expect(events.length).not().assertEqual(0); + }) + + it("onSelectedCandidatePairChange", DEFAULT, async () => { + let transport = await createIceTransport(caller, callee); + let events: string[] = []; + transport.onselectedcandidatepairchange = () => { + events.push("1"); + }; + await sleep(1000); + expect(events.length).not().assertEqual(0); + }) + + it("onGatheringStateChange", DEFAULT, async () => { + const transceiver = caller.addTransceiver('audio', { direction: 'recvonly' }); + await caller.setLocalDescription(); + const iceTransport = transceiver.sender.transport!.iceTransport; + + let events: string[] = []; + await new Promise((resolve, reject) => { + iceTransport.ongatheringstatechange = () => { + events.push(iceTransport.gatheringState); + console.info(TAG, '2 gatheringState change: ', iceTransport.gatheringState); + resolve(); + }; + }); + expect(events.length).assertLarger(0); + }) + + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCPeerConnection-getStats.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCPeerConnection-getStats.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..d05831959bfbad4d0518dbc1637d4aa86a4f016e --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCPeerConnection-getStats.test.ets @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const TAG: string = '[RTCPeerConnectionGetStatsTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); +let caller: webrtc.RTCPeerConnection; + +export default function RTCPeerConnectionGetStatsTest() { + describe('RTCPeerConnectionGetStatsTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({}); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + caller.close(); + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it('getStatsWithNoArgument', 0, async () => { + await caller.getStats().then((statsReport) => { + let found = false; + statsReport.stats.forEach((value: webrtc.RTCStats, _key: string) => { + if (value.type === 'peer-connection') { + found = true; + } + }); + expect(found).assertTrue(); + }).catch(() => { + expect().assertFail(); + }); + }) + + it('getStatsWithNull', 0, async () => { + await caller.getStats(null).then(() => { + }).catch(() => { + expect().assertFail(); + }); + }) + + it('getStatsWithUndefined', 0, async () => { + await caller.getStats(undefined).then(() => { + }).catch(() => { + expect().assertFail(); + }); + }) + + it('getStatsWithNotAddedTrack', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + await caller.getStats(audioTrack).then(() => { + expect().assertFail(); + }).catch((e: Error) => { + console.log(TAG, 'get stats error: ' + e); + expect(e).assertInstanceOf('Error'); + }); + }) + + it('getStatsWithAddedTrack', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + caller.addTrack(audioTrack); + + await caller.getStats(audioTrack).then(() => { + }).catch(() => { + expect().assertFail(); + }); + }) + + it('getStatsWithTrackViaAddTransceiver', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + caller.addTransceiver(audioTrack); + + await caller.getStats(audioTrack).then(() => { + }).catch(() => { + expect().assertFail(); + }); + }) + + it('getStatsWithTrackAssociatedWithBothSenderAndReceiver', 0, async () => { + + const transceiver1 = caller.addTransceiver('audio'); + + // Create another transceiver that resends what + // is being received, kind of like echo + const transceiver2 = caller.addTransceiver(transceiver1.receiver!.track); + + expect(transceiver1.receiver!.track.id).assertEqual(transceiver2.sender!.track!.id); + + await caller.getStats(transceiver1.receiver!.track).then(() => { + expect().assertFail(); + }).catch((e: Error) => { + console.log(TAG, 'get stats error: ' + e); + expect(e).assertInstanceOf('Error'); + }); + }) + }) +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpCapabilitiesHelper.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpCapabilitiesHelper.ets new file mode 100644 index 0000000000000000000000000000000000000000..b5d32c16ad54cc9a6d60dfcd79bdbaa03132f448 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpCapabilitiesHelper.ets @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import * as helper from './Helper'; + +export function validateRtpCapabilities(capabilities: webrtc.RTCRtpCapabilities) { + helper.assertArray(capabilities.codecs); + for(const codec of capabilities.codecs) { + validateCodecCapability(codec); + } + expect(capabilities.codecs.length).assertLarger(0); + + helper.assertArray(capabilities.headerExtensions); + for(const headerExt of capabilities.headerExtensions) { + validateHeaderExtensionCapability(headerExt); + } +} + +function validateCodecCapability(codec: webrtc.RTCRtpCodec) { + if (codec.mimeType !== undefined) { + expect(typeof codec.mimeType).assertEqual('string'); + } + if (codec.clockRate !== undefined) { + expect(Number.isInteger(codec.clockRate) && codec.clockRate > 0).assertTrue(); + } + if (codec.channels !== undefined) { + expect(Number.isInteger(codec.channels) && codec.channels > 0).assertTrue(); + } + if (codec.sdpFmtpLine !== undefined) { + expect(typeof codec.sdpFmtpLine).assertEqual('string'); + } +} + +function validateHeaderExtensionCapability(headerExt: webrtc.RTCRtpHeaderExtensionCapability) { + helper.assertOptionalString(headerExt.uri); +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpParametersHelper.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpParametersHelper.ets new file mode 100644 index 0000000000000000000000000000000000000000..a2323ff627393acb276d329c3b0d700ac83abcd9 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpParametersHelper.ets @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import * as helper from './Helper'; + +export function validateReceiverRtpParameters(param: webrtc.RTCRtpReceiveParameters) { + validateRtpParameters(param); +} + +function validateRtpParameters(param: webrtc.RTCRtpParameters) { + helper.assertArray(param.headerExtensions); + for(const headerExt of param.headerExtensions) { + validateHeaderExtensionParameters(headerExt); + } + + helper.assertObject(param.rtcp); + validateRtcpParameters(param.rtcp); + + helper.assertArray(param.codecs); + for(const codec of param.codecs) { + validateCodecParameters(codec); + } +} + +function validateHeaderExtensionParameters(headerExt: webrtc.RTCRtpHeaderExtensionParameters) { + helper.assertString(headerExt.uri); + helper.assertUnsignedInt(headerExt.id); + helper.assertOptionalBoolean(headerExt.encrypted); +} + +function validateCodecParameters(codec: webrtc.RTCRtpCodecParameters) { + helper.assertUnsignedInt(codec.payloadType); + helper.assertString(codec.mimeType); + helper.assertUnsignedInt(codec.clockRate); + helper.assertOptionalUnsignedInt(codec.channels); + helper.assertString(codec.sdpFmtpLine); +} + +function validateRtcpParameters(rtcp: webrtc.RTCRtcpParameters) { + helper.assertOptionalString(rtcp.cname); + helper.assertOptionalBoolean(rtcp.reducedSize); +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpReceiver.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpReceiver.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..53c368fae9ee2dc93f28ec5b3c178b2f0cdd2d0d --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpReceiver.test.ets @@ -0,0 +1,243 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; +import { validateRtpCapabilities } from './RTCRtpCapabilitiesHelper'; +import { validateReceiverRtpParameters } from './RTCRtpParametersHelper'; +import * as helper from './Helper'; + +const TAG: string = '[RTCRtpReceiverTest]'; +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +async function doOfferAnswerExchange() { + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + const answer = await callee.createAnswer(); + await callee.setLocalDescription(answer); + await caller.setRemoteDescription(answer); +} + +async function getMediaStream() { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + return new webrtc.MediaStream([audioTrack]); +} + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +// 等待pc.connectionState变为'connected'. +async function listenToConnected(pc: webrtc.RTCPeerConnection) { + while (pc.connectionState != 'connected') { + await new Promise((resolve) => { + pc.onconnectionstatechange = resolve; + }); + pc.onconnectionstatechange = null; + } +} + +async function waitForTrackUnmuted(_track: webrtc.MediaStreamTrack) { + console.log(TAG, 'waitForTrackUnmuted'); + // 暂未提供muted属性和onunmute事件等 + await helper.sleep(1000); +} + +export default function RTCRtpReceiverTest() { + describe('RTCRtpReceiverTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it('getCapabilitiesWithAudioKind', 0, () => { + let capabilities = webrtc.RTCRtpReceiver.getCapabilities('audio'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + // expect(capabilities).not().assertNull(); + validateRtpCapabilities(capabilities!); + }) + + it('getCapabilitiesWithVideoKind', 0, () => { + let capabilities = webrtc.RTCRtpReceiver.getCapabilities('video'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + // expect(capabilities).assertNull(); + validateRtpCapabilities(capabilities!); + }) + + it('getCapabilitiesWithInvalidKind', 0, () => { + let capabilities = webrtc.RTCRtpReceiver.getCapabilities('whatever'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + expect(capabilities).assertNull(); + }) + + it('getTransportInitially', 0, () => { + const transceiver = caller.addTransceiver('audio', { direction: 'recvonly' }); + expect(transceiver.receiver!.transport).assertNull(); + }) + + it('getTransportAfterSetLocalDescription', 0, async () => { + const transceiver = caller.addTransceiver('audio', { direction: 'recvonly' }); + await caller.setLocalDescription(); + expect(transceiver.receiver!.transport instanceof webrtc.RTCDtlsTransport).assertTrue(); + }) + + it('getParametersWithAudioReceiver', 0, async () => { + caller.addTransceiver('audio'); + + await doOfferAnswerExchange(); + + const transceivers = callee.getTransceivers(); + expect(transceivers.length).assertLarger(0); + const transceiver = transceivers[0]; + expect(transceiver.receiver).not().assertUndefined(); + + const param = transceiver.receiver!.getParameters(); + validateReceiverRtpParameters(param); + expect(param.headerExtensions.length).assertLarger(0); + expect(param.codecs.length).assertLarger(0); + }) + + it('getParametersWithVideoReceiver', 0, async () => { + caller.addTransceiver('video'); + + await doOfferAnswerExchange(); + + const transceivers = callee.getTransceivers(); + expect(transceivers.length).assertLarger(0); + const transceiver = transceivers[0]; + expect(transceiver.receiver).not().assertUndefined(); + + const param = transceiver!.receiver!.getParameters(); + validateReceiverRtpParameters(param); + expect(param.headerExtensions.length).assertLarger(0); + expect(param.codecs.length).assertLarger(0); + }) + + it('getParametersWithSimulcastVideoReceiver', 0, async () => { + caller.addTransceiver('audio', { + sendEncodings: [ + { + rid: "rid1" + }, + { + rid: "rid2" + } + ] + }); + + await doOfferAnswerExchange(); + + const transceivers = callee.getTransceivers(); + expect(transceivers.length).assertLarger(0); + const transceiver = transceivers[0]; + expect(transceiver.receiver).not().assertUndefined(); + + const param = transceiver!.receiver!.getParameters(); + validateReceiverRtpParameters(param); + expect(param.headerExtensions.length).assertLarger(0); + expect(param.codecs.length).assertLarger(0); + }) + + it('getStats', 0, async () => { + const stream = await getMediaStream(); + const tracks = stream.getTracks(); + callee.addTrack(tracks[0], stream); + + const receiver = caller.addTransceiver('audio').receiver; + + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); + await listenToConnected(callee); + await waitForTrackUnmuted(receiver!.track); + const statsReport = await receiver!.getStats(); + + let found = false; + statsReport.stats.forEach((value: webrtc.RTCStats, _key: string) => { + if (value.type === 'inbound-rtp') { + found = true; + } + }); + expect(found).assertTrue(); + }) + }) +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpSender.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpSender.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..91056033a54dd040e3f8af5588ea2a56b3f9bc3a --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpSender.test.ets @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; +import { sleep } from './Helper'; +import { validateRtpCapabilities } from './RTCRtpCapabilitiesHelper'; + +const TAG: string = '[RTCRtpSenderTest]'; +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; + +let audioRtpSender: webrtc.RTCRtpSender; + +async function createAudioRtpSender() { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + audioRtpSender = caller.addTrack(audioTrack); +} + +// async function destroyAudioRtpSender() { +// pc.removeTrack(audioRtpSender); +// audioRtpSender = null; +// } + +export default function RTCRtpSenderTest() { + describe('RTCRtpSenderTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }) + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it('getCapabilitiesWithAudioKind', 0, () => { + let capabilities = webrtc.RTCRtpSender.getCapabilities('audio'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + validateRtpCapabilities(capabilities!); + }) + + it('getCapabilitiesWithVideoKind', 0, () => { + let capabilities = webrtc.RTCRtpSender.getCapabilities('video'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + validateRtpCapabilities(capabilities!); + }) + + it('getCapabilitiesWithInvalidKind', 0, () => { + let capabilities = webrtc.RTCRtpSender.getCapabilities('whatever'); + console.debug(TAG, "getCapabilities: " + JSON.stringify(capabilities)); + expect(capabilities).assertNull(); + }) + + it('dtmfOfVideoSenderIsNull', 0, async () => { + const t1 = caller.addTransceiver("audio"); + const t2 = caller.addTransceiver("video"); + + expect(t1.sender!.dtmf).not().assertNull(); + expect(t2.sender!.dtmf).assertNull(); + }) + + it('getTransportInitially', 0, async () => { + const transceiver = caller.addTransceiver('audio', { + direction: 'recvonly' + }); + + expect(transceiver.sender!.transport).assertNull(); + }) + + it('getTransportAfterSetLocalDescription', 0, async () => { + const transceiver = caller.addTransceiver('audio', { + direction: 'recvonly' + }); + await caller.setLocalDescription(); + expect(transceiver.sender!.transport instanceof webrtc.RTCDtlsTransport).assertTrue(); + }) + + it('canGetTrack', 0, () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + audioRtpSender = caller.addTrack(audioTrack); + + expect(audioRtpSender).not().assertUndefined(); + expect(audioRtpSender.track).not().assertUndefined(); + expect(audioTrack.id).assertEqual(audioRtpSender.track!.id); + }) + + it('canSetParameters', 0, async () => { + createAudioRtpSender(); + + let parameters = audioRtpSender.getParameters(); + expect(parameters.encodings.length).assertEqual(1); + + try { + await audioRtpSender.setParameters(parameters); + } catch (e) { + console.error(TAG, "canSetParameters: " + JSON.stringify(e)); + expect().assertFail(); + } + }) + + it('setParametersWhenTransceiverIsStopped', 0, async () => { + const transceiver = caller.addTransceiver('audio'); + const sender = transceiver.sender; + + const param = sender!.getParameters(); + transceiver.stop(); + + try { + await sender!.setParameters(param); + expect().assertFail(); + } catch (e) { + console.error(TAG, "setParameters rejected: " + JSON.stringify(e)); + } + }) + + it('setParametersWithSame', 0, async () => { + const transceiver = caller.addTransceiver('audio'); + const sender = transceiver.sender; + + const param = sender!.getParameters(); + + await sender!.setParameters(param); + }) + + it('replaceTrackWithClosedConnection', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + const transceiver = caller.addTransceiver('audio'); + const sender = transceiver.sender; + caller.close(); + + try { + await sender!.replaceTrack(audioTrack); + expect().assertFail(); + } catch (e) { + console.error(TAG, 'failed to replace track - ' + e); + } + }) + + it('replaceTrackWithStoppedTransceiver', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + const transceiver = caller.addTransceiver('audio'); + const sender = transceiver.sender; + + transceiver.stop(); + + try { + await sender!.replaceTrack(audioTrack); + expect().assertFail(); + } catch (e) { + console.error(TAG, 'failed to replace track - ' + e); + } + }) + + it('replaceTrackWithDifferentKind', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + const transceiver = caller.addTransceiver('video'); + const sender = transceiver.sender; + caller.close(); + + try { + await sender!.replaceTrack(audioTrack); + expect().assertFail(); + } catch (e) { + console.error(TAG, 'failed to replace track - ' + e); + } + }) + + it('replaceTrackWithNull', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + const transceiver = caller.addTransceiver(audioTrack); + const sender = transceiver.sender; + + expect(sender!.track!.id).assertEqual(audioTrack.id); + + try { + await sender!.replaceTrack(null); + expect(sender!.track).assertNull(); + } catch (e) { + expect().assertFail(); + } + }) + }) +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpTransceiver.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpTransceiver.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..eab6b5d53244f56295fb56aed306469e70e19a4a --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCRtpTransceiver.test.ets @@ -0,0 +1,370 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; +import * as helper from './Helper'; + +const TAG: string = '[RTCRtpTransceiverTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +async function doOfferAnswerExchange() { + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + const answer = await callee.createAnswer(); + await callee.setLocalDescription(answer); + await caller.setRemoteDescription(answer); +} + +async function getMediaStream() { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + return new webrtc.MediaStream([audioTrack]); +} + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +// 等待pc.connectionState变为'connected'. +async function listenToConnected(pc: webrtc.RTCPeerConnection) { + while (pc.connectionState != 'connected') { + await new Promise((resolve) => { + pc.onconnectionstatechange = resolve; + }); + pc.onconnectionstatechange = null; + } +} + +async function waitForTrackUnmuted(_track: webrtc.MediaStreamTrack) { + console.log(TAG, 'waitForTrackUnmuted'); + // 暂未提供muted属性和onunmute事件等 + await helper.sleep(1000); +} + +// async function generateAnswer(offer) { +// const pc = new RTCPeerConnection(); +// await pc.setRemoteDescription(offer); +// const answer = await pc.createAnswer(); +// pc.close(); +// return answer; +// } + +export default function RTCRtpTransceiverTest() { + describe('RTCRtpTransceiverTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({}); + callee = pcf.createPeerConnection({}); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + caller.close(); + callee.close(); + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it('addTransceiverNoTrack', 0, async () => { + expect(caller.getTransceivers().length).assertEqual(0); + + caller.addTransceiver("audio"); + + const transceivers = caller.getTransceivers(); + expect(transceivers.length).assertEqual(1); + + const transceiver = transceivers[0]; + expect(transceiver.mid).assertNull(); + expect(transceiver.direction).assertEqual('sendrecv'); + expect(transceiver.currentDirection).assertNull(); + expect(transceiver.sender).not().assertNull(); + expect(transceiver.sender.track).assertNull(); + expect(transceiver.receiver).not().assertNull(); + expect(transceiver.receiver.track).not().assertNull(); + expect(transceiver.receiver.track.kind).assertEqual("audio"); + expect(transceiver.receiver.track.readyState).assertEqual("live"); + }) + + it('addTransceiverWithTrack', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + caller.addTransceiver(audioTrack); + + const transceivers = caller.getTransceivers(); + expect(transceivers.length).assertEqual(1); + + const transceiver = transceivers[0]; + expect(transceiver.mid).assertNull(); + expect(transceiver.direction).assertEqual('sendrecv'); + expect(transceiver.currentDirection).assertNull(); + expect(transceiver.sender).not().assertNull(); + expect(transceiver.sender.track).not().assertNull(); + expect(transceiver.sender.track!.id).assertEqual(audioTrack.id); + expect(transceiver.receiver).not().assertNull(); + expect(transceiver.receiver.track).not().assertNull(); + expect(transceiver.receiver.track.kind).assertEqual("audio"); + }) + + it('addTransceiverWithAddTrack', 0, async () => { + let audioSource = pcf.createAudioSource(); + let audioTrack = pcf.createAudioTrack("audio", audioSource); + + caller.addTrack(audioTrack); + + const transceivers = caller.getTransceivers(); + expect(transceivers.length).assertEqual(1); + + const transceiver = transceivers[0]; + expect(transceiver.mid).assertNull(); + expect(transceiver.direction).assertEqual('sendrecv'); + expect(transceiver.currentDirection).assertNull(); + expect(transceiver.sender).not().assertNull(); + expect(transceiver.sender.track).not().assertNull(); + expect(transceiver.sender.track!.id).assertEqual(audioTrack.id); + expect(transceiver.receiver).not().assertNull(); + expect(transceiver.receiver.track).not().assertNull(); + expect(transceiver.receiver.track.kind).assertEqual("audio"); + }) + + it('addTransceiverWithDirection', 0, async () => { + caller.addTransceiver("audio", { + direction: "recvonly" + }); + + const transceivers = caller.getTransceivers(); + expect(transceivers.length).assertEqual(1); + + const transceiver = transceivers[0]; + expect(transceiver.mid).assertNull(); + expect(transceiver.direction).assertEqual('recvonly'); + expect(transceiver.currentDirection).assertNull(); + expect(transceiver.sender).not().assertNull(); + expect(transceiver.sender.track).assertNull(); + expect(transceiver.receiver).not().assertNull(); + expect(transceiver.receiver.track).not().assertNull(); + expect(transceiver.receiver.track.kind).assertEqual("audio"); + }) + + it('addTransceiverWithBadKind', 0, async () => { + try { + caller.addTransceiver("other"); + expect().assertFail(); + } catch (e) { + // should throw TypeError + } + }) + + it('setDirection', 0, async () => { + caller.addTransceiver("audio"); + expect(caller.getTransceivers().length).assertEqual(1); + + caller.getTransceivers()[0].direction = "sendonly"; + expect(caller.getTransceivers()[0].direction).assertEqual("sendonly"); + caller.getTransceivers()[0].direction = "recvonly"; + expect(caller.getTransceivers()[0].direction).assertEqual("recvonly"); + caller.getTransceivers()[0].direction = "inactive"; + expect(caller.getTransceivers()[0].direction).assertEqual("inactive"); + caller.getTransceivers()[0].direction = "sendrecv"; + expect(caller.getTransceivers()[0].direction).assertEqual("sendrecv"); + }) + + it('setDirection2', 0, async () => { + const transceiver = caller.addTransceiver('audio', { + direction: 'recvonly' + }); + expect(transceiver.direction).assertEqual("recvonly"); + expect(transceiver.currentDirection).assertNull(); + + const offer = await caller.createOffer(); + await caller.setLocalDescription(offer); + await callee.setRemoteDescription(offer); + + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + + expect(transceiver.currentDirection).assertEqual("inactive"); + transceiver.direction = 'sendrecv'; + expect(transceiver.direction).assertEqual("sendrecv"); + expect(transceiver.currentDirection).assertEqual("inactive"); + }) + + it('createOfferAfterStop', 0, async () => { + caller.addTransceiver("audio", { direction: "sendonly" }); + caller.addTransceiver("video"); + caller.getTransceivers()[0].stop(); + + const offer = await caller.createOffer(); + + console.log(TAG, 'offer.sdp!.includes("m=audio"): ' + offer.sdp!.includes("m=audio")); + expect(offer.sdp!.includes("m=audio")).assertFalse(); + console.log(TAG, 'offer.sdp!.includes("m=video"): ' + offer.sdp!.includes("m=video")); + expect(offer.sdp!.includes("m=video")).assertTrue(); + }) + + it('createOfferAfterStopSendOnlyTransceiver', 0, async () => { + caller.addTransceiver("audio"); + + await exchangeOfferAnswer(caller, callee); + + caller.getTransceivers()[0].direction = "sendonly"; + caller.getTransceivers()[0].stop(); + + const offer = await caller.createOffer(); + + // assert_true(offer.sdp.includes("a=inactive"), "The audio m-section should be inactive"); + expect(offer.sdp!.includes("a=inactive")).assertTrue(); + }) + + it('getTransportAfterStop', 0, async () => { + caller.addTransceiver("audio", { direction: "sendonly" }); + caller.addTransceiver("video"); + + const transceiver = caller.getTransceivers()[1]; + expect(transceiver.receiver.transport).assertNull(); + + transceiver.stop(); + expect(transceiver.receiver.transport).assertNull(); + }) + + it('setCodecPreferences', 0, () => { + const transceiver1 = caller.addTransceiver('audio'); + const capabilities1 = webrtc.RTCRtpReceiver.getCapabilities('audio'); + expect(capabilities1).not().assertNull(); + transceiver1.setCodecPreferences(capabilities1!.codecs); + + const transceiver2 = caller.addTransceiver('video'); + const capabilities2 = webrtc.RTCRtpReceiver.getCapabilities('video'); + expect(capabilities2).not().assertNull(); + transceiver2.setCodecPreferences(capabilities2!.codecs); + }) + + it('setCodecPreferencesWithEmptyCodecs', 0, () => { + const transceiver = caller.addTransceiver('audio'); + transceiver.setCodecPreferences([]); + }) + + it('setCodecPreferencesOnAudioTransceiverWithVideoCodecs', 0, () => { + const transceiver = caller.addTransceiver('audio'); + const capabilities = webrtc.RTCRtpReceiver.getCapabilities('video'); + expect(capabilities).not().assertNull(); + try { + transceiver.setCodecPreferences(capabilities!.codecs); + expect().assertFail(); + } catch (e) { + // should throw InvalidModificationError + expect(e).assertInstanceOf('Error'); + } + }) + + it('setCodecPreferencesWithInvalidMimeType', 0, () => { + const transceiver = caller.addTransceiver('audio'); + const codecs: webrtc.RTCRtpCodec[] = [{ + mimeType: 'data', + clockRate: 2000, + channels: 2, + sdpFmtpLine: '0-15' + }]; + + try { + transceiver.setCodecPreferences(codecs); + expect().assertFail(); + } catch (e) { + // should throw InvalidModificationError + expect(e).assertInstanceOf('Error'); + } + }) + + it('setCodecPreferencesWithUserDefinedCodec', 0, () => { + const transceiver = caller.addTransceiver('audio'); + const codecs: webrtc.RTCRtpCodec[] = [{ + mimeType: 'audio/piepiper', + clockRate: 2000, + channels: 2, + sdpFmtpLine: '0-15' + }]; + + try { + transceiver.setCodecPreferences(codecs); + expect().assertFail(); + } catch (e) { + // should throw InvalidModificationError + expect(e).assertInstanceOf('Error'); + } + }) + + it('setCodecPreferencesWithModifiedCodecs', 0, () => { + const transceiver = caller.addTransceiver('audio'); + const capabilities = webrtc.RTCRtpReceiver.getCapabilities('audio'); + + const codecs = [capabilities!.codecs[0]]; + codecs[0].sdpFmtpLine = "modifiedparameter=1"; + + try { + transceiver.setCodecPreferences(codecs); + expect().assertFail(); + } catch (e) { + // should throw InvalidModificationError + expect(e).assertInstanceOf('Error'); + } + }) + }) +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCSctpTransport.test.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCSctpTransport.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f8ffd542fae04b383ef2b8ab44228d3dbd86ba9c --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/test/RTCSctpTransport.test.ets @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' +import webrtc from 'libohos_webrtc.so'; +import { Logging, LoggingSeverity } from '../../../main/ets/log/Logging'; + +const DEFAULT = 0 +const TAG: string = '[RTCSctpTransportTest]'; + +const pcf = new webrtc.PeerConnectionFactory(); + +let caller: webrtc.RTCPeerConnection; +let callee: webrtc.RTCPeerConnection; + +const STUN_SERVER = "stun:stun.l.google.com:19302"; + +function exchangeIceCandidates(pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) { + const doExchange = (pc1: webrtc.RTCPeerConnection, pc2: webrtc.RTCPeerConnection) => { + pc1.onicecandidate = (event) => { + const candidate = event.candidate; + if (pc2.signalingState !== 'closed') { + pc2.addIceCandidate(candidate); + } + }; + }; + + doExchange(pc1, pc2); + doExchange(pc2, pc1); +} + +async function exchangeOffer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await caller.setLocalDescription(await caller.createOffer()); + console.log(TAG, "caller.localDescription: " + caller.localDescription!.type); + await callee.setRemoteDescription(caller.localDescription); +} + +async function exchangeAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + // 必须先执行caller.setRemoteDescription,否则callee的candidates有可能在caller的remote description设置之前到达 + const answer = await callee.createAnswer(); + await caller.setRemoteDescription(answer); + await callee.setLocalDescription(answer); +} + +async function exchangeOfferAnswer(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + await exchangeOffer(caller, callee); + await exchangeAnswer(caller, callee); +} + +function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function createSctpTransport(caller: webrtc.RTCPeerConnection, callee: webrtc.RTCPeerConnection) { + let audioSource = pcf?.createAudioSource(); + let audioTrack = pcf?.createAudioTrack("audio", audioSource); + + caller.addTrack(audioTrack); + + caller.createDataChannel("myDataChannel"); + caller.oniceconnectionstatechange = () => { + console.info(TAG, "ICE state: ", caller.iceConnectionState); + }; + exchangeIceCandidates(caller, callee); + await exchangeOfferAnswer(caller, callee); +} + +export default function RTCSctpTransportTest() { + describe('RTCSctpTransportTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + Logging.enableLogToDebugOutput(LoggingSeverity.VERBOSE); + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + caller = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + callee = pcf.createPeerConnection({ + iceServers: [{ + urls: STUN_SERVER + }] + }); + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + if (caller) { + caller.close(); + } + if (callee) { + callee.close(); + } + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + + it("anUnconnectedPeerConnectionMustNotHaveMaxChannelsSet", DEFAULT, async () => { + caller.createDataChannel('test'); + const offer = await caller.createOffer(); + await caller.setRemoteDescription(offer); + const answer = await caller.createAnswer(); + await caller.setLocalDescription(answer); + + expect(caller.sctp).not().assertUndefined(); + await sleep(1000); + console.info(TAG, "state: ", caller.sctp?.state); + expect(caller.sctp?.maxChannels).assertUndefined(); + }) + + it("maxChannelsGetsInstantiatedAfterConnecting", DEFAULT, async () => { + createSctpTransport(caller, callee); + await sleep(1000); + console.info(TAG, "state: ", caller.sctp?.state); + expect(caller.sctp?.maxChannels).not().assertUndefined(); + expect(callee.sctp?.maxChannels).not().assertUndefined(); + expect(caller.sctp?.maxChannels).assertEqual(caller.sctp?.maxChannels); + }) + + it("maxMessageSizeAfterInitializationNotNull", DEFAULT, async () => { + caller.createDataChannel(''); + await exchangeOfferAnswer(caller, callee); + await sleep(1000); + console.info(TAG, "maxMessageSize: ", caller.sctp?.maxMessageSize); + expect(caller.sctp?.maxMessageSize).not().assertUndefined(); + }) + + it("RTCDtlsTransportAfterInitializationNotNull", DEFAULT, async () => { + createSctpTransport(caller, callee); + await sleep(1000); + expect(caller.sctp?.transport).not().assertUndefined(); + }) + + it("afterInitializationStateIsConnected", DEFAULT, async () => { + createSctpTransport(caller, callee); + await sleep(1000); + console.info(TAG, "state: ", caller.sctp?.state); + }) + + it("whenTheStateChangesTheEventWillTrigger", DEFAULT, async () => { + createSctpTransport(caller, callee); + await sleep(1000); + caller.sctp!.onstatechange = () => { + console.info(TAG, "state changed: ", caller.sctp?.state); + } + await sleep(5000); + }) + }) +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/TestAbility.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..b35938126d29da4d8d687d6db72b54733b7dc1eb --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; +import Want from '@ohos.app.ability.Want'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); + let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs; + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/pages/Index.ets b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1adf5ab9447058e54daac8d94f7f55bb18f13381 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..7482f5a834554034d5b90ccc1055e1b2a77ff53b --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import Want from '@ohos.app.ability.Want'; + +let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined +let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err : Error) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName = 'TestAbility'; + const moduleName = abilityDelegatorArguments.parameters['-m']; + let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator(); + abilityDelegator.startAbility(want, (err, data) => { + hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/module.json5 b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..60e8f232c9c5d4bade3543d01f32cda1305bbda7 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/module.json5 @@ -0,0 +1,43 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name" : "ohos.permission.INTERNET", + "reason": "$string:internet" + } + ] + } +} diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/color.json b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..d66f9a7d4ac61fb8d215239ab3620b7bcd77bf33 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/string.json b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..78debe1334c534b3cf9c822620c981b007d246df --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,20 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + }, + { + "name": "internet", + "value": "internet permission" + } + ] +} \ No newline at end of file diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/media/icon.png b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/media/icon.png differ diff --git a/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/profile/test_pages.json b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..c3d813c41aa8ac1b64ccf124bb705adefb381787 --- /dev/null +++ b/sdk/ohos/har_hap/ohos_webrtc/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/sdk/ohos/img/webrtc-arch.png b/sdk/ohos/img/webrtc-arch.png new file mode 100755 index 0000000000000000000000000000000000000000..5e21361fc38a27de0f19a7fc010e934b1c80edeb Binary files /dev/null and b/sdk/ohos/img/webrtc-arch.png differ diff --git a/sdk/ohos/src/node-addon-api/LICENSE.md b/sdk/ohos/src/node-addon-api/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..819d91a5bd799be3e9444acc9dd1ebe6ee5de529 --- /dev/null +++ b/sdk/ohos/src/node-addon-api/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2017 [Node.js API collaborators](https://github.com/nodejs/node-addon-api#collaborators) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/sdk/ohos/src/node-addon-api/README.md b/sdk/ohos/src/node-addon-api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..211cc3b75c211073d0e87ca90d9cf28bb3699f46 --- /dev/null +++ b/sdk/ohos/src/node-addon-api/README.md @@ -0,0 +1,95 @@ +# **node-addon-api module** + +[![codecov](https://codecov.io/gh/nodejs/node-addon-api/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nodejs/node-addon-api/tree/main) + +[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) + +This module contains **header-only C++ wrapper classes** which simplify +the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +provided by Node.js when using C++. It provides a C++ object model +and exception handling semantics with low overhead. + +- [API References](doc/README.md) +- [Badges](#badges) +- [Contributing](#contributing) +- [License](#license) + +## API References + +API references are available in the [doc](doc/README.md) directory. + + +## Current version: 8.1.0 + + +(See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) + +node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. +This allows addons built with it to run with Node.js versions which support the targeted Node-API version. +**However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that +every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. + +The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. + +## Badges + +The use of badges is recommended to indicate the minimum version of Node-API +required for the module. This helps to determine which Node.js major versions are +supported. Addon maintainers can consult the [Node-API support matrix][] to determine +which Node.js versions provide a given Node-API version. The following badges are +available: + +![Node-API v1 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v1%20Badge.svg) +![Node-API v2 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v2%20Badge.svg) +![Node-API v3 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v3%20Badge.svg) +![Node-API v4 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v4%20Badge.svg) +![Node-API v5 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v5%20Badge.svg) +![Node-API v6 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v6%20Badge.svg) +![Node-API v7 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v7%20Badge.svg) +![Node-API v8 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v8%20Badge.svg) +![Node-API v9 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v9%20Badge.svg) +![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) + +## Contributing + +We love contributions from the community to **node-addon-api**! +See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. + +## Team members + +### Active + +| Name | GitHub Link | +| ------------------- | ----------------------------------------------------- | +| Anna Henningsen | [addaleax](https://github.com/addaleax) | +| Chengzhong Wu | [legendecas](https://github.com/legendecas) | +| Jack Xia | [JckXia](https://github.com/JckXia) | +| Kevin Eady | [KevinEady](https://github.com/KevinEady) | +| Michael Dawson | [mhdawson](https://github.com/mhdawson) | +| Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | +| Vladimir Morozov | [vmoroz](https://github.com/vmoroz) | + +
+ +Emeritus + +### Emeritus + +| Name | GitHub Link | +| ------------------- | ----------------------------------------------------- | +| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | +| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) | +| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) | +| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) | +| Jason Ginchereau | [jasongin](https://github.com/jasongin) | +| Jim Schlight | [jschlight](https://github.com/jschlight) | +| Sampson Gao | [sampsongao](https://github.com/sampsongao) | +| Taylor Woll | [boingoing](https://github.com/boingoing) | + +
+ +## License + +Licensed under [MIT](./LICENSE.md) + +[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix diff --git a/sdk/ohos/src/node-addon-api/napi-inl.h b/sdk/ohos/src/node-addon-api/napi-inl.h new file mode 100644 index 0000000000000000000000000000000000000000..1d0b7ea0b0c9068b6471af9a3526000a2584d9f2 --- /dev/null +++ b/sdk/ohos/src/node-addon-api/napi-inl.h @@ -0,0 +1,6627 @@ +#ifndef SRC_NAPI_INL_H_ +#define SRC_NAPI_INL_H_ + +//////////////////////////////////////////////////////////////////////////////// +// Node-API C++ Wrapper Classes +// +// Inline header-only implementations for "Node-API" ABI-stable C APIs for +// Node.js. +//////////////////////////////////////////////////////////////////////////////// + +// Note: Do not include this file directly! Include "napi.h" instead. + +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Helpers to handle functions exposed from C++ and internal constants. +namespace details { + +// New napi_status constants not yet available in all supported versions of +// Node.js releases. Only necessary when they are used in napi.h and napi-inl.h. +constexpr int napi_no_external_buffers_allowed = 22; + +template +inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); +} + +// Attach a data item to an object and delete it when the object gets +// garbage-collected. +// TODO: Replace this code with `napi_add_finalizer()` whenever it becomes +// available on all supported versions of Node.js. +template > +inline napi_status AttachData(napi_env env, + napi_value obj, + FreeType* data, + void* hint = nullptr) { + napi_status status; +#if (defined(__OHOS__) || NAPI_VERSION < 5) + napi_value symbol, external; + status = napi_create_symbol(env, nullptr, &symbol); + if (status == napi_ok) { + status = napi_create_external(env, data, finalizer, hint, &external); + if (status == napi_ok) { + napi_property_descriptor desc = {"data", + symbol, + nullptr, + nullptr, + nullptr, + external, + napi_default, + nullptr}; + status = napi_define_properties(env, obj, 1, &desc); + } + } +#else // NAPI_VERSION >= 5 + status = napi_add_finalizer(env, obj, data, finalizer, hint, nullptr); +#endif + return status; +} + +// For use in JS to C++ callback wrappers to catch any Napi::Error exceptions +// and rethrow them as JavaScript exceptions before returning from the callback. +template +inline napi_value WrapCallback(Callable callback) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + return callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + return nullptr; + } +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + return callback(); +#endif // NAPI_CPP_EXCEPTIONS +} + +// For use in JS to C++ void callback wrappers to catch any Napi::Error +// exceptions and rethrow them as JavaScript exceptions before returning from +// the callback. +template +inline void WrapVoidCallback(Callable callback) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + callback(); +#endif // NAPI_CPP_EXCEPTIONS +} + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); + } + + Callable callback; + void* data; +}; + +template +struct CallbackData { + static inline napi_value Wrapper(napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + CallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); + } + + Callable callback; + void* data; +}; + +template +napi_value TemplatedVoidCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + Callback(cbInfo); + return nullptr; + }); +} + +template +napi_value TemplatedCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + return Callback(cbInfo); + }); +} + +template +napi_value TemplatedInstanceCallback(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); + }); +} + +template +napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) + NAPI_NOEXCEPT { + return details::WrapCallback([&] { + CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*UnwrapCallback)(cbInfo); + return nullptr; + }); +} + +template +struct FinalizeData { + static inline void Wrapper(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(Env(env), static_cast(data)); + delete finalizeData; + }); + } + + static inline void WrapperWithHint(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback([&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback( + Env(env), static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + + Finalizer callback; + Hint* hint; +}; + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +template , + typename FinalizerDataType = void> +struct ThreadSafeFinalize { + static inline void Wrapper(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env)); + delete finalizeData; + } + + static inline void FinalizeWrapperWithData(napi_env env, + void* rawFinalizeData, + void* /* rawContext */) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), finalizeData->data); + delete finalizeData; + } + + static inline void FinalizeWrapperWithContext(napi_env env, + void* rawFinalizeData, + void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback(Env(env), static_cast(rawContext)); + delete finalizeData; + } + + static inline void FinalizeFinalizeWrapperWithDataAndContext( + napi_env env, void* rawFinalizeData, void* rawContext) { + if (rawFinalizeData == nullptr) return; + + ThreadSafeFinalize* finalizeData = + static_cast(rawFinalizeData); + finalizeData->callback( + Env(env), finalizeData->data, static_cast(rawContext)); + delete finalizeData; + } + + FinalizerDataType* data; + Finalizer callback; +}; + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { + details::WrapVoidCallback([&]() { + call(env, + Function(env, jsCallback), + static_cast(context), + static_cast(data)); + }); +} + +template +inline typename std::enable_if(nullptr)>::type +CallJsWrapper(napi_env env, + napi_value jsCallback, + void* /*context*/, + void* /*data*/) { + details::WrapVoidCallback([&]() { + if (jsCallback != nullptr) { + Function(env, jsCallback).Call(0, nullptr); + } + }); +} + +#if NAPI_VERSION > 4 + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, std::nullptr_t /*cb*/) { + return nullptr; +} + +template +napi_value DefaultCallbackWrapper(napi_env /*env*/, Napi::Function cb) { + return cb; +} + +#else +template +napi_value DefaultCallbackWrapper(napi_env env, Napi::Function cb) { + if (cb.IsEmpty()) { + return TSFN::EmptyFunctionFactory(env); + } + return cb; +} +#endif // NAPI_VERSION > 4 +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +template +struct AccessorCallbackData { + static inline napi_value GetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); + } + + static inline napi_value SetterWrapper(napi_env env, + napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + AccessorCallbackData* callbackData = + static_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo); + return nullptr; + }); + } + + Getter getterCallback; + Setter setterCallback; + void* data; +}; + +} // namespace details + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED +#include "napi-inl.deprecated.h" +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + +//////////////////////////////////////////////////////////////////////////////// +// Module registration +//////////////////////////////////////////////////////////////////////////////// + +// Register an add-on based on an initializer function. +#define NODE_API_MODULE(modname, regfunc) \ + static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, regfunc); \ + } \ + NAPI_MODULE(modname, __napi_##regfunc) + +// Register an add-on based on a subclass of `Addon` with a custom Node.js +// module name. +#define NODE_API_NAMED_ADDON(modname, classname) \ + static napi_value __napi_##classname(napi_env env, napi_value exports) { \ + return Napi::RegisterModule(env, exports, &classname::Init); \ + } \ + NAPI_MODULE(modname, __napi_##classname) + +// Register an add-on based on a subclass of `Addon` with the Node.js module +// name given by node-gyp from the `target_name` in binding.gyp. +#define NODE_API_ADDON(classname) \ + NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname) + +// Adapt the NAPI_MODULE registration function: +// - Wrap the arguments in NAPI wrappers. +// - Catch any NAPI errors and rethrow as JS exceptions. +inline napi_value RegisterModule(napi_env env, + napi_value exports, + ModuleRegisterCallback registerCallback) { + return details::WrapCallback([&] { + return napi_value( + registerCallback(Napi::Env(env), Napi::Object(env, exports))); + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Maybe class +//////////////////////////////////////////////////////////////////////////////// + +template +bool Maybe::IsNothing() const { + return !_has_value; +} + +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + +//////////////////////////////////////////////////////////////////////////////// +// Env class +//////////////////////////////////////////////////////////////////////////////// + +inline Env::Env(napi_env env) : _env(env) {} + +inline Env::operator napi_env() const { + return _env; +} + +inline Object Env::Global() const { + napi_value value; + napi_status status = napi_get_global(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Object()); + return Object(*this, value); +} + +inline Value Env::Undefined() const { + napi_value value; + napi_status status = napi_get_undefined(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline Value Env::Null() const { + napi_value value; + napi_status status = napi_get_null(*this, &value); + NAPI_THROW_IF_FAILED(*this, status, Value()); + return Value(*this, value); +} + +inline bool Env::IsExceptionPending() const { + bool result; + napi_status status = napi_is_exception_pending(_env, &result); + if (status != napi_ok) + result = false; // Checking for a pending exception shouldn't throw. + return result; +} + +inline Error Env::GetAndClearPendingException() const { + napi_value value; + napi_status status = napi_get_and_clear_last_exception(_env, &value); + if (status != napi_ok) { + // Don't throw another exception when failing to get the exception! + return Error(); + } + return Error(_env, value); +} + +inline MaybeOrValue Env::RunScript(const char* utf8script) const { + String script = String::New(_env, utf8script); + return RunScript(script); +} + +inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { + return RunScript(utf8script.c_str()); +} + +inline MaybeOrValue Env::RunScript(String script) const { + napi_value result; + napi_status status = napi_run_script(_env, script, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +#if NAPI_VERSION > 2 +template +void Env::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = + static_cast::CleanupData*>( + data); + cleanupData->hook(); + delete cleanupData; +} + +template +void Env::CleanupHook::WrapperWithArg(void* data) NAPI_NOEXCEPT { + auto* cleanupData = + static_cast::CleanupData*>( + data); + cleanupData->hook(static_cast(cleanupData->arg)); + delete cleanupData; +} +#endif // NAPI_VERSION > 2 + +#if !defined(__OHOS__) +#if NAPI_VERSION > 5 +template fini> +inline void Env::SetInstanceData(T* data) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, + nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template fini> +inline void Env::SetInstanceData(DataType* data, HintType* hint) const { + napi_status status = napi_set_instance_data( + _env, + data, + [](napi_env env, void* data, void* hint) { + fini(env, static_cast(data), static_cast(hint)); + }, + hint); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template +inline T* Env::GetInstanceData() const { + void* data = nullptr; + + napi_status status = napi_get_instance_data(_env, &data); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + + return static_cast(data); +} + +template +void Env::DefaultFini(Env, T* data) { + delete data; +} + +template +void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) { + delete data; +} +#endif // NAPI_VERSION > 5 +#endif // !defined(__OHOS__) + +#if NAPI_VERSION > 8 +inline const char* Env::GetModuleFileName() const { + const char* result; + napi_status status = node_api_get_module_file_name(_env, &result); + NAPI_THROW_IF_FAILED(*this, status, nullptr); + return result; +} +#endif // NAPI_VERSION > 8 +//////////////////////////////////////////////////////////////////////////////// +// Value class +//////////////////////////////////////////////////////////////////////////////// + +inline Value::Value() : _env(nullptr), _value(nullptr) {} + +inline Value::Value(napi_env env, napi_value value) + : _env(env), _value(value) {} + +inline Value::operator napi_value() const { + return _value; +} + +inline bool Value::operator==(const Value& other) const { + return StrictEquals(other); +} + +inline bool Value::operator!=(const Value& other) const { + return !this->operator==(other); +} + +inline bool Value::StrictEquals(const Value& other) const { + bool result; + napi_status status = napi_strict_equals(_env, *this, other, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline Napi::Env Value::Env() const { + return Napi::Env(_env); +} + +inline bool Value::IsEmpty() const { + return _value == nullptr; +} + +inline napi_valuetype Value::Type() const { + if (IsEmpty()) { + return napi_undefined; + } + + napi_valuetype type; + napi_status status = napi_typeof(_env, _value, &type); + NAPI_THROW_IF_FAILED(_env, status, napi_undefined); + return type; +} + +inline bool Value::IsUndefined() const { + return Type() == napi_undefined; +} + +inline bool Value::IsNull() const { + return Type() == napi_null; +} + +inline bool Value::IsBoolean() const { + return Type() == napi_boolean; +} + +inline bool Value::IsNumber() const { + return Type() == napi_number; +} + +#if NAPI_VERSION > 5 +inline bool Value::IsBigInt() const { + return Type() == napi_bigint; +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +inline bool Value::IsDate() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_date(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + +inline bool Value::IsString() const { + return Type() == napi_string; +} + +inline bool Value::IsSymbol() const { + return Type() == napi_symbol; +} + +inline bool Value::IsArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_array(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsArrayBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_arraybuffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsTypedArray() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_typedarray(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsObject() const { + return Type() == napi_object || IsFunction(); +} + +inline bool Value::IsFunction() const { + return Type() == napi_function; +} + +inline bool Value::IsPromise() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_promise(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsDataView() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_dataview(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = napi_is_buffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +inline bool Value::IsExternal() const { + return Type() == napi_external; +} + +template +inline T Value::As() const { +#ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS + T::CheckCast(_env, _value); +#endif + return T(_env, _value); +} + +inline MaybeOrValue Value::ToBoolean() const { + napi_value result; + napi_status status = napi_coerce_to_bool(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); +} + +inline MaybeOrValue Value::ToNumber() const { + napi_value result; + napi_status status = napi_coerce_to_number(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); +} + +inline MaybeOrValue Value::ToString() const { + napi_value result; + napi_status status = napi_coerce_to_string(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); +} + +inline MaybeOrValue Value::ToObject() const { + napi_value result; + napi_status status = napi_coerce_to_object(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Boolean class +//////////////////////////////////////////////////////////////////////////////// + +inline Boolean Boolean::New(napi_env env, bool val) { + napi_value value; + napi_status status = napi_get_boolean(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Boolean()); + return Boolean(env, value); +} + +inline void Boolean::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Boolean::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_boolean, "Boolean::CheckCast", "value is not napi_boolean"); +} + +inline Boolean::Boolean() : Napi::Value() {} + +inline Boolean::Boolean(napi_env env, napi_value value) + : Napi::Value(env, value) {} + +inline Boolean::operator bool() const { + return Value(); +} + +inline bool Boolean::Value() const { + bool result; + napi_status status = napi_get_value_bool(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Number class +//////////////////////////////////////////////////////////////////////////////// + +inline Number Number::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_double(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Number()); + return Number(env, value); +} + +inline void Number::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Number::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_number, "Number::CheckCast", "value is not napi_number"); +} + +inline Number::Number() : Value() {} + +inline Number::Number(napi_env env, napi_value value) : Value(env, value) {} + +inline Number::operator int32_t() const { + return Int32Value(); +} + +inline Number::operator uint32_t() const { + return Uint32Value(); +} + +inline Number::operator int64_t() const { + return Int64Value(); +} + +inline Number::operator float() const { + return FloatValue(); +} + +inline Number::operator double() const { + return DoubleValue(); +} + +inline int32_t Number::Int32Value() const { + int32_t result; + napi_status status = napi_get_value_int32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint32_t Number::Uint32Value() const { + uint32_t result; + napi_status status = napi_get_value_uint32(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline int64_t Number::Int64Value() const { + int64_t result; + napi_status status = napi_get_value_int64(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline float Number::FloatValue() const { + return static_cast(DoubleValue()); +} + +inline double Number::DoubleValue() const { + double result; + napi_status status = napi_get_value_double(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// BigInt Class +//////////////////////////////////////////////////////////////////////////////// + +inline BigInt BigInt::New(napi_env env, int64_t val) { + napi_value value; + napi_status status = napi_create_bigint_int64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, uint64_t val) { + napi_value value; + napi_status status = napi_create_bigint_uint64(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline BigInt BigInt::New(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words) { + napi_value value; + napi_status status = + napi_create_bigint_words(env, sign_bit, word_count, words, &value); + NAPI_THROW_IF_FAILED(env, status, BigInt()); + return BigInt(env, value); +} + +inline void BigInt::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "BigInt::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_bigint, "BigInt::CheckCast", "value is not napi_bigint"); +} + +inline BigInt::BigInt() : Value() {} + +inline BigInt::BigInt(napi_env env, napi_value value) : Value(env, value) {} + +inline int64_t BigInt::Int64Value(bool* lossless) const { + int64_t result; + napi_status status = + napi_get_value_bigint_int64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline uint64_t BigInt::Uint64Value(bool* lossless) const { + uint64_t result; + napi_status status = + napi_get_value_bigint_uint64(_env, _value, &result, lossless); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +inline size_t BigInt::WordCount() const { + size_t word_count; + napi_status status = + napi_get_value_bigint_words(_env, _value, nullptr, &word_count, nullptr); + NAPI_THROW_IF_FAILED(_env, status, 0); + return word_count; +} + +inline void BigInt::ToWords(int* sign_bit, + size_t* word_count, + uint64_t* words) { + napi_status status = + napi_get_value_bigint_words(_env, _value, sign_bit, word_count, words); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +//////////////////////////////////////////////////////////////////////////////// +// Date Class +//////////////////////////////////////////////////////////////////////////////// + +inline Date Date::New(napi_env env, double val) { + napi_value value; + napi_status status = napi_create_date(env, val, &value); + NAPI_THROW_IF_FAILED(env, status, Date()); + return Date(env, value); +} + +inline void Date::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_date(env, value, &result); + NAPI_CHECK(status == napi_ok, "Date::CheckCast", "napi_is_date failed"); + NAPI_CHECK(result, "Date::CheckCast", "value is not date"); +} + +inline Date::Date() : Value() {} + +inline Date::Date(napi_env env, napi_value value) : Value(env, value) {} + +inline Date::operator double() const { + return ValueOf(); +} + +inline double Date::ValueOf() const { + double result; + napi_status status = napi_get_date_value(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Name class +//////////////////////////////////////////////////////////////////////////////// +inline void Name::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Name::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol"); +} + +inline Name::Name() : Value() {} + +inline Name::Name(napi_env env, napi_value value) : Value(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// String class +//////////////////////////////////////////////////////////////////////////////// + +inline String String::New(napi_env env, const std::string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const std::u16string& val) { + return String::New(env, val.c_str(), val.size()); +} + +inline String String::New(napi_env env, const char* val) { + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_value value; + napi_status status = + napi_create_string_utf8(env, val, std::strlen(val), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val) { + napi_value value; + // TODO(@gabrielschulhof) Remove if-statement when core's error handling is + // available in all supported versions. + if (val == nullptr) { + // Throw an error that looks like it came from core. + NAPI_THROW_IF_FAILED(env, napi_invalid_arg, String()); + } + napi_status status = + napi_create_string_utf16(env, val, std::u16string(val).size(), &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf8(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline String String::New(napi_env env, const char16_t* val, size_t length) { + napi_value value; + napi_status status = napi_create_string_utf16(env, val, length, &value); + NAPI_THROW_IF_FAILED(env, status, String()); + return String(env, value); +} + +inline void String::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "String::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_string, "String::CheckCast", "value is not napi_string"); +} + +inline String::String() : Name() {} + +inline String::String(napi_env env, napi_value value) : Name(env, value) {} + +inline String::operator std::string() const { + return Utf8Value(); +} + +inline String::operator std::u16string() const { + return Utf16Value(); +} + +inline std::string String::Utf8Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, ""); + + std::string value; + value.reserve(length + 1); + value.resize(length); +#if defined(__OHOS__) + status = napi_get_value_string_utf8( + _env, _value, &value[0], value.capacity(), &length); +#else + status = napi_get_value_string_utf8( + _env, _value, &value[0], value.capacity(), nullptr); +#endif + NAPI_THROW_IF_FAILED(_env, status, ""); + return value; +} + +inline std::u16string String::Utf16Value() const { + size_t length; + napi_status status = + napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + + std::u16string value; + value.reserve(length + 1); + value.resize(length); + status = napi_get_value_string_utf16( + _env, _value, &value[0], value.capacity(), nullptr); + NAPI_THROW_IF_FAILED(_env, status, NAPI_WIDE_TEXT("")); + return value; +} + +//////////////////////////////////////////////////////////////////////////////// +// Symbol class +//////////////////////////////////////////////////////////////////////////////// + +inline Symbol Symbol::New(napi_env env, const char* description) { + napi_value descriptionValue = description != nullptr + ? String::New(env, description) + : static_cast(nullptr); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, String description) { + napi_value descriptionValue = description; + return Symbol::New(env, descriptionValue); +} + +inline Symbol Symbol::New(napi_env env, napi_value description) { + napi_value value; + napi_status status = napi_create_symbol(env, description, &value); + NAPI_THROW_IF_FAILED(env, status, Symbol()); + return Symbol(env, value); +} + +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + return Napi::Env(env) + .Global() + .Get("Symbol") + .As() + .Get(name) + .As(); +#endif +} + +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + +inline MaybeOrValue Symbol::For(napi_env env, String description) { + return Symbol::For(env, static_cast(description)); +} + +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif +} + +inline void Symbol::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Symbol::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_symbol, "Symbol::CheckCast", "value is not napi_symbol"); +} + +inline Symbol::Symbol() : Name() {} + +inline Symbol::Symbol(napi_env env, napi_value value) : Name(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// Automagic value creation +//////////////////////////////////////////////////////////////////////////////// + +namespace details { +template +struct vf_number { + static Number From(napi_env env, T value) { + return Number::New(env, static_cast(value)); + } +}; + +template <> +struct vf_number { + static Boolean From(napi_env env, bool value) { + return Boolean::New(env, value); + } +}; + +struct vf_utf8_charp { + static String From(napi_env env, const char* value) { + return String::New(env, value); + } +}; + +struct vf_utf16_charp { + static String From(napi_env env, const char16_t* value) { + return String::New(env, value); + } +}; +struct vf_utf8_string { + static String From(napi_env env, const std::string& value) { + return String::New(env, value); + } +}; + +struct vf_utf16_string { + static String From(napi_env env, const std::u16string& value) { + return String::New(env, value); + } +}; + +template +struct vf_fallback { + static Value From(napi_env env, const T& value) { return Value(env, value); } +}; + +template +struct disjunction : std::false_type {}; +template +struct disjunction : B {}; +template +struct disjunction + : std::conditional>::type {}; + +template +struct can_make_string + : disjunction::type, + typename std::is_convertible::type, + typename std::is_convertible::type, + typename std::is_convertible::type> {}; +} // namespace details + +template +Value Value::From(napi_env env, const T& value) { + using Helper = typename std::conditional< + std::is_integral::value || std::is_floating_point::value, + details::vf_number, + typename std::conditional::value, + String, + details::vf_fallback>::type>::type; + return Helper::From(env, value); +} + +template +String String::From(napi_env env, const T& value) { + struct Dummy {}; + using Helper = typename std::conditional< + std::is_convertible::value, + details::vf_utf8_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_charp, + typename std::conditional< + std::is_convertible::value, + details::vf_utf8_string, + typename std::conditional< + std::is_convertible::value, + details::vf_utf16_string, + Dummy>::type>::type>::type>::type; + return Helper::From(env, value); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypeTaggable class +//////////////////////////////////////////////////////////////////////////////// + +inline TypeTaggable::TypeTaggable() : Value() {} + +inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value) + : Value(_env, _value) {} + +#if NAPI_VERSION >= 8 + +inline void TypeTaggable::TypeTag(const napi_type_tag* type_tag) const { + napi_status status = napi_type_tag_object(_env, _value, type_tag); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const { + bool result; + napi_status status = + napi_check_object_type_tag(_env, _value, type_tag, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} + +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// Object class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object::PropertyLValue::operator Value() const { + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif +} + +template +template +inline Object::PropertyLValue& Object::PropertyLValue::operator=( + ValueType value) { +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif + return *this; +} + +template +inline Object::PropertyLValue::PropertyLValue(Object object, Key key) + : _env(object.Env()), _object(object), _key(key) {} + +inline Object Object::New(napi_env env) { + napi_value value; + napi_status status = napi_create_object(env, &value); + NAPI_THROW_IF_FAILED(env, status, Object()); + return Object(env, value); +} + +inline void Object::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Object::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); + NAPI_CHECK( + type == napi_object, "Object::CheckCast", "value is not napi_object"); +} + +inline Object::Object() : TypeTaggable() {} + +inline Object::Object(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +inline Object::PropertyLValue Object::operator[]( + const char* utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[]( + const std::string& utf8name) { + return PropertyLValue(*this, utf8name); +} + +inline Object::PropertyLValue Object::operator[](uint32_t index) { + return PropertyLValue(*this, index); +} + +inline Object::PropertyLValue Object::operator[](Value index) const { + return PropertyLValue(*this, index); +} + +inline MaybeOrValue Object::operator[](const char* utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { + return Get(utf8name); +} + +inline MaybeOrValue Object::operator[](uint32_t index) const { + return Get(index); +} + +inline MaybeOrValue Object::Has(napi_value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(Value key) const { + bool result; + napi_status status = napi_has_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const char* utf8name) const { + bool result; + napi_status status = napi_has_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Has(const std::string& utf8name) const { + return Has(utf8name.c_str()); +} + +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(Value key) const { + bool result; + napi_status status = napi_has_own_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { + napi_value key; + napi_status status = + napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); + return HasOwnProperty(key); +} + +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { + return HasOwnProperty(utf8name.c_str()); +} + +inline MaybeOrValue Object::Get(napi_value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(Value key) const { + napi_value result; + napi_status status = napi_get_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const char* utf8name) const { + napi_value result; + napi_status status = napi_get_named_property(_env, _value, utf8name, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); +} + +inline MaybeOrValue Object::Get(const std::string& utf8name) const { + return Get(utf8name.c_str()); +} + +template +inline MaybeOrValue Object::Set(napi_value key, + const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(Value key, const ValueType& value) const { + napi_status status = + napi_set_property(_env, _value, key, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) const { + napi_status status = + napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +template +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) const { + return Set(utf8name.c_str(), value); +} + +inline MaybeOrValue Object::Delete(napi_value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(Value key) const { + bool result; + napi_status status = napi_delete_property(_env, _value, key, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Delete(const char* utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Delete(const std::string& utf8name) const { + return Delete(String::New(_env, utf8name)); +} + +inline MaybeOrValue Object::Has(uint32_t index) const { + bool result; + napi_status status = napi_has_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::Get(uint32_t index) const { + napi_value value; + napi_status status = napi_get_element(_env, _value, index, &value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); +} + +template +inline MaybeOrValue Object::Set(uint32_t index, + const ValueType& value) const { + napi_status status = + napi_set_element(_env, _value, index, Value::From(_env, value)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Delete(uint32_t index) const { + bool result; + napi_status status = napi_delete_element(_env, _value, index, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +inline MaybeOrValue Object::GetPropertyNames() const { + napi_value result; + napi_status status = napi_get_property_names(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); +} + +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) const { + napi_status status = napi_define_properties( + _env, + _value, + 1, + reinterpret_cast(&property)); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::initializer_list& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.begin())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::DefineProperties( + const std::vector& properties) const { + napi_status status = napi_define_properties( + _env, + _value, + properties.size(), + reinterpret_cast(properties.data())); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { + bool result; + napi_status status = napi_instanceof(_env, _value, constructor, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + details::AttachData::Wrapper>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = details:: + AttachData::WrapperWithHint>( + _env, *this, data, finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +#ifdef NAPI_CPP_EXCEPTIONS +inline Object::const_iterator::const_iterator(const Object* object, + const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::const_iterator Napi::Object::begin() const { + const_iterator it(this, Object::const_iterator::Type::BEGIN); + return it; +} + +inline Object::const_iterator Napi::Object::end() const { + const_iterator it(this, Object::const_iterator::Type::END); + return it; +} + +inline Object::const_iterator& Object::const_iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::const_iterator::operator==( + const const_iterator& other) const { + return _index == other._index; +} + +inline bool Object::const_iterator::operator!=( + const const_iterator& other) const { + return _index != other._index; +} + +inline const std::pair> +Object::const_iterator::operator*() const { + const Value key = _keys[_index]; + const PropertyLValue value = (*_object)[key]; + return {key, value}; +} + +inline Object::iterator::iterator(Object* object, const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::iterator Napi::Object::begin() { + iterator it(this, Object::iterator::Type::BEGIN); + return it; +} + +inline Object::iterator Napi::Object::end() { + iterator it(this, Object::iterator::Type::END); + return it; +} + +inline Object::iterator& Object::iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::iterator::operator==(const iterator& other) const { + return _index == other._index; +} + +inline bool Object::iterator::operator!=(const iterator& other) const { + return _index != other._index; +} + +inline std::pair> +Object::iterator::operator*() { + Value key = _keys[_index]; + PropertyLValue value = (*_object)[key]; + return {key, value}; +} +#endif // NAPI_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 +inline MaybeOrValue Object::Freeze() const { + napi_status status = napi_object_freeze(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} + +inline MaybeOrValue Object::Seal() const { + napi_status status = napi_object_seal(_env, _value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif // NAPI_VERSION >= 8 + +//////////////////////////////////////////////////////////////////////////////// +// External class +//////////////////////////////////////////////////////////////////////////////// + +template +inline External External::New(napi_env env, T* data) { + napi_value value; + napi_status status = + napi_create_external(env, data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, External()); + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external(env, + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +template +inline External External::New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external( + env, + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, External()); + } + return External(env, value); +} + +template +inline void External::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "External::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_external, + "External::CheckCast", + "value is not napi_external"); +} + +template +inline External::External() : TypeTaggable() {} + +template +inline External::External(napi_env env, napi_value value) + : TypeTaggable(env, value) {} + +template +inline T* External::Data() const { + void* data; + napi_status status = napi_get_value_external(_env, _value, &data); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return reinterpret_cast(data); +} + +//////////////////////////////////////////////////////////////////////////////// +// Array class +//////////////////////////////////////////////////////////////////////////////// + +inline Array Array::New(napi_env env) { + napi_value value; + napi_status status = napi_create_array(env, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline Array Array::New(napi_env env, size_t length) { + napi_value value; + napi_status status = napi_create_array_with_length(env, length, &value); + NAPI_THROW_IF_FAILED(env, status, Array()); + return Array(env, value); +} + +inline void Array::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Array::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_array(env, value, &result); + NAPI_CHECK(status == napi_ok, "Array::CheckCast", "napi_is_array failed"); + NAPI_CHECK(result, "Array::CheckCast", "value is not array"); +} + +inline Array::Array() : Object() {} + +inline Array::Array(napi_env env, napi_value value) : Object(env, value) {} + +inline uint32_t Array::Length() const { + uint32_t result; + napi_status status = napi_get_array_length(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// ArrayBuffer class +//////////////////////////////////////////////////////////////////////////////// + +inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { + napi_value value; + void* data; + napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength) { + napi_value value; + napi_status status = napi_create_external_arraybuffer( + env, externalData, byteLength, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} + +template +inline ArrayBuffer ArrayBuffer::New(napi_env env, + void* externalData, + size_t byteLength, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_arraybuffer( + env, + externalData, + byteLength, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); + } + + return ArrayBuffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +inline void ArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "ArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_arraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "ArrayBuffer::CheckCast", + "napi_is_arraybuffer failed"); + NAPI_CHECK(result, "ArrayBuffer::CheckCast", "value is not arraybuffer"); +} + +inline ArrayBuffer::ArrayBuffer() : Object() {} + +inline ArrayBuffer::ArrayBuffer(napi_env env, napi_value value) + : Object(env, value) {} + +inline void* ArrayBuffer::Data() { + void* data; +#if !defined(__OHOS__) + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); +#else + size_t length; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &length); +#endif + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; +} + +inline size_t ArrayBuffer::ByteLength() { + size_t length; +#if !defined(__OHOS__) + napi_status status = napi_get_arraybuffer_info(_env, _value, nullptr, &length); +#else + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, &length); +#endif + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; +} + +#if NAPI_VERSION >= 7 +inline bool ArrayBuffer::IsDetached() const { + bool detached; + napi_status status = napi_is_detached_arraybuffer(_env, _value, &detached); + NAPI_THROW_IF_FAILED(_env, status, false); + return detached; +} + +inline void ArrayBuffer::Detach() { + napi_status status = napi_detach_arraybuffer(_env, _value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} +#endif // NAPI_VERSION >= 7 + +//////////////////////////////////////////////////////////////////////////////// +// DataView class +//////////////////////////////////////////////////////////////////////////////// +inline DataView DataView::New(napi_env env, Napi::ArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); + } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} + +inline void DataView::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_dataview(env, value, &result); + NAPI_CHECK( + status == napi_ok, "DataView::CheckCast", "napi_is_dataview failed"); + NAPI_CHECK(result, "DataView::CheckCast", "value is not dataview"); +} + +inline DataView::DataView() : Object() {} + +inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + &_length /* byteLength */, + &_data /* data */, + nullptr /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + &arrayBuffer /* arrayBuffer */, + nullptr /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +inline size_t DataView::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_dataview_info(_env, + _value /* dataView */, + nullptr /* byteLength */, + nullptr /* data */, + nullptr /* arrayBuffer */, + &byteOffset /* byteOffset */); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t DataView::ByteLength() const { + return _length; +} + +inline void* DataView::Data() const { + return _data; +} + +inline float DataView::GetFloat32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline double DataView::GetFloat64(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int8_t DataView::GetInt8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int16_t DataView::GetInt16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline int32_t DataView::GetInt32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint8_t DataView::GetUint8(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint16_t DataView::GetUint16(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline uint32_t DataView::GetUint32(size_t byteOffset) const { + return ReadData(byteOffset); +} + +inline void DataView::SetFloat32(size_t byteOffset, float value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetFloat64(size_t byteOffset, double value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt8(size_t byteOffset, int8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt16(size_t byteOffset, int16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetInt32(size_t byteOffset, int32_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint8(size_t byteOffset, uint8_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint16(size_t byteOffset, uint16_t value) const { + WriteData(byteOffset, value); +} + +inline void DataView::SetUint32(size_t byteOffset, uint32_t value) const { + WriteData(byteOffset, value); +} + +template +inline T DataView::ReadData(size_t byteOffset) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW( + RangeError::New(_env, "Offset is outside the bounds of the DataView"), + 0); + } + + return *reinterpret_cast(static_cast(_data) + byteOffset); +} + +template +inline void DataView::WriteData(size_t byteOffset, T value) const { + if (byteOffset + sizeof(T) > _length || + byteOffset + sizeof(T) < byteOffset) { // overflow + NAPI_THROW_VOID( + RangeError::New(_env, "Offset is outside the bounds of the DataView")); + } + + *reinterpret_cast(static_cast(_data) + byteOffset) = value; +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArray class +//////////////////////////////////////////////////////////////////////////////// +inline void TypedArray::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "TypedArray::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_typedarray(env, value, &result); + NAPI_CHECK( + status == napi_ok, "TypedArray::CheckCast", "napi_is_typedarray failed"); + NAPI_CHECK(result, "TypedArray::CheckCast", "value is not typedarray"); +} + +inline TypedArray::TypedArray() + : Object(), _type(napi_typedarray_type::napi_int8_array), _length(0) {} + +inline TypedArray::TypedArray(napi_env env, napi_value value) + : Object(env, value), + _type(napi_typedarray_type::napi_int8_array), + _length(0) { + if (value != nullptr) { + napi_status status = + napi_get_typedarray_info(_env, + _value, + &const_cast(this)->_type, + &const_cast(this)->_length, + nullptr, + nullptr, + nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline TypedArray::TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length) + : Object(env, value), _type(type), _length(length) {} + +inline napi_typedarray_type TypedArray::TypedArrayType() const { + return _type; +} + +inline uint8_t TypedArray::ElementSize() const { + switch (_type) { + case napi_int8_array: + case napi_uint8_array: + case napi_uint8_clamped_array: + return 1; + case napi_int16_array: + case napi_uint16_array: + return 2; + case napi_int32_array: + case napi_uint32_array: + case napi_float32_array: + return 4; + case napi_float64_array: +#if (NAPI_VERSION > 5) + case napi_bigint64_array: + case napi_biguint64_array: +#endif // (NAPI_VERSION > 5) + return 8; + default: + return 0; + } +} + +inline size_t TypedArray::ElementLength() const { + return _length; +} + +inline size_t TypedArray::ByteOffset() const { + size_t byteOffset; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); + NAPI_THROW_IF_FAILED(_env, status, 0); + return byteOffset; +} + +inline size_t TypedArray::ByteLength() const { + return ElementSize() * ElementLength(); +} + +inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); + return Napi::ArrayBuffer(_env, arrayBuffer); +} + +//////////////////////////////////////////////////////////////////////////////// +// TypedArrayOf class +//////////////////////////////////////////////////////////////////////////////// +template +inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { + TypedArray::CheckCast(env, value); + napi_typedarray_type type; + napi_status status = napi_get_typedarray_info( + env, value, &type, nullptr, nullptr, nullptr, nullptr); + NAPI_CHECK(status == napi_ok, + "TypedArrayOf::CheckCast", + "napi_is_typedarray failed"); + + NAPI_CHECK( + (type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && std::is_same::value)), + "TypedArrayOf::CheckCast", + "Array type must match the template parameter. (Uint8 arrays may " + "optionally have the \"clamped\" array type.)"); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + napi_typedarray_type type) { + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New(env, elementLength * sizeof(T)); + return New(env, elementLength, arrayBuffer, 0, type); +} + +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::ArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} + +template +inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) + : TypedArray(env, value), _data(nullptr) { + napi_status status = napi_ok; + if (value != nullptr) { + void* data = nullptr; + status = napi_get_typedarray_info( + _env, _value, &_type, &_length, &data, nullptr, nullptr); + _data = static_cast(data); + } else { + _type = TypedArrayTypeForPrimitiveType(); + _length = 0; + } + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +template +inline TypedArrayOf::TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data) + : TypedArray(env, value, type, length), _data(data) { + if (!(type == TypedArrayTypeForPrimitiveType() || + (type == napi_uint8_clamped_array && + std::is_same::value))) { + NAPI_THROW_VOID(TypeError::New( + env, + "Array type must match the template parameter. " + "(Uint8 arrays may optionally have the \"clamped\" array type.)")); + } +} + +template +inline T& TypedArrayOf::operator[](size_t index) { + return _data[index]; +} + +template +inline const T& TypedArrayOf::operator[](size_t index) const { + return _data[index]; +} + +template +inline T* TypedArrayOf::Data() { + return _data; +} + +template +inline const T* TypedArrayOf::Data() const { + return _data; +} + +//////////////////////////////////////////////////////////////////////////////// +// Function class +//////////////////////////////////////////////////////////////////////////////// + +template +inline napi_status CreateFunction(napi_env env, + const char* utf8name, + napi_callback cb, + CbData* data, + napi_value* result) { + napi_status status = + napi_create_function(env, utf8name, NAPI_AUTO_LENGTH, cb, data, result); + if (status == napi_ok) { + status = Napi::details::AttachData(env, *result, data); + } + + return status; +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedVoidCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, const char* utf8name, void* data) { + napi_value result = nullptr; + napi_status status = napi_create_function(env, + utf8name, + NAPI_AUTO_LENGTH, + details::TemplatedCallback, + data, + &result); + NAPI_THROW_IF_FAILED(env, status, Function()); + return Function(env, result); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + const std::string& utf8name, + void* data) { + return Function::New(env, utf8name.c_str(), data); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const char* utf8name, + void* data) { + using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr))); + using CbData = details::CallbackData; + auto callbackData = new CbData{std::move(cb), data}; + + napi_value value; + napi_status status = + CreateFunction(env, utf8name, CbData::Wrapper, callbackData, &value); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, Function()); + } + + return Function(env, value); +} + +template +inline Function Function::New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data) { + return New(env, cb, utf8name.c_str(), data); +} + +inline void Function::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Function::CheckCast", "empty value"); + + napi_valuetype type; + napi_status status = napi_typeof(env, value, &type); + NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); + NAPI_CHECK(type == napi_function, + "Function::CheckCast", + "value is not napi_function"); +} + +inline Function::Function() : Object() {} + +inline Function::Function(napi_env env, napi_value value) + : Object(env, value) {} + +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call( + const std::vector& args) const { + return Call(Env().Undefined(), args); +} + +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { + return Call(Env().Undefined(), argc, args); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { + return Call(recv, args.size(), args.begin()); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + return Call(recv, args.size(), args.data()); +} + +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { + const size_t argc = args.size(); + const size_t stackArgsCount = 6; + napi_value stackArgs[stackArgsCount]; + std::vector heapArgs; + napi_value* argv; + if (argc <= stackArgsCount) { + argv = stackArgs; + } else { + heapArgs.resize(argc); + argv = heapArgs.data(); + } + + for (size_t index = 0; index < argc; index++) { + argv[index] = static_cast(args[index]); + } + + return Call(recv, argc, argv); +} + +inline MaybeOrValue Function::Call(napi_value recv, + size_t argc, + const napi_value* args) const { + napi_value result; + napi_status status = + napi_call_function(_env, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.begin(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + return MakeCallback(recv, args.size(), args.data(), context); +} + +inline MaybeOrValue Function::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + napi_value result; + napi_status status = + napi_make_callback(_env, context, recv, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); +} + +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { + return New(args.size(), args.begin()); +} + +inline MaybeOrValue Function::New( + const std::vector& args) const { + return New(args.size(), args.data()); +} + +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { + napi_value result; + napi_status status = napi_new_instance(_env, _value, argc, args, &result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); +} + +//////////////////////////////////////////////////////////////////////////////// +// Promise class +//////////////////////////////////////////////////////////////////////////////// + +inline Promise::Deferred Promise::Deferred::New(napi_env env) { + return Promise::Deferred(env); +} + +inline Promise::Deferred::Deferred(napi_env env) : _env(env) { + napi_status status = napi_create_promise(_env, &_deferred, &_promise); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline Promise Promise::Deferred::Promise() const { + return Napi::Promise(_env, _promise); +} + +inline Napi::Env Promise::Deferred::Env() const { + return Napi::Env(_env); +} + +inline void Promise::Deferred::Resolve(napi_value value) const { + napi_status status = napi_resolve_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::Deferred::Reject(napi_value value) const { + napi_status status = napi_reject_deferred(_env, _deferred, value); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void Promise::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Promise::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_promise(env, value, &result); + NAPI_CHECK(status == napi_ok, "Promise::CheckCast", "napi_is_promise failed"); + NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); +} + +inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} + +//////////////////////////////////////////////////////////////////////////////// +// Buffer class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Buffer Buffer::New(napi_env env, size_t length) { + napi_value value; + void* data; + napi_status status = + napi_create_buffer(env, length * sizeof(T), &data, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +template +inline Buffer Buffer::New(napi_env env, T* data, size_t length) { + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} + +template +template +inline Buffer Buffer::New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + napi_value value; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +} +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + +template +inline Buffer Buffer::NewOrCopy(napi_env env, T* data, size_t length) { +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, length * sizeof(T), data, nullptr, nullptr, &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + return Buffer::Copy(env, data, length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = + napi_create_external_buffer(env, + length * sizeof(T), + data, + details::FinalizeData::Wrapper, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::Wrapper(env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED +} + +template +template +inline Buffer Buffer::NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + napi_value value; + napi_status status = napi_create_external_buffer( + env, + length * sizeof(T), + data, + details::FinalizeData::WrapperWithHint, + finalizeData, + &value); + if (status == details::napi_no_external_buffers_allowed) { +#endif + // If we can't create an external buffer, we'll just copy the data. + Buffer ret = Buffer::Copy(env, data, length); + details::FinalizeData::WrapperWithHint( + env, data, finalizeData); + return ret; +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + } + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, Buffer()); + } + return Buffer(env, value); +#endif +} + +template +inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { + napi_value value; + napi_status status = + napi_create_buffer_copy(env, length * sizeof(T), data, nullptr, &value); + NAPI_THROW_IF_FAILED(env, status, Buffer()); + return Buffer(env, value); +} + +template +inline void Buffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "Buffer::CheckCast", "empty value"); + + bool result; + napi_status status = napi_is_buffer(env, value, &result); + NAPI_CHECK(status == napi_ok, "Buffer::CheckCast", "napi_is_buffer failed"); + NAPI_CHECK(result, "Buffer::CheckCast", "value is not buffer"); +} + +template +inline Buffer::Buffer() : Uint8Array() {} + +template +inline Buffer::Buffer(napi_env env, napi_value value) + : Uint8Array(env, value) {} + +template +inline size_t Buffer::Length() const { + return ByteLength() / sizeof(T); +} + +template +inline T* Buffer::Data() const { + return reinterpret_cast(const_cast(Uint8Array::Data())); +} + +//////////////////////////////////////////////////////////////////////////////// +// Error class +//////////////////////////////////////////////////////////////////////////////// + +inline Error Error::New(napi_env env) { + napi_status status; + napi_value error = nullptr; + bool is_exception_pending; + napi_extended_error_info last_error_info_copy; + + { + // We must retrieve the last error info before doing anything else because + // doing anything else will replace the last error info. + const napi_extended_error_info* last_error_info; + status = napi_get_last_error_info(env, &last_error_info); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info"); + + // All fields of the `napi_extended_error_info` structure gets reset in + // subsequent Node-API function calls on the same `env`. This includes a + // call to `napi_is_exception_pending()`. So here it is necessary to make a + // copy of the information as the `error_code` field is used later on. + memcpy(&last_error_info_copy, + last_error_info, + sizeof(napi_extended_error_info)); + } + + status = napi_is_exception_pending(env, &is_exception_pending); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending"); + + // A pending exception takes precedence over any internal error status. + if (is_exception_pending) { + status = napi_get_and_clear_last_exception(env, &error); + NAPI_FATAL_IF_FAILED( + status, "Error::New", "napi_get_and_clear_last_exception"); + } else { + const char* error_message = last_error_info_copy.error_message != nullptr + ? last_error_info_copy.error_message + : "Error in native callback"; + + napi_value message; + status = napi_create_string_utf8( + env, error_message, std::strlen(error_message), &message); + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8"); + + switch (last_error_info_copy.error_code) { + case napi_object_expected: + case napi_string_expected: + case napi_boolean_expected: + case napi_number_expected: + status = napi_create_type_error(env, nullptr, message, &error); + break; + default: + status = napi_create_error(env, nullptr, message, &error); + break; + } + NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error"); + } + + return Error(env, error); +} + +inline Error Error::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_error); +} + +inline Error Error::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_error); +} + +inline NAPI_NO_RETURN void Error::Fatal(const char* location, + const char* message) { + napi_fatal_error(location, NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH); +} + +inline Error::Error() : ObjectReference() {} + +inline Error::Error(napi_env env, napi_value value) + : ObjectReference(env, nullptr) { + if (value != nullptr) { + // Attempting to create a reference on the error object. + // If it's not a Object/Function/Symbol, this call will return an error + // status. + napi_status status = napi_create_reference(env, value, 1, &_ref); + + if (status != napi_ok) { + napi_value wrappedErrorObj; + + // Create an error object + status = napi_create_object(env, &wrappedErrorObj); + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); + + // property flag that we attach to show the error object is wrapped + napi_property_descriptor wrapObjFlag = { + ERROR_WRAP_VALUE(), // Unique GUID identifier since Symbol isn't a + // viable option + nullptr, + nullptr, + nullptr, + nullptr, + Value::From(env, value), + napi_enumerable, + nullptr}; + + status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + if (status == napi_pending_exception) { + // Test if the pending exception was reported because the environment is + // shutting down. We assume that a status of napi_pending_exception + // coupled with the absence of an actual pending exception means that + // the environment is shutting down. If so, we replace the + // napi_pending_exception status with napi_ok. + bool is_exception_pending = false; + status = napi_is_exception_pending(env, &is_exception_pending); + if (status == napi_ok && !is_exception_pending) { + status = napi_ok; + } else { + status = napi_pending_exception; + } + } +#endif // NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); + + // Create a reference on the newly wrapped object + status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); + } + + // Avoid infinite recursion in the failure case. + NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); + } +} + +inline Object Error::Value() const { + if (_ref == nullptr) { + return Object(_env, nullptr); + } + + napi_value refValue; + napi_status status = napi_get_reference_value(_env, _ref, &refValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + napi_valuetype type; + status = napi_typeof(_env, refValue, &type); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + // If refValue isn't a symbol, then we proceed to whether the refValue has the + // wrapped error flag + if (type != napi_symbol) { + // We are checking if the object is wrapped + bool isWrappedObject = false; + + status = napi_has_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &isWrappedObject); + + // Don't care about status + if (isWrappedObject) { + napi_value unwrappedValue; + status = napi_get_property(_env, + refValue, + String::From(_env, ERROR_WRAP_VALUE()), + &unwrappedValue); + NAPI_THROW_IF_FAILED(_env, status, Object()); + + return Object(_env, unwrappedValue); + } + } + + return Object(_env, refValue); +} + +inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {} + +inline Error& Error::operator=(Error&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline Error::Error(const Error& other) : ObjectReference(other) {} + +inline Error& Error::operator=(const Error& other) { + Reset(); + + _env = other.Env(); + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_THROW_IF_FAILED(_env, status, *this); + } + + return *this; +} + +inline const std::string& Error::Message() const NAPI_NOEXCEPT { + if (_message.size() == 0 && _env != nullptr) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + _message = Get("message").As(); + } catch (...) { + // Catch all errors here, to include e.g. a std::bad_alloc from + // the std::string::operator=, because this method may not throw. + } +#else // NAPI_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else + _message = Get("message").As(); +#endif +#endif // NAPI_CPP_EXCEPTIONS + } + return _message; +} + +// we created an object on the &_ref +inline void Error::ThrowAsJavaScriptException() const { + HandleScope scope(_env); + if (!IsEmpty()) { +#ifdef NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS + bool pendingException = false; + + // check if there is already a pending exception. If so don't try to throw a + // new one as that is not allowed/possible + napi_status status = napi_is_exception_pending(_env, &pendingException); + + if ((status != napi_ok) || + ((status == napi_ok) && (pendingException == false))) { + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + status = napi_throw(_env, Value()); + + if (status == napi_pending_exception) { + // The environment must be terminating as we checked earlier and there + // was no pending exception. In this case continuing will result + // in a fatal error and there is nothing the author has done incorrectly + // in their code that is worth flagging through a fatal error + return; + } + } else { + status = napi_pending_exception; + } +#else + // We intentionally don't use `NAPI_THROW_*` macros here to ensure + // that there is no possible recursion as `ThrowAsJavaScriptException` + // is part of `NAPI_THROW_*` macro definition for noexcept. + + napi_status status = napi_throw(_env, Value()); +#endif + +#ifdef NAPI_CPP_EXCEPTIONS + if (status != napi_ok) { + throw Error::New(_env); + } +#else // NAPI_CPP_EXCEPTIONS + NAPI_FATAL_IF_FAILED( + status, "Error::ThrowAsJavaScriptException", "napi_throw"); +#endif // NAPI_CPP_EXCEPTIONS + } +} + +#ifdef NAPI_CPP_EXCEPTIONS + +inline const char* Error::what() const NAPI_NOEXCEPT { + return Message().c_str(); +} + +#endif // NAPI_CPP_EXCEPTIONS + +inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { + return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; +} + +template +inline TError Error::New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error) { + napi_value str; + napi_status status = napi_create_string_utf8(env, message, length, &str); + NAPI_THROW_IF_FAILED(env, status, TError()); + + napi_value error; + status = create_error(env, nullptr, str, &error); + NAPI_THROW_IF_FAILED(env, status, TError()); + + return TError(env, error); +} + +inline TypeError TypeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_type_error); +} + +inline TypeError TypeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_type_error); +} + +inline TypeError::TypeError() : Error() {} + +inline TypeError::TypeError(napi_env env, napi_value value) + : Error(env, value) {} + +inline RangeError RangeError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), napi_create_range_error); +} + +inline RangeError RangeError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), napi_create_range_error); +} + +inline RangeError::RangeError() : Error() {} + +inline RangeError::RangeError(napi_env env, napi_value value) + : Error(env, value) {} + +#if NAPI_VERSION > 8 +inline SyntaxError SyntaxError::New(napi_env env, const char* message) { + return Error::New( + env, message, std::strlen(message), node_api_create_syntax_error); +} + +inline SyntaxError SyntaxError::New(napi_env env, const std::string& message) { + return Error::New( + env, message.c_str(), message.size(), node_api_create_syntax_error); +} + +inline SyntaxError::SyntaxError() : Error() {} + +inline SyntaxError::SyntaxError(napi_env env, napi_value value) + : Error(env, value) {} +#endif // NAPI_VERSION > 8 + +//////////////////////////////////////////////////////////////////////////////// +// Reference class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Reference Reference::New(const T& value, + uint32_t initialRefcount) { + napi_env env = value.Env(); + napi_value val = value; + + if (val == nullptr) { + return Reference(env, nullptr); + } + + napi_ref ref; + napi_status status = napi_create_reference(env, value, initialRefcount, &ref); + NAPI_THROW_IF_FAILED(env, status, Reference()); + + return Reference(env, ref); +} + +template +inline Reference::Reference() + : _env(nullptr), _ref(nullptr), _suppressDestruct(false) {} + +template +inline Reference::Reference(napi_env env, napi_ref ref) + : _env(env), _ref(ref), _suppressDestruct(false) {} + +template +inline Reference::~Reference() { + if (_ref != nullptr) { + if (!_suppressDestruct) { + napi_delete_reference(_env, _ref); + } + + _ref = nullptr; + } +} + +template +inline Reference::Reference(Reference&& other) + : _env(other._env), + _ref(other._ref), + _suppressDestruct(other._suppressDestruct) { + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; +} + +template +inline Reference& Reference::operator=(Reference&& other) { + Reset(); + _env = other._env; + _ref = other._ref; + _suppressDestruct = other._suppressDestruct; + other._env = nullptr; + other._ref = nullptr; + other._suppressDestruct = false; + return *this; +} + +template +inline Reference::Reference(const Reference& other) + : _env(other._env), _ref(nullptr), _suppressDestruct(false) { + HandleScope scope(_env); + + napi_value value = other.Value(); + if (value != nullptr) { + // Copying is a limited scenario (currently only used for Error object) and + // always creates a strong reference to the given value even if the incoming + // reference is weak. + napi_status status = napi_create_reference(_env, value, 1, &_ref); + NAPI_FATAL_IF_FAILED( + status, "Reference::Reference", "napi_create_reference"); + } +} + +template +inline Reference::operator napi_ref() const { + return _ref; +} + +template +inline bool Reference::operator==(const Reference& other) const { + HandleScope scope(_env); + return this->Value().StrictEquals(other.Value()); +} + +template +inline bool Reference::operator!=(const Reference& other) const { + return !this->operator==(other); +} + +template +inline Napi::Env Reference::Env() const { + return Napi::Env(_env); +} + +template +inline bool Reference::IsEmpty() const { + return _ref == nullptr; +} + +template +inline T Reference::Value() const { + if (_ref == nullptr) { + return T(_env, nullptr); + } + + napi_value value; + napi_status status = napi_get_reference_value(_env, _ref, &value); + NAPI_THROW_IF_FAILED(_env, status, T()); + return T(_env, value); +} + +template +inline uint32_t Reference::Ref() const { + uint32_t result; + napi_status status = napi_reference_ref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline uint32_t Reference::Unref() const { + uint32_t result; + napi_status status = napi_reference_unref(_env, _ref, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; +} + +template +inline void Reference::Reset() { + if (_ref != nullptr) { + napi_status status = napi_delete_reference(_env, _ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + _ref = nullptr; + } +} + +template +inline void Reference::Reset(const T& value, uint32_t refcount) { + Reset(); + _env = value.Env(); + + napi_value val = value; + if (val != nullptr) { + napi_status status = napi_create_reference(_env, value, refcount, &_ref); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Reference::SuppressDestruct() { + _suppressDestruct = true; +} + +template +inline Reference Weak(T value) { + return Reference::New(value, 0); +} + +inline ObjectReference Weak(Object value) { + return Reference::New(value, 0); +} + +inline FunctionReference Weak(Function value) { + return Reference::New(value, 0); +} + +template +inline Reference Persistent(T value) { + return Reference::New(value, 1); +} + +inline ObjectReference Persistent(Object value) { + return Reference::New(value, 1); +} + +inline FunctionReference Persistent(Function value) { + return Reference::New(value, 1); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectReference class +//////////////////////////////////////////////////////////////////////////////// + +inline ObjectReference::ObjectReference() : Reference() {} + +inline ObjectReference::ObjectReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline ObjectReference::ObjectReference(Reference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(ObjectReference&& other) + : Reference(std::move(other)) {} + +inline ObjectReference& ObjectReference::operator=(ObjectReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline ObjectReference::ObjectReference(const ObjectReference& other) + : Reference(other) {} + +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(utf8name, value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(utf8name, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(utf8name, numberValue); +} + +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) const { + HandleScope scope(_env); + return Value().Set(index, value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set( + uint32_t index, const std::string& utf8value) const { + HandleScope scope(_env); + return Value().Set(index, utf8value); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + bool boolValue) const { + HandleScope scope(_env); + return Value().Set(index, boolValue); +} + +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) const { + HandleScope scope(_env); + return Value().Set(index, numberValue); +} + +//////////////////////////////////////////////////////////////////////////////// +// FunctionReference class +//////////////////////////////////////////////////////////////////////////////// + +inline FunctionReference::FunctionReference() : Reference() {} + +inline FunctionReference::FunctionReference(napi_env env, napi_ref ref) + : Reference(env, ref) {} + +inline FunctionReference::FunctionReference(Reference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + Reference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline FunctionReference::FunctionReference(FunctionReference&& other) + : Reference(std::move(other)) {} + +inline FunctionReference& FunctionReference::operator=( + FunctionReference&& other) { + static_cast*>(this)->operator=(std::move(other)); + return *this; +} + +inline MaybeOrValue FunctionReference::operator()( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::Call( + napi_value recv, size_t argc, const napi_value* args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { + EscapableHandleScope scope(_env); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// CallbackInfo class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) + : _env(env), + _info(info), + _this(nullptr), + _dynamicArgs(nullptr), + _data(nullptr) { + _argc = _staticArgCount; + _argv = _staticArgs; + napi_status status = + napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + if (_argc > _staticArgCount) { + // Use either a fixed-size array (on the stack) or a dynamically-allocated + // array (on the heap) depending on the number of args. + _dynamicArgs = new napi_value[_argc]; + _argv = _dynamicArgs; + + status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +inline CallbackInfo::~CallbackInfo() { + if (_dynamicArgs != nullptr) { + delete[] _dynamicArgs; + } +} + +inline CallbackInfo::operator napi_callback_info() const { + return _info; +} + +inline Value CallbackInfo::NewTarget() const { + napi_value newTarget; + napi_status status = napi_get_new_target(_env, _info, &newTarget); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, newTarget); +} + +inline bool CallbackInfo::IsConstructCall() const { + return !NewTarget().IsEmpty(); +} + +inline Napi::Env CallbackInfo::Env() const { + return Napi::Env(_env); +} + +inline size_t CallbackInfo::Length() const { + return _argc; +} + +inline const Value CallbackInfo::operator[](size_t index) const { + return index < _argc ? Value(_env, _argv[index]) : Env().Undefined(); +} + +inline Value CallbackInfo::This() const { + if (_this == nullptr) { + return Env().Undefined(); + } + return Object(_env, _this); +} + +inline void* CallbackInfo::Data() const { + return _data; +} + +inline void CallbackInfo::SetData(void* data) { + _data = data; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyDescriptor class +//////////////////////////////////////////////////////////////////////////////// + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + const std::string& utf8name, + napi_property_attributes attributes, + void* data) { + return Accessor(utf8name.c_str(), attributes, data); +} + +template +PropertyDescriptor PropertyDescriptor::Accessor( + Name name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = details::TemplatedVoidCallback; + desc.attributes = attributes; + desc.data = data; + + return desc; +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes, + void* data) { + return Accessor(env, object, utf8name.c_str(), getter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes, + void* data) { + using CbData = details::CallbackData; + auto callbackData = new CbData({getter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::Wrapper, + nullptr, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + return Accessor( + env, object, utf8name.c_str(), getter, setter, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes, + void* data) { + using CbData = details::AccessorCallbackData; + auto callbackData = new CbData({getter, setter, data}); + + napi_status status = AttachData(env, object, callbackData); + if (status != napi_ok) { + delete callbackData; + NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor()); + } + + return PropertyDescriptor({nullptr, + name, + nullptr, + CbData::GetterWrapper, + CbData::SetterWrapper, + nullptr, + attributes, + callbackData}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + const char* utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, utf8name, data), + attributes, + nullptr}); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return Function(env, object, utf8name.c_str(), cb, attributes, data); +} + +template +inline PropertyDescriptor PropertyDescriptor::Function( + Napi::Env env, + Napi::Object /*object*/, + Name name, + Callable cb, + napi_property_attributes attributes, + void* data) { + return PropertyDescriptor({nullptr, + name, + nullptr, + nullptr, + nullptr, + Napi::Function::New(env, cb, nullptr, data), + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes) { + return PropertyDescriptor({utf8name, + nullptr, + nullptr, + nullptr, + nullptr, + value, + attributes, + nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes) { + return Value(utf8name.c_str(), value, attributes); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + napi_value name, napi_value value, napi_property_attributes attributes) { + return PropertyDescriptor( + {nullptr, name, nullptr, nullptr, nullptr, value, attributes, nullptr}); +} + +inline PropertyDescriptor PropertyDescriptor::Value( + Name name, Napi::Value value, napi_property_attributes attributes) { + napi_value nameValue = name; + napi_value valueValue = value; + return PropertyDescriptor::Value(nameValue, valueValue, attributes); +} + +inline PropertyDescriptor::PropertyDescriptor(napi_property_descriptor desc) + : _desc(desc) {} + +inline PropertyDescriptor::operator napi_property_descriptor&() { + return _desc; +} + +inline PropertyDescriptor::operator const napi_property_descriptor&() const { + return _desc; +} + +//////////////////////////////////////////////////////////////////////////////// +// InstanceWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline void InstanceWrap::AttachPropData( + napi_env env, napi_value value, const napi_property_descriptor* prop) { + napi_status status; + if (!(prop->attributes & napi_static)) { + if (prop->method == T::InstanceVoidMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->method == T::InstanceMethodCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } else if (prop->getter == T::InstanceGetterCallbackWrapper || + prop->setter == T::InstanceSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED_VOID(env, status); + } + } +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceVoidMethodCallbackData* callbackData = + new InstanceVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes, + void* data) { + InstanceMethodCallbackData* callbackData = + new InstanceMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::InstanceMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceVoidMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceVoidCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceMethodCallback method> +inline ClassPropertyDescriptor InstanceWrap::InstanceMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedInstanceCallback; + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +template ::InstanceGetterCallback getter, + typename InstanceWrap::InstanceSetterCallback setter> +inline ClassPropertyDescriptor InstanceWrap::InstanceAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedInstanceCallback; + desc.setter = This::WrapSetter(This::SetterTag()); + desc.data = data; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline ClassPropertyDescriptor InstanceWrap::InstanceValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + +template +inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + if (instance) (instance->*cb)(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->callback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->getterCallback; + return instance ? (instance->*cb)(callbackInfo) : Napi::Value(); + }); +} + +template +inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + InstanceAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + T* instance = T::Unwrap(callbackInfo.This().As()); + auto cb = callbackData->setterCallback; + if (instance) (instance->*cb)(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +template ::InstanceSetterCallback method> +inline napi_value InstanceWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + T* instance = T::Unwrap(cbInfo.This().As()); + if (instance) (instance->*method)(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// ObjectWrap class +//////////////////////////////////////////////////////////////////////////////// + +template +inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { + napi_env env = callbackInfo.Env(); + napi_value wrapper = callbackInfo.This(); + napi_status status; + napi_ref ref; + T* instance = static_cast(this); + status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); + NAPI_THROW_IF_FAILED_VOID(env, status); + + Reference* instanceRef = instance; + *instanceRef = Reference(env, ref); +} + +template +inline ObjectWrap::~ObjectWrap() { + // If the JS object still exists at this point, remove the finalizer added + // through `napi_wrap()`. + if (!IsEmpty()) { + Object object = Value(); + // It is not valid to call `napi_remove_wrap()` with an empty `object`. + // This happens e.g. during garbage collection. + if (!object.IsEmpty() && _construction_failed) { + napi_remove_wrap(Env(), object, nullptr); + } + } +} + +template +inline T* ObjectWrap::Unwrap(Object wrapper) { + void* unwrapped; + napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); + return static_cast(unwrapped); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* descriptors, + void* data) { + napi_status status; + std::vector props(props_count); + + // We copy the descriptors to a local array because before defining the class + // we must replace static method property descriptors with value property + // descriptors such that the value is a function-valued `napi_value` created + // with `CreateFunction()`. + // + // This replacement could be made for instance methods as well, but V8 aborts + // if we do that, because it expects methods defined on the prototype template + // to have `FunctionTemplate`s. + for (size_t index = 0; index < props_count; index++) { + props[index] = descriptors[index]; + napi_property_descriptor* prop = &props[index]; + if (prop->method == T::StaticMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } else if (prop->method == T::StaticVoidMethodCallbackWrapper) { + status = + CreateFunction(env, + utf8name, + prop->method, + static_cast(prop->data), + &(prop->value)); + NAPI_THROW_IF_FAILED(env, status, Function()); + prop->method = nullptr; + prop->data = nullptr; + } + } + + napi_value value; + status = napi_define_class(env, + utf8name, + NAPI_AUTO_LENGTH, + T::ConstructorCallbackWrapper, + data, + props_count, + props.data(), + &value); + NAPI_THROW_IF_FAILED(env, status, Function()); + + // After defining the class we iterate once more over the property descriptors + // and attach the data associated with accessors and instance methods to the + // newly created JavaScript class. + for (size_t idx = 0; idx < props_count; idx++) { + const napi_property_descriptor* prop = &props[idx]; + + if (prop->getter == T::StaticGetterCallbackWrapper || + prop->setter == T::StaticSetterCallbackWrapper) { + status = Napi::details::AttachData( + env, value, static_cast(prop->data)); + NAPI_THROW_IF_FAILED(env, status, Function()); + } else { + // InstanceWrap::AttachPropData is responsible for attaching the data + // of instance methods and accessors. + T::AttachPropData(env, value, prop); + } + } + + return Function(env, value); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.begin()), + data); +} + +template +inline Function ObjectWrap::DefineClass( + Napi::Env env, + const char* utf8name, + const std::vector>& properties, + void* data) { + return DefineClass( + env, + utf8name, + properties.size(), + reinterpret_cast(properties.data()), + data); +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticVoidMethodCallbackData* callbackData = + new StaticVoidMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + StaticMethodCallbackData* callbackData = + new StaticMethodCallbackData({method, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticVoidMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedVoidCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticMethodCallback method> +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = details::TemplatedCallback; + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({getter, setter, data}); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + const char* utf8name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +template ::StaticGetterCallback getter, + typename ObjectWrap::StaticSetterCallback setter> +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, napi_property_attributes attributes, void* data) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = details::TemplatedCallback; + desc.setter = This::WrapStaticSetter(This::StaticSetterTag()); + desc.data = data; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.utf8name = utf8name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue( + Symbol name, Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = + static_cast(attributes | napi_static); + return desc; +} + +template +inline Value ObjectWrap::OnCalledAsFunction( + const Napi::CallbackInfo& callbackInfo) { + NAPI_THROW( + TypeError::New(callbackInfo.Env(), + "Class constructors cannot be invoked without 'new'"), + Napi::Value()); +} + +template +inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} + +template +inline napi_value ObjectWrap::ConstructorCallbackWrapper( + napi_env env, napi_callback_info info) { + napi_value new_target; + napi_status status = napi_get_new_target(env, info, &new_target); + if (status != napi_ok) return nullptr; + + bool isConstructCall = (new_target != nullptr); + if (!isConstructCall) { + return details::WrapCallback( + [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); + } + + napi_value wrapper = details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + T* instance = new T(callbackInfo); +#ifdef NAPI_CPP_EXCEPTIONS + instance->_construction_failed = false; +#else + if (callbackInfo.Env().IsExceptionPending()) { + // We need to clear the exception so that removing the wrap might work. + Error e = callbackInfo.Env().GetAndClearPendingException(); + delete instance; + e.ThrowAsJavaScriptException(); + } else { + instance->_construction_failed = false; + } +#endif // NAPI_CPP_EXCEPTIONS + return callbackInfo.This(); + }); + + return wrapper; +} + +template +inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + StaticVoidMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->callback(callbackInfo); + return nullptr; + }); +} + +template +inline napi_value ObjectWrap::StaticMethodCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + StaticMethodCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->callback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticGetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + return callbackData->getterCallback(callbackInfo); + }); +} + +template +inline napi_value ObjectWrap::StaticSetterCallbackWrapper( + napi_env env, napi_callback_info info) { + return details::WrapCallback([&] { + CallbackInfo callbackInfo(env, info); + StaticAccessorCallbackData* callbackData = + reinterpret_cast(callbackInfo.Data()); + callbackInfo.SetData(callbackData->data); + callbackData->setterCallback(callbackInfo, callbackInfo[0]); + return nullptr; + }); +} + +template +inline void ObjectWrap::FinalizeCallback(napi_env env, + void* data, + void* /*hint*/) { + HandleScope scope(env); + T* instance = static_cast(data); + instance->Finalize(Napi::Env(env)); + delete instance; +} + +template +template ::StaticSetterCallback method> +inline napi_value ObjectWrap::WrappedMethod( + napi_env env, napi_callback_info info) NAPI_NOEXCEPT { + return details::WrapCallback([&] { + const CallbackInfo cbInfo(env, info); + method(cbInfo, cbInfo[0]); + return nullptr; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// HandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) + : _env(env), _scope(scope) {} + +inline HandleScope::HandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline HandleScope::~HandleScope() { + napi_status status = napi_close_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "HandleScope::~HandleScope", "napi_close_handle_scope"); +} + +inline HandleScope::operator napi_handle_scope() const { + return _scope; +} + +inline Napi::Env HandleScope::Env() const { + return Napi::Env(_env); +} + +//////////////////////////////////////////////////////////////////////////////// +// EscapableHandleScope class +//////////////////////////////////////////////////////////////////////////////// + +inline EscapableHandleScope::EscapableHandleScope( + napi_env env, napi_escapable_handle_scope scope) + : _env(env), _scope(scope) {} + +inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { + napi_status status = napi_open_escapable_handle_scope(_env, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline EscapableHandleScope::~EscapableHandleScope() { + napi_status status = napi_close_escapable_handle_scope(_env, _scope); + NAPI_FATAL_IF_FAILED(status, + "EscapableHandleScope::~EscapableHandleScope", + "napi_close_escapable_handle_scope"); +} + +inline EscapableHandleScope::operator napi_escapable_handle_scope() const { + return _scope; +} + +inline Napi::Env EscapableHandleScope::Env() const { + return Napi::Env(_env); +} + +inline Value EscapableHandleScope::Escape(napi_value escapee) { + napi_value result; + napi_status status = napi_escape_handle(_env, _scope, escapee, &result); + NAPI_THROW_IF_FAILED(_env, status, Value()); + return Value(_env, result); +} + +#if !defined(__OHOS__) +#if (NAPI_VERSION > 2) +//////////////////////////////////////////////////////////////////////////////// +// CallbackScope class +//////////////////////////////////////////////////////////////////////////////// + +inline CallbackScope::CallbackScope(napi_env env, napi_callback_scope scope) + : _env(env), _scope(scope) {} + +inline CallbackScope::CallbackScope(napi_env env, napi_async_context context) + : _env(env) { + napi_status status = + napi_open_callback_scope(_env, Object::New(env), context, &_scope); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline CallbackScope::~CallbackScope() { + napi_status status = napi_close_callback_scope(_env, _scope); + NAPI_FATAL_IF_FAILED( + status, "CallbackScope::~CallbackScope", "napi_close_callback_scope"); +} + +inline CallbackScope::operator napi_callback_scope() const { + return _scope; +} + +inline Napi::Env CallbackScope::Env() const { + return Napi::Env(_env); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// AsyncContext class +//////////////////////////////////////////////////////////////////////////////// + +inline AsyncContext::AsyncContext(napi_env env, const char* resource_name) + : AsyncContext(env, resource_name, Object::New(env)) {} + +inline AsyncContext::AsyncContext(napi_env env, + const char* resource_name, + const Object& resource) + : _env(env), _context(nullptr) { + napi_value resource_id; + napi_status status = napi_create_string_utf8( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_async_init(_env, resource, resource_id, &_context); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncContext::~AsyncContext() { + if (_context != nullptr) { + napi_async_destroy(_env, _context); + _context = nullptr; + } +} + +inline AsyncContext::AsyncContext(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; +} + +inline AsyncContext& AsyncContext::operator=(AsyncContext&& other) { + _env = other._env; + other._env = nullptr; + _context = other._context; + other._context = nullptr; + return *this; +} + +inline AsyncContext::operator napi_async_context() const { + return _context; +} + +inline Napi::Env AsyncContext::Env() const { + return Napi::Env(_env); +} +#endif // !defined(__OHOS__) + +//////////////////////////////////////////////////////////////////////////////// +// AsyncWorker class +//////////////////////////////////////////////////////////////////////////////// + +#if NAPI_HAS_THREADS + +inline AsyncWorker::AsyncWorker(const Function& callback) + : AsyncWorker(callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name) + : AsyncWorker(callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback) + : AsyncWorker(receiver, callback, "generic") {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : _env(callback.Env()), + _receiver(Napi::Persistent(receiver)), + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::AsyncWorker(Napi::Env env) : AsyncWorker(env, "generic") {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) {} + +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : _env(env), _receiver(), _callback(), _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); + + status = napi_create_async_work(_env, + resource, + resource_id, + OnAsyncWorkExecute, + OnAsyncWorkComplete, + this, + &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline AsyncWorker::~AsyncWorker() { + if (_work != nullptr) { + napi_delete_async_work(_env, _work); + _work = nullptr; + } +} + +inline void AsyncWorker::Destroy() { + delete this; +} + +inline AsyncWorker::operator napi_async_work() const { + return _work; +} + +inline Napi::Env AsyncWorker::Env() const { + return Napi::Env(_env); +} + +inline void AsyncWorker::Queue() { + napi_status status = napi_queue_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline void AsyncWorker::Cancel() { + napi_status status = napi_cancel_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} + +inline ObjectReference& AsyncWorker::Receiver() { + return _receiver; +} + +inline FunctionReference& AsyncWorker::Callback() { + return _callback; +} + +inline void AsyncWorker::SuppressDestruct() { + _suppress_destruct = true; +} + +inline void AsyncWorker::OnOK() { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), GetResult(_callback.Env())); + } +} + +inline void AsyncWorker::OnError(const Error& e) { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), + std::initializer_list{e.Value()}); + } +} + +inline void AsyncWorker::SetError(const std::string& error) { + _error = error; +} + +inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { + return {}; +} +// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnExecute(env); +} +// The OnExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + Execute(); + } catch (const std::exception& e) { + SetError(e.what()); + } +#else // NAPI_CPP_EXCEPTIONS + Execute(); +#endif // NAPI_CPP_EXCEPTIONS +} + +inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnWorkComplete(env, status); +} +inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { + if (status != napi_cancelled) { + HandleScope scope(_env); + details::WrapCallback([&] { + if (_error.size() == 0) { + OnOK(); + } else { + OnError(Error::New(_env, _error)); + } + return nullptr; + }); + } + if (!_suppress_destruct) { + Destroy(); + } +} + +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +//////////////////////////////////////////////////////////////////////////////// +// TypedThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// Starting with NAPI 5, the JavaScript function `func` parameter of +// `napi_create_threadsafe_function` is optional. +#if NAPI_VERSION > 4 +// static, with Callback [missing] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [missing] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} +#endif + +// static, with Callback [passed] Resource [missing] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [passed] Finalizer [missing] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + TypedThreadSafeFunction tsfn; + + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + nullptr, + nullptr, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with Callback [passed] Resource [missing] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +// static, with: Callback [passed] Resource [passed] Finalizer [passed] +template +template +inline TypedThreadSafeFunction +TypedThreadSafeFunction::New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + TypedThreadSafeFunction tsfn; + + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = napi_create_threadsafe_function( + env, + details::DefaultCallbackWrapper< + CallbackType, + TypedThreadSafeFunction>(env, + callback), + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext, + context, + CallJsInternal, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED( + env, status, TypedThreadSafeFunction()); + } + + return tsfn; +} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction() + : _tsfn() {} + +template +inline TypedThreadSafeFunction:: + TypedThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +template +inline TypedThreadSafeFunction:: +operator napi_threadsafe_function() const { + return _tsfn; +} + +template +inline napi_status +TypedThreadSafeFunction::BlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status +TypedThreadSafeFunction::NonBlockingCall( + DataType* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline void TypedThreadSafeFunction::Ref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline void TypedThreadSafeFunction::Unref( + napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +template +inline napi_status +TypedThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +template +inline napi_status +TypedThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +template +inline napi_status +TypedThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +template +inline ContextType* +TypedThreadSafeFunction::GetContext() const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "TypedThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return static_cast(context); +} + +// static +template +void TypedThreadSafeFunction::CallJsInternal( + napi_env env, napi_value jsCallback, void* context, void* data) { + details::CallJsWrapper( + env, jsCallback, context, data); +} + +#if NAPI_VERSION == 4 +// static +template +Napi::Function +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env env) { + return Napi::Function::New(env, [](const CallbackInfo& cb) {}); +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env env, Napi::Function& callback) { + if (callback.IsEmpty()) { + return EmptyFunctionFactory(env); + } + return callback; +} + +#else +// static +template +std::nullptr_t +TypedThreadSafeFunction::EmptyFunctionFactory( + Napi::Env /*env*/) { + return nullptr; +} + +// static +template +Napi::Function +TypedThreadSafeFunction::FunctionOrEmpty( + Napi::Env /*env*/, Napi::Function& callback) { + return callback; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ThreadSafeFunction class +//////////////////////////////////////////////////////////////////////////////// + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New( + env, callback, Object(), resourceName, maxQueueSize, initialThreadCount); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + Object(), + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + [](Env, ContextType*) {} /* empty finalizer */); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::Wrapper); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New(env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + static_cast(nullptr) /* context */, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeWrapperWithData); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + static_cast(nullptr) /* data */, + details::ThreadSafeFinalize::FinalizeWrapperWithContext); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data) { + return New( + env, + callback, + resource, + resourceName, + maxQueueSize, + initialThreadCount, + context, + finalizeCallback, + data, + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext); +} + +inline ThreadSafeFunction::ThreadSafeFunction() : _tsfn() {} + +inline ThreadSafeFunction::ThreadSafeFunction(napi_threadsafe_function tsfn) + : _tsfn(tsfn) {} + +inline ThreadSafeFunction::operator napi_threadsafe_function() const { + return _tsfn; +} + +inline napi_status ThreadSafeFunction::BlockingCall() const { + return CallInternal(nullptr, napi_tsfn_blocking); +} + +template <> +inline napi_status ThreadSafeFunction::BlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_blocking); +} + +template +inline napi_status ThreadSafeFunction::BlockingCall(DataType* data, + Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_blocking); +} + +inline napi_status ThreadSafeFunction::NonBlockingCall() const { + return CallInternal(nullptr, napi_tsfn_nonblocking); +} + +template <> +inline napi_status ThreadSafeFunction::NonBlockingCall(void* data) const { + return napi_call_threadsafe_function(_tsfn, data, napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + Callback callback) const { + return CallInternal(new CallbackWrapper(callback), napi_tsfn_nonblocking); +} + +template +inline napi_status ThreadSafeFunction::NonBlockingCall( + DataType* data, Callback callback) const { + auto wrapper = [data, callback](Env env, Function jsCallback) { + callback(env, jsCallback, data); + }; + return CallInternal(new CallbackWrapper(wrapper), napi_tsfn_nonblocking); +} + +inline void ThreadSafeFunction::Ref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_ref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline void ThreadSafeFunction::Unref(napi_env env) const { + if (_tsfn != nullptr) { + napi_status status = napi_unref_threadsafe_function(env, _tsfn); + NAPI_THROW_IF_FAILED_VOID(env, status); + } +} + +inline napi_status ThreadSafeFunction::Acquire() const { + return napi_acquire_threadsafe_function(_tsfn); +} + +inline napi_status ThreadSafeFunction::Release() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_release); +} + +inline napi_status ThreadSafeFunction::Abort() const { + return napi_release_threadsafe_function(_tsfn, napi_tsfn_abort); +} + +inline ThreadSafeFunction::ConvertibleContext ThreadSafeFunction::GetContext() + const { + void* context; + napi_status status = napi_get_threadsafe_function_context(_tsfn, &context); + NAPI_FATAL_IF_FAILED(status, + "ThreadSafeFunction::GetContext", + "napi_get_threadsafe_function_context"); + return ConvertibleContext({context}); +} + +// static +template +inline ThreadSafeFunction ThreadSafeFunction::New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper) { + static_assert(details::can_make_string::value || + std::is_convertible::value, + "Resource name should be convertible to the string type"); + + ThreadSafeFunction tsfn; + auto* finalizeData = new details:: + ThreadSafeFinalize( + {data, finalizeCallback}); + napi_status status = + napi_create_threadsafe_function(env, + callback, + resource, + Value::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + wrapper, + context, + CallJS, + &tsfn._tsfn); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED(env, status, ThreadSafeFunction()); + } + + return tsfn; +} + +inline napi_status ThreadSafeFunction::CallInternal( + CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const { + napi_status status = + napi_call_threadsafe_function(_tsfn, callbackWrapper, mode); + if (status != napi_ok && callbackWrapper != nullptr) { + delete callbackWrapper; + } + + return status; +} + +// static +inline void ThreadSafeFunction::CallJS(napi_env env, + napi_value jsCallback, + void* /* context */, + void* data) { + if (env == nullptr && jsCallback == nullptr) { + return; + } + + details::WrapVoidCallback([&]() { + if (data != nullptr) { + auto* callbackWrapper = static_cast(data); + (*callbackWrapper)(env, Function(env, jsCallback)); + delete callbackWrapper; + } else if (jsCallback != nullptr) { + Function(env, jsCallback).Call({}); + } + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker Base class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(callback.Env(), + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase( + Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are available on all versions we can remove the dummy Function + // here. + Function callback; + // Fill all possible arguments to work around ambiguous + // ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(env, + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} +#endif + +template +inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release + // duplicated. + _tsfn.Abort(); +} + +template +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress( + Napi::Env /* env */, Napi::Function /* jsCallback */, void* data) { + ThreadSafeData* tsd = static_cast(data); + tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); + delete tsd; +} + +template +inline napi_status AsyncProgressWorkerBase::NonBlockingCall( + DataType* data) { + auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); + auto ret = _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); + if (ret != napi_ok) { + delete tsd; + } + return ret; +} + +template +inline void AsyncProgressWorkerBase::OnWorkComplete( + Napi::Env /* env */, napi_status status) { + _work_completed = true; + _complete_status = status; + _tsfn.Release(); +} + +template +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize( + Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { + if (context->_work_completed) { + context->AsyncWorker::OnWorkComplete(env, context->_complete_status); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) + : AsyncProgressWorker(callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback) + : AsyncProgressWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0), + _signaled(false) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) + : AsyncProgressWorker(env, "generic") {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase(env, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) {} +#endif + +template +inline AsyncProgressWorker::~AsyncProgressWorker() { + { + std::lock_guard lock(this->_mutex); + _asyncdata = nullptr; + _asyncsize = 0; + } +} + +template +inline void AsyncProgressWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressWorker::OnWorkProgress(void*) { + T* data; + size_t size; + bool signaled; + { + std::lock_guard lock(this->_mutex); + data = this->_asyncdata; + size = this->_asyncsize; + signaled = this->_signaled; + this->_asyncdata = nullptr; + this->_asyncsize = 0; + this->_signaled = false; + } + + /** + * The callback of ThreadSafeFunction is not been invoked immediately on the + * callback of uv_async_t (uv io poll), rather the callback of TSFN is + * invoked on the right next uv idle callback. There are chances that during + * the deferring the signal of uv_async_t is been sent again, i.e. potential + * not coalesced two calls of the TSFN callback. + */ + if (data == nullptr && !signaled) { + return; + } + + this->OnProgress(data, size); + delete[] data; +} + +template +inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(this->_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + _signaled = false; + } + this->NonBlockingCall(nullptr); + + delete[] old_data; +} + +template +inline void AsyncProgressWorker::Signal() { + { + std::lock_guard lock(this->_mutex); + _signaled = true; + } + this->NonBlockingCall(static_cast(nullptr)); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Signal() const { + this->_worker->Signal(); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Queue Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Function& callback, const char* resource_name, const Object& resource) + : AsyncProgressQueueWorker( + Object::New(callback.Env()), callback, resource_name, resource) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, const Function& callback, const char* resource_name) + : AsyncProgressQueueWorker( + receiver, callback, resource_name, Object::New(callback.Env())) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>( + receiver, + callback, + resource_name, + resource, + /** unlimited queue size */ 0) {} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) + : AsyncProgressQueueWorker(env, "generic") {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) {} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker( + Napi::Env env, const char* resource_name, const Object& resource) + : AsyncProgressWorkerBase>( + env, resource_name, resource, /** unlimited queue size */ 0) {} +#endif + +template +inline void AsyncProgressQueueWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressQueueWorker::OnWorkProgress( + std::pair* datapair) { + if (datapair == nullptr) { + return; + } + + T* data = datapair->first; + size_t size = datapair->second; + + this->OnProgress(data, size); + delete datapair; + delete[] data; +} + +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, + size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); +} + +template +inline void AsyncProgressQueueWorker::Signal() const { + this->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, + napi_status status) { + // Draining queued items in TSFN. + AsyncProgressWorkerBase>::OnWorkComplete(env, status); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { + _worker->SendProgress_(static_cast(nullptr), 0); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send( + const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +#if (!defined(__OHOS__)) +//////////////////////////////////////////////////////////////////////////////// +// Memory Management class +//////////////////////////////////////////////////////////////////////////////// + +inline int64_t MemoryManagement::AdjustExternalMemory(Env env, + int64_t change_in_bytes) { + int64_t result; + napi_status status = + napi_adjust_external_memory(env, change_in_bytes, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// Version Management class +//////////////////////////////////////////////////////////////////////////////// + +inline uint32_t VersionManagement::GetNapiVersion(Env env) { + uint32_t result; + napi_status status = napi_get_version(env, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + +inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { + const napi_node_version* result; + napi_status status = napi_get_node_version(env, &result); + NAPI_THROW_IF_FAILED(env, status, 0); + return result; +} + +#if NAPI_VERSION > 5 +//////////////////////////////////////////////////////////////////////////////// +// Addon class +//////////////////////////////////////////////////////////////////////////////// + +template +inline Object Addon::Init(Env env, Object exports) { + T* addon = new T(env, exports); + env.SetInstanceData(addon); + return addon->entry_point_; +} + +template +inline T* Addon::Unwrap(Object wrapper) { + return wrapper.Env().GetInstanceData(); +} + +template +inline void Addon::DefineAddon( + Object exports, const std::initializer_list& props) { + DefineProperties(exports, props); + entry_point_ = exports; +} + +template +inline Napi::Object Addon::DefineProperties( + Object object, const std::initializer_list& props) { + const napi_property_descriptor* properties = + reinterpret_cast(props.begin()); + size_t size = props.size(); + napi_status status = + napi_define_properties(object.Env(), object, size, properties); + NAPI_THROW_IF_FAILED(object.Env(), status, object); + for (size_t idx = 0; idx < size; idx++) + T::AttachPropData(object.Env(), object, &properties[idx]); + return object; +} +#endif // NAPI_VERSION > 5 +#endif // !defined(__OHOS__) + +#if NAPI_VERSION > 2 +template +Env::CleanupHook Env::AddCleanupHook(Hook hook, Arg* arg) { + return CleanupHook(*this, hook, arg); +} + +template +Env::CleanupHook Env::AddCleanupHook(Hook hook) { + return CleanupHook(*this, hook); +} + +template +Env::CleanupHook::CleanupHook() { + data = nullptr; +} + +template +Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook) + : wrapper(Env::CleanupHook::Wrapper) { + data = new CleanupData{std::move(hook), nullptr}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook, Arg* arg) + : wrapper(Env::CleanupHook::WrapperWithArg) { + data = new CleanupData{std::move(hook), arg}; + napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); + if (status != napi_ok) { + delete data; + data = nullptr; + } +} + +template +bool Env::CleanupHook::Remove(Env env) { + napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); + delete data; + data = nullptr; + return status == napi_ok; +} + +template +bool Env::CleanupHook::IsEmpty() const { + return data == nullptr; +} +#endif // NAPI_VERSION > 2 + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +#endif // SRC_NAPI_INL_H_ diff --git a/sdk/ohos/src/node-addon-api/napi.h b/sdk/ohos/src/node-addon-api/napi.h new file mode 100644 index 0000000000000000000000000000000000000000..ded9be1a9b11c210a3c09505c852ceb92992c38b --- /dev/null +++ b/sdk/ohos/src/node-addon-api/napi.h @@ -0,0 +1,3207 @@ +#ifndef SRC_NAPI_H_ +#define SRC_NAPI_H_ + +#ifndef NAPI_HAS_THREADS +#if !defined(__wasm__) || (defined(__EMSCRIPTEN_PTHREADS__) || \ + (defined(__wasi__) && defined(_REENTRANT))) +#define NAPI_HAS_THREADS 1 +#else +#define NAPI_HAS_THREADS 0 +#endif +#endif + +#include +#include +#include +#include +#if NAPI_HAS_THREADS +#include +#endif // NAPI_HAS_THREADS +#include +#include + +// VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known +// good version) +#if !defined(_MSC_VER) || _MSC_FULL_VER >= 190024210 +#define NAPI_HAS_CONSTEXPR 1 +#endif + +// VS2013 does not support char16_t literal strings, so we'll work around it +// using wchar_t strings and casting them. This is safe as long as the character +// sizes are the same. +#if defined(_MSC_VER) && _MSC_VER <= 1800 +static_assert(sizeof(char16_t) == sizeof(wchar_t), + "Size mismatch between char16_t and wchar_t"); +#define NAPI_WIDE_TEXT(x) reinterpret_cast(L##x) +#else +#define NAPI_WIDE_TEXT(x) u##x +#endif + +// If C++ exceptions are not explicitly enabled or disabled, enable them +// if exceptions were enabled in the compiler settings. +#if !defined(NAPI_CPP_EXCEPTIONS) && !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#else +#error Exception support not detected. \ + Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS. +#endif +#endif + +// If C++ NAPI_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE should +// not be set +#if defined(NAPI_CPP_EXCEPTIONS) && defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NAPI_CPP_EXCEPTIONS is defined. +#endif + +#ifdef _NOEXCEPT +#define NAPI_NOEXCEPT _NOEXCEPT +#else +#define NAPI_NOEXCEPT noexcept +#endif + +#ifdef NAPI_CPP_EXCEPTIONS + +// When C++ exceptions are enabled, Errors are thrown directly. There is no need +// to return anything after the throw statements. The variadic parameter is an +// optional return value that is ignored. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) throw e +#define NAPI_THROW_VOID(e) throw e + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) throw Napi::Error::New(env); + +#else // NAPI_CPP_EXCEPTIONS + +// When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, +// which are pending until the callback returns to JS. The variadic parameter +// is an optional return value; usually it is an empty result. +// We need _VOID versions of the macros to avoid warnings resulting from +// leaving the NAPI_THROW_* `...` argument empty. + +#define NAPI_THROW(e, ...) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } while (0) + +#define NAPI_THROW_VOID(e) \ + do { \ + (e).ThrowAsJavaScriptException(); \ + return; \ + } while (0) + +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ + } + +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ + } + +#endif // NAPI_CPP_EXCEPTIONS + +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif + +#define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; +#define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; + +#define NAPI_DISALLOW_ASSIGN_COPY(CLASS) \ + NAPI_DISALLOW_ASSIGN(CLASS) \ + NAPI_DISALLOW_COPY(CLASS) + +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ + } while (0) + +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + +//////////////////////////////////////////////////////////////////////////////// +/// Node-API C++ Wrapper Classes +/// +/// These classes wrap the "Node-API" ABI-stable C APIs for Node.js, providing a +/// C++ object model and C++ exception-handling semantics with low overhead. +/// The wrappers are all header-only so that they do not affect the ABI. +//////////////////////////////////////////////////////////////////////////////// +namespace Napi { + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +// NAPI_CPP_CUSTOM_NAMESPACE can be #define'd per-addon to avoid symbol +// conflicts between different instances of node-addon-api + +// First dummy definition of the namespace to make sure that Napi::(name) still +// refers to the right things inside this file. +namespace NAPI_CPP_CUSTOM_NAMESPACE {} +using namespace NAPI_CPP_CUSTOM_NAMESPACE; + +namespace NAPI_CPP_CUSTOM_NAMESPACE { +#endif + +// Forward declarations +class Env; +class Value; +class Boolean; +class Number; +#if NAPI_VERSION > 5 +class BigInt; +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) +class Date; +#endif +class String; +class Object; +class Array; +class ArrayBuffer; +class Function; +class Error; +class PropertyDescriptor; +class CallbackInfo; +class TypedArray; +template +class TypedArrayOf; + +using Int8Array = + TypedArrayOf; ///< Typed-array of signed 8-bit integers +using Uint8Array = + TypedArrayOf; ///< Typed-array of unsigned 8-bit integers +using Int16Array = + TypedArrayOf; ///< Typed-array of signed 16-bit integers +using Uint16Array = + TypedArrayOf; ///< Typed-array of unsigned 16-bit integers +using Int32Array = + TypedArrayOf; ///< Typed-array of signed 32-bit integers +using Uint32Array = + TypedArrayOf; ///< Typed-array of unsigned 32-bit integers +using Float32Array = + TypedArrayOf; ///< Typed-array of 32-bit floating-point values +using Float64Array = + TypedArrayOf; ///< Typed-array of 64-bit floating-point values +#if NAPI_VERSION > 5 +using BigInt64Array = + TypedArrayOf; ///< Typed array of signed 64-bit integers +using BigUint64Array = + TypedArrayOf; ///< Typed array of unsigned 64-bit integers +#endif // NAPI_VERSION > 5 + +/// Defines the signature of a Node-API C++ module's registration callback +/// (init) function. +using ModuleRegisterCallback = Object (*)(Env env, Object exports); + +class MemoryManagement; + +/// A simple Maybe type, representing an object which may or may not have a +/// value. +/// +/// If an API method returns a Maybe<>, the API method can potentially fail +/// either because an exception is thrown, or because an exception is pending, +/// e.g. because a previous API call threw an exception that hasn't been +/// caught yet. In that case, a "Nothing" value is returned. +template +class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); +}; + +template +inline Maybe Nothing(); + +template +inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +template +using MaybeOrValue = Maybe; +#else +template +using MaybeOrValue = T; +#endif + +/// Environment for Node-API values and operations. +/// +/// All Node-API values and operations must be associated with an environment. +/// An environment instance is always provided to callback functions; that +/// environment must then be used for any creation of Node-API values or other +/// Node-API operations within the callback. (Many methods infer the +/// environment from the `this` instance that the method is called on.) +/// +/// In the future, multiple environments per process may be supported, +/// although current implementations only support one environment per process. +/// +/// In the V8 JavaScript engine, a Node-API environment approximately +/// corresponds to an Isolate. +class Env { + private: + napi_env _env; +#if NAPI_VERSION > 5 + template + static void DefaultFini(Env, T* data); + template + static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); +#endif // NAPI_VERSION > 5 + public: + Env(napi_env env); + + operator napi_env() const; + + Object Global() const; + Value Undefined() const; + Value Null() const; + + bool IsExceptionPending() const; + Error GetAndClearPendingException() const; + + MaybeOrValue RunScript(const char* utf8script) const; + MaybeOrValue RunScript(const std::string& utf8script) const; + MaybeOrValue RunScript(String script) const; + +#if NAPI_VERSION > 2 + template + class CleanupHook; + + template + CleanupHook AddCleanupHook(Hook hook); + + template + CleanupHook AddCleanupHook(Hook hook, Arg* arg); +#endif // NAPI_VERSION > 2 + +#if !defined (__OHOS__) +#if NAPI_VERSION > 5 + template + T* GetInstanceData() const; + + template + using Finalizer = void (*)(Env, T*); + template fini = Env::DefaultFini> + void SetInstanceData(T* data) const; + + template + using FinalizerWithHint = void (*)(Env, DataType*, HintType*); + template fini = + Env::DefaultFiniWithHint> + void SetInstanceData(DataType* data, HintType* hint) const; +#endif // NAPI_VERSION > 5 +#endif // !defined (__OHOS__) + +#if NAPI_VERSION > 2 + template + class CleanupHook { + public: + CleanupHook(); + CleanupHook(Env env, Hook hook, Arg* arg); + CleanupHook(Env env, Hook hook); + bool Remove(Env env); + bool IsEmpty() const; + + private: + static inline void Wrapper(void* data) NAPI_NOEXCEPT; + static inline void WrapperWithArg(void* data) NAPI_NOEXCEPT; + + void (*wrapper)(void* arg); + struct CleanupData { + Hook hook; + Arg* arg; + } * data; + }; +#endif // NAPI_VERSION > 2 + +#if NAPI_VERSION > 8 + const char* GetModuleFileName() const; +#endif // NAPI_VERSION > 8 +}; + +/// A JavaScript value of unknown type. +/// +/// For type-specific operations, convert to one of the Value subclasses using a +/// `To*` or `As()` method. The `To*` methods do type coercion; the `As()` +/// method does not. +/// +/// Napi::Value value = ... +/// if (!value.IsString()) throw Napi::TypeError::New(env, "Invalid +/// arg..."); Napi::String str = value.As(); // Cast to a +/// string value +/// +/// Napi::Value anotherValue = ... +/// bool isTruthy = anotherValue.ToBoolean(); // Coerce to a boolean value +class Value { + public: + Value(); ///< Creates a new _empty_ Value instance. + Value(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Creates a JS value from a C++ primitive. + /// + /// `value` may be any of: + /// - bool + /// - Any integer type + /// - Any floating point type + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + /// - napi::Value + /// - napi_value + template + static Value From(napi_env env, const T& value); + + /// Converts to a Node-API value primitive. + /// + /// If the instance is _empty_, this returns `nullptr`. + operator napi_value() const; + + /// Tests if this value strictly equals another value. + bool operator==(const Value& other) const; + + /// Tests if this value does not strictly equal another value. + bool operator!=(const Value& other) const; + + /// Tests if this value strictly equals another value. + bool StrictEquals(const Value& other) const; + + /// Gets the environment the value is associated with. + Napi::Env Env() const; + + /// Checks if the value is empty (uninitialized). + /// + /// An empty value is invalid, and most attempts to perform an operation on an + /// empty value will result in an exception. Note an empty value is distinct + /// from JavaScript `null` or `undefined`, which are valid values. + /// + /// When C++ exceptions are disabled at compile time, a method with a `Value` + /// return type may return an empty value to indicate a pending exception. So + /// when not using C++ exceptions, callers should check whether the value is + /// empty before attempting to use it. + bool IsEmpty() const; + + napi_valuetype Type() const; ///< Gets the type of the value. + + bool IsUndefined() + const; ///< Tests if a value is an undefined JavaScript value. + bool IsNull() const; ///< Tests if a value is a null JavaScript value. + bool IsBoolean() const; ///< Tests if a value is a JavaScript boolean. + bool IsNumber() const; ///< Tests if a value is a JavaScript number. +#if NAPI_VERSION > 5 + bool IsBigInt() const; ///< Tests if a value is a JavaScript bigint. +#endif // NAPI_VERSION > 5 +#if (NAPI_VERSION > 4) + bool IsDate() const; ///< Tests if a value is a JavaScript date. +#endif + bool IsString() const; ///< Tests if a value is a JavaScript string. + bool IsSymbol() const; ///< Tests if a value is a JavaScript symbol. + bool IsArray() const; ///< Tests if a value is a JavaScript array. + bool IsArrayBuffer() + const; ///< Tests if a value is a JavaScript array buffer. + bool IsTypedArray() const; ///< Tests if a value is a JavaScript typed array. + bool IsObject() const; ///< Tests if a value is a JavaScript object. + bool IsFunction() const; ///< Tests if a value is a JavaScript function. + bool IsPromise() const; ///< Tests if a value is a JavaScript promise. + bool IsDataView() const; ///< Tests if a value is a JavaScript data view. + bool IsBuffer() const; ///< Tests if a value is a Node buffer. + bool IsExternal() const; ///< Tests if a value is a pointer to external data. + + /// Casts to another type of `Napi::Value`, when the actual type is known or + /// assumed. + /// + /// This conversion does NOT coerce the type. Calling any methods + /// inappropriate for the actual value type will throw `Napi::Error`. + /// + /// If `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined, this method + /// asserts that the actual type is the expected type. + template + T As() const; + + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. + + protected: + /// !cond INTERNAL + napi_env _env; + napi_value _value; + /// !endcond +}; + +/// A JavaScript boolean value. +class Boolean : public Value { + public: + static Boolean New(napi_env env, ///< Node-API environment + bool value ///< Boolean value + ); + + static void CheckCast(napi_env env, napi_value value); + + Boolean(); ///< Creates a new _empty_ Boolean instance. + Boolean(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator bool() const; ///< Converts a Boolean value to a boolean primitive. + bool Value() const; ///< Converts a Boolean value to a boolean primitive. +}; + +/// A JavaScript number value. +class Number : public Value { + public: + static Number New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Number(); ///< Creates a new _empty_ Number instance. + Number(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator int32_t() + const; ///< Converts a Number value to a 32-bit signed integer value. + operator uint32_t() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + operator int64_t() + const; ///< Converts a Number value to a 64-bit signed integer value. + operator float() + const; ///< Converts a Number value to a 32-bit floating-point value. + operator double() + const; ///< Converts a Number value to a 64-bit floating-point value. + + int32_t Int32Value() + const; ///< Converts a Number value to a 32-bit signed integer value. + uint32_t Uint32Value() + const; ///< Converts a Number value to a 32-bit unsigned integer value. + int64_t Int64Value() + const; ///< Converts a Number value to a 64-bit signed integer value. + float FloatValue() + const; ///< Converts a Number value to a 32-bit floating-point value. + double DoubleValue() + const; ///< Converts a Number value to a 64-bit floating-point value. +}; + +#if NAPI_VERSION > 5 +/// A JavaScript bigint value. +class BigInt : public Value { + public: + static BigInt New(napi_env env, ///< Node-API environment + int64_t value ///< Number value + ); + static BigInt New(napi_env env, ///< Node-API environment + uint64_t value ///< Number value + ); + + /// Creates a new BigInt object using a specified sign bit and a + /// specified list of digits/words. + /// The resulting number is calculated as: + /// (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + static BigInt New(napi_env env, ///< Node-API environment + int sign_bit, ///< Sign bit. 1 if negative. + size_t word_count, ///< Number of words in array + const uint64_t* words ///< Array of words + ); + + static void CheckCast(napi_env env, napi_value value); + + BigInt(); ///< Creates a new _empty_ BigInt instance. + BigInt(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + int64_t Int64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit signed integer value. + uint64_t Uint64Value(bool* lossless) + const; ///< Converts a BigInt value to a 64-bit unsigned integer value. + + size_t WordCount() const; ///< The number of 64-bit words needed to store + ///< the result of ToWords(). + + /// Writes the contents of this BigInt to a specified memory location. + /// `sign_bit` must be provided and will be set to 1 if this BigInt is + /// negative. + /// `*word_count` has to be initialized to the length of the `words` array. + /// Upon return, it will be set to the actual number of words that would + /// be needed to store this BigInt (i.e. the return value of `WordCount()`). + void ToWords(int* sign_bit, size_t* word_count, uint64_t* words); +}; +#endif // NAPI_VERSION > 5 + +#if (NAPI_VERSION > 4) +/// A JavaScript date value. +class Date : public Value { + public: + /// Creates a new Date value from a double primitive. + static Date New(napi_env env, ///< Node-API environment + double value ///< Number value + ); + + static void CheckCast(napi_env env, napi_value value); + + Date(); ///< Creates a new _empty_ Date instance. + Date(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + operator double() const; ///< Converts a Date value to double primitive + + double ValueOf() const; ///< Converts a Date value to a double primitive. +}; +#endif + +/// A JavaScript string or symbol value (that can be used as a property name). +class Name : public Value { + public: + static void CheckCast(napi_env env, napi_value value); + + Name(); ///< Creates a new _empty_ Name instance. + Name(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +/// A JavaScript string value. +class String : public Name { + public: + /// Creates a new String value from a UTF-8 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::string& value ///< UTF-8 encoded C++ string + ); + + /// Creates a new String value from a UTF-16 encoded C++ string. + static String New(napi_env env, ///< Node-API environment + const std::u16string& value ///< UTF-16 encoded C++ string + ); + + /// Creates a new String value from a UTF-8 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char* value ///< UTF-8 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-16 encoded C string. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value ///< UTF-16 encoded null-terminated C string + ); + + /// Creates a new String value from a UTF-8 encoded C string with specified + /// length. + static String New(napi_env env, ///< Node-API environment + const char* value, ///< UTF-8 encoded C string (not + ///< necessarily null-terminated) + size_t length ///< length of the string in bytes + ); + + /// Creates a new String value from a UTF-16 encoded C string with specified + /// length. + static String New( + napi_env env, ///< Node-API environment + const char16_t* value, ///< UTF-16 encoded C string (not necessarily + ///< null-terminated) + size_t length ///< Length of the string in 2-byte code units + ); + + /// Creates a new String based on the original object's type. + /// + /// `value` may be any of: + /// - const char* (encoded using UTF-8, null-terminated) + /// - const char16_t* (encoded using UTF-16-LE, null-terminated) + /// - std::string (encoded using UTF-8) + /// - std::u16string + template + static String From(napi_env env, const T& value); + + static void CheckCast(napi_env env, napi_value value); + + String(); ///< Creates a new _empty_ String instance. + String(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + operator std::string() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + operator std::u16string() + const; ///< Converts a String value to a UTF-16 encoded C++ string. + std::string Utf8Value() + const; ///< Converts a String value to a UTF-8 encoded C++ string. + std::u16string Utf16Value() + const; ///< Converts a String value to a UTF-16 encoded C++ string. +}; + +/// A JavaScript symbol value. +class Symbol : public Name { + public: + /// Creates a new Symbol value with an optional description. + static Symbol New( + napi_env env, ///< Node-API environment + const char* description = + nullptr ///< Optional UTF-8 encoded null-terminated C string + /// describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + const std::string& + description ///< UTF-8 encoded C++ string describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New(napi_env env, ///< Node-API environment + String description ///< String value describing the symbol + ); + + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + napi_value description ///< String value describing the symbol + ); + + /// Get a public Symbol (e.g. Symbol.iterator). + static MaybeOrValue WellKnown(napi_env, const std::string& name); + + // Create a symbol in the global registry, UTF-8 Encoded cpp string + static MaybeOrValue For(napi_env env, const std::string& description); + + // Create a symbol in the global registry, C style string (null terminated) + static MaybeOrValue For(napi_env env, const char* description); + + // Create a symbol in the global registry, String value describing the symbol + static MaybeOrValue For(napi_env env, String description); + + // Create a symbol in the global registry, napi_value describing the symbol + static MaybeOrValue For(napi_env env, napi_value description); + + static void CheckCast(napi_env env, napi_value value); + + Symbol(); ///< Creates a new _empty_ Symbol instance. + Symbol(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. +}; + +class TypeTaggable : public Value { + public: +#if NAPI_VERSION >= 8 + void TypeTag(const napi_type_tag* type_tag) const; + bool CheckTypeTag(const napi_type_tag* type_tag) const; +#endif // NAPI_VERSION >= 8 + protected: + TypeTaggable(); + TypeTaggable(napi_env env, napi_value value); +}; + +/// A JavaScript object value. +class Object : public TypeTaggable { + public: + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { + public: + /// Converts an L-value to a value. + operator Value() const; + + /// Assigns a value to the property. The type of value can be + /// anything supported by `Object::Set`. + template + PropertyLValue& operator=(ValueType value); + + private: + PropertyLValue() = delete; + PropertyLValue(Object object, Key key); + napi_env _env; + napi_value _object; + Key _key; + + friend class Napi::Object; + }; + + /// Creates a new Object value. + static Object New(napi_env env ///< Node-API environment + ); + + static void CheckCast(napi_env env, napi_value value); + + Object(); ///< Creates a new _empty_ Object instance. + Object(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + /// Gets or sets a named property. + PropertyLValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ); + + /// Gets or sets a named property. + PropertyLValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[]( + uint32_t index /// Property / element index + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue operator[](uint32_t index ///< Property / element index + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a property is present. + MaybeOrValue Has(Value key ///< Property key + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a named property is present. + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(napi_value key ///< Property key primitive + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty(Value key ///< Property key + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Checks whether a own property is present. + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Gets a property. + MaybeOrValue Get(napi_value key ///< Property key primitive + ) const; + + /// Gets a property. + MaybeOrValue Get(Value key ///< Property key + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Gets a named property. + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Sets a property. + template + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive + ) const; + + /// Sets a property. + template + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value + ) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const char* utf8name, ///< UTF-8 encoded null-terminated property name + const ValueType& value) const; + + /// Sets a named property. + template + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(napi_value key ///< Property key primitive + ) const; + + /// Delete property. + MaybeOrValue Delete(Value key ///< Property key + ) const; + + /// Delete property. + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name + ) const; + + /// Delete property. + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name + ) const; + + /// Checks whether an indexed property is present. + MaybeOrValue Has(uint32_t index ///< Property / element index + ) const; + + /// Gets an indexed property or array element. + MaybeOrValue Get(uint32_t index ///< Property / element index + ) const; + + /// Sets an indexed property or array element. + template + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive + ) const; + + /// Deletes an indexed property or array element. + MaybeOrValue Delete(uint32_t index ///< Property / element index + ) const; + + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names + + /// Defines a property on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( + const PropertyDescriptor& + property ///< Descriptor for the property to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::initializer_list& properties + ///< List of descriptors for the properties to be defined + ) const; + + /// Defines properties on the object. + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( + const std::vector& properties + ///< Vector of descriptors for the properties to be defined + ) const; + + /// Checks if an object is an instance created by a constructor function. + /// + /// This is equivalent to the JavaScript `instanceof` operator. + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function + ) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) const; + +#ifdef NAPI_CPP_EXCEPTIONS + class const_iterator; + + inline const_iterator begin() const; + + inline const_iterator end() const; + + class iterator; + + inline iterator begin(); + + inline iterator end(); +#endif // NAPI_CPP_EXCEPTIONS + +#if NAPI_VERSION >= 8 + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze() const; + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal() const; +#endif // NAPI_VERSION >= 8 +}; + +template +class External : public TypeTaggable { + public: + static External New(napi_env env, T* data); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static External New(napi_env env, T* data, Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static External New(napi_env env, + T* data, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static void CheckCast(napi_env env, napi_value value); + + External(); + External(napi_env env, napi_value value); + + T* Data() const; +}; + +class Array : public Object { + public: + static Array New(napi_env env); + static Array New(napi_env env, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Array(); + Array(napi_env env, napi_value value); + + uint32_t Length() const; +}; + +#ifdef NAPI_CPP_EXCEPTIONS +class Object::const_iterator { + private: + enum class Type { BEGIN, END }; + + inline const_iterator(const Object* object, const Type type); + + public: + inline const_iterator& operator++(); + + inline bool operator==(const const_iterator& other) const; + + inline bool operator!=(const const_iterator& other) const; + + inline const std::pair> operator*() + const; + + private: + const Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; + +class Object::iterator { + private: + enum class Type { BEGIN, END }; + + inline iterator(Object* object, const Type type); + + public: + inline iterator& operator++(); + + inline bool operator==(const iterator& other) const; + + inline bool operator!=(const iterator& other) const; + + inline std::pair> operator*(); + + private: + Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; +}; +#endif // NAPI_CPP_EXCEPTIONS + +/// A JavaScript array buffer value. +class ArrayBuffer : public Object { + public: + /// Creates a new ArrayBuffer instance over a new automatically-allocated + /// buffer. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + size_t byteLength ///< Length of the buffer to be allocated, in bytes + ); + +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength ///< Length of the external buffer to be used by the + ///< array, in bytes + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env env, + /// void* externalData)` + ); + + /// Creates a new ArrayBuffer instance, using an external buffer with + /// specified byte length. + template + static ArrayBuffer New( + napi_env env, ///< Node-API environment + void* externalData, ///< Pointer to the external buffer to be used by + ///< the array + size_t byteLength, ///< Length of the external buffer to be used by the + ///< array, + /// in bytes + Finalizer finalizeCallback, ///< Function to be called when the array + ///< buffer is destroyed; + /// must implement `void operator()(Env + /// env, void* externalData, Hint* hint)` + Hint* finalizeHint ///< Hint (second parameter) to be passed to the + ///< finalize callback + ); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static void CheckCast(napi_env env, napi_value value); + + ArrayBuffer(); ///< Creates a new _empty_ ArrayBuffer instance. + ArrayBuffer(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + void* Data(); ///< Gets a pointer to the data buffer. + size_t ByteLength(); ///< Gets the length of the array buffer in bytes. + +#if NAPI_VERSION >= 7 + bool IsDetached() const; + void Detach(); +#endif // NAPI_VERSION >= 7 +}; + +/// A JavaScript typed-array value with unknown array type. +/// +/// For type-specific operations, cast to a `TypedArrayOf` instance using the +/// `As()` method: +/// +/// Napi::TypedArray array = ... +/// if (t.TypedArrayType() == napi_int32_array) { +/// Napi::Int32Array int32Array = t.As(); +/// } +class TypedArray : public Object { + public: + static void CheckCast(napi_env env, napi_value value); + + TypedArray(); ///< Creates a new _empty_ TypedArray instance. + TypedArray(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + napi_typedarray_type TypedArrayType() + const; ///< Gets the type of this typed-array. + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + uint8_t ElementSize() + const; ///< Gets the size in bytes of one element in the array. + size_t ElementLength() const; ///< Gets the number of elements in the array. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + protected: + /// !cond INTERNAL + napi_typedarray_type _type; + size_t _length; + + TypedArray(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length); + + template + static +#if defined(NAPI_HAS_CONSTEXPR) + constexpr +#endif + napi_typedarray_type + TypedArrayTypeForPrimitiveType() { + return std::is_same::value ? napi_int8_array + : std::is_same::value ? napi_uint8_array + : std::is_same::value ? napi_int16_array + : std::is_same::value ? napi_uint16_array + : std::is_same::value ? napi_int32_array + : std::is_same::value ? napi_uint32_array + : std::is_same::value ? napi_float32_array + : std::is_same::value ? napi_float64_array +#if NAPI_VERSION > 5 + : std::is_same::value ? napi_bigint64_array + : std::is_same::value ? napi_biguint64_array +#endif // NAPI_VERSION > 5 + : napi_int8_array; + } + /// !endcond +}; + +/// A JavaScript typed-array value with known array type. +/// +/// Note while it is possible to create and access Uint8 "clamped" arrays using +/// this class, the _clamping_ behavior is only applied in JavaScript. +template +class TypedArrayOf : public TypedArray { + public: + /// Creates a new TypedArray instance over a new automatically-allocated array + /// buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + /// Creates a new TypedArray instance over a provided array buffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::ArrayBuffer arrayBuffer, ///< Backing array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); + + static void CheckCast(napi_env env, napi_value value); + + TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. + TypedArrayOf(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + T& operator[](size_t index); ///< Gets or sets an element in the array. + const T& operator[](size_t index) const; ///< Gets an element in the array. + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + T* Data(); + + /// Gets a pointer to the array's backing buffer. + /// + /// This is not necessarily the same as the `ArrayBuffer::Data()` pointer, + /// because the typed-array may have a non-zero `ByteOffset()` into the + /// `ArrayBuffer`. + const T* Data() const; + + private: + T* _data; + + TypedArrayOf(napi_env env, + napi_value value, + napi_typedarray_type type, + size_t length, + T* data); +}; + +/// The DataView provides a low-level interface for reading/writing multiple +/// number types in an ArrayBuffer irrespective of the platform's endianness. +class DataView : public Object { + public: + static DataView New(napi_env env, Napi::ArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::ArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); + + static void CheckCast(napi_env env, napi_value value); + + DataView(); ///< Creates a new _empty_ DataView instance. + DataView(napi_env env, + napi_value value); ///< Wraps a Node-API value primitive. + + Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + size_t ByteOffset() + const; ///< Gets the offset into the buffer where the array starts. + size_t ByteLength() const; ///< Gets the length of the array in bytes. + + void* Data() const; + + float GetFloat32(size_t byteOffset) const; + double GetFloat64(size_t byteOffset) const; + int8_t GetInt8(size_t byteOffset) const; + int16_t GetInt16(size_t byteOffset) const; + int32_t GetInt32(size_t byteOffset) const; + uint8_t GetUint8(size_t byteOffset) const; + uint16_t GetUint16(size_t byteOffset) const; + uint32_t GetUint32(size_t byteOffset) const; + + void SetFloat32(size_t byteOffset, float value) const; + void SetFloat64(size_t byteOffset, double value) const; + void SetInt8(size_t byteOffset, int8_t value) const; + void SetInt16(size_t byteOffset, int16_t value) const; + void SetInt32(size_t byteOffset, int32_t value) const; + void SetUint8(size_t byteOffset, uint8_t value) const; + void SetUint16(size_t byteOffset, uint16_t value) const; + void SetUint32(size_t byteOffset, uint32_t value) const; + + private: + template + T ReadData(size_t byteOffset) const; + + template + void WriteData(size_t byteOffset, T value) const; + + void* _data; + size_t _length; +}; + +class Function : public Object { + public: + using VoidCallback = void (*)(const CallbackInfo& info); + using Callback = Value (*)(const CallbackInfo& info); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const char* utf8name = nullptr, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + template + static Function New(napi_env env, + const std::string& utf8name, + void* data = nullptr); + + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const char* utf8name = nullptr, + void* data = nullptr); + /// Callable must implement operator() accepting a const CallbackInfo& + /// and return either void or Value. + template + static Function New(napi_env env, + Callable cb, + const std::string& utf8name, + void* data = nullptr); + + static void CheckCast(napi_env env, napi_value value); + + Function(); + Function(napi_env env, napi_value value); + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call(const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call(napi_value recv, + const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; +}; + +class Promise : public Object { + public: + class Deferred { + public: + static Deferred New(napi_env env); + Deferred(napi_env env); + + Napi::Promise Promise() const; + Napi::Env Env() const; + + void Resolve(napi_value value) const; + void Reject(napi_value value) const; + + private: + napi_env _env; + napi_deferred _deferred; + napi_value _promise; + }; + + static void CheckCast(napi_env env, napi_value value); + + Promise(napi_env env, napi_value value); +}; + +template +class Buffer : public Uint8Array { + public: + static Buffer New(napi_env env, size_t length); +#ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + static Buffer New(napi_env env, T* data, size_t length); + + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer New(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); +#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED + + static Buffer NewOrCopy(napi_env env, T* data, size_t length); + // Finalizer must implement `void operator()(Env env, T* data)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback); + // Finalizer must implement `void operator()(Env env, T* data, Hint* hint)`. + template + static Buffer NewOrCopy(napi_env env, + T* data, + size_t length, + Finalizer finalizeCallback, + Hint* finalizeHint); + + static Buffer Copy(napi_env env, const T* data, size_t length); + + static void CheckCast(napi_env env, napi_value value); + + Buffer(); + Buffer(napi_env env, napi_value value); + size_t Length() const; + T* Data() const; + + private: +}; + +/// Holds a counted reference to a value; initially a weak reference unless +/// otherwise specified, may be changed to/from a strong reference by adjusting +/// the refcount. +/// +/// The referenced value is not immediately destroyed when the reference count +/// is zero; it is merely then eligible for garbage-collection if there are no +/// other references to the value. +template +class Reference { + public: + static Reference New(const T& value, uint32_t initialRefcount = 0); + + Reference(); + Reference(napi_env env, napi_ref ref); + ~Reference(); + + // A reference can be moved but cannot be copied. + Reference(Reference&& other); + Reference& operator=(Reference&& other); + NAPI_DISALLOW_ASSIGN(Reference) + + operator napi_ref() const; + bool operator==(const Reference& other) const; + bool operator!=(const Reference& other) const; + + Napi::Env Env() const; + bool IsEmpty() const; + + // Note when getting the value of a Reference it is usually correct to do so + // within a HandleScope so that the value handle gets cleaned up efficiently. + T Value() const; + + uint32_t Ref() const; + uint32_t Unref() const; + void Reset(); + void Reset(const T& value, uint32_t refcount = 0); + + // Call this on a reference that is declared as static data, to prevent its + // destructor from running at program shutdown time, which would attempt to + // reset the reference when the environment is no longer valid. Avoid using + // this if at all possible. If you do need to use static data, MAKE SURE to + // warn your users that your addon is NOT threadsafe. + void SuppressDestruct(); + + protected: + Reference(const Reference&); + + /// !cond INTERNAL + napi_env _env; + napi_ref _ref; + /// !endcond + + private: + bool _suppressDestruct; +}; + +class ObjectReference : public Reference { + public: + ObjectReference(); + ObjectReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + ObjectReference(Reference&& other); + ObjectReference& operator=(Reference&& other); + ObjectReference(ObjectReference&& other); + ObjectReference& operator=(ObjectReference&& other); + NAPI_DISALLOW_ASSIGN(ObjectReference) + + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value) const; + MaybeOrValue Set(const char* utf8name, Napi::Value value) const; + MaybeOrValue Set(const char* utf8name, const char* utf8value) const; + MaybeOrValue Set(const char* utf8name, bool boolValue) const; + MaybeOrValue Set(const char* utf8name, double numberValue) const; + MaybeOrValue Set(const std::string& utf8name, napi_value value) const; + MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; + MaybeOrValue Set(const std::string& utf8name, + std::string& utf8value) const; + MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; + MaybeOrValue Set(const std::string& utf8name, double numberValue) const; + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value) const; + MaybeOrValue Set(uint32_t index, const Napi::Value value) const; + MaybeOrValue Set(uint32_t index, const char* utf8value) const; + MaybeOrValue Set(uint32_t index, const std::string& utf8value) const; + MaybeOrValue Set(uint32_t index, bool boolValue) const; + MaybeOrValue Set(uint32_t index, double numberValue) const; + + protected: + ObjectReference(const ObjectReference&); +}; + +class FunctionReference : public Reference { + public: + FunctionReference(); + FunctionReference(napi_env env, napi_ref ref); + + // A reference can be moved but cannot be copied. + FunctionReference(Reference&& other); + FunctionReference& operator=(Reference&& other); + FunctionReference(FunctionReference&& other); + FunctionReference& operator=(FunctionReference&& other); + NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) + + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New(const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; +}; + +// Shortcuts to creating a new reference with inferred type and refcount = 0. +template +Reference Weak(T value); +ObjectReference Weak(Object value); +FunctionReference Weak(Function value); + +// Shortcuts to creating a new reference with inferred type and refcount = 1. +template +Reference Persistent(T value); +ObjectReference Persistent(Object value); +FunctionReference Persistent(Function value); + +/// A persistent reference to a JavaScript error object. Use of this class +/// depends somewhat on whether C++ exceptions are enabled at compile time. +/// +/// ### Handling Errors With C++ Exceptions +/// +/// If C++ exceptions are enabled, then the `Error` class extends +/// `std::exception` and enables integrated error-handling for C++ exceptions +/// and JavaScript exceptions. +/// +/// If a Node-API call fails without executing any JavaScript code (for +/// example due to an invalid argument), then the Node-API wrapper +/// automatically converts and throws the error as a C++ exception of type +/// `Napi::Error`. Or if a JavaScript function called by C++ code via Node-API +/// throws a JavaScript exception, then the Node-API wrapper automatically +/// converts and throws it as a C++ exception of type `Napi::Error`. +/// +/// If a C++ exception of type `Napi::Error` escapes from a Node-API C++ +/// callback, then the Node-API wrapper automatically converts and throws it +/// as a JavaScript exception. Therefore, catching a C++ exception of type +/// `Napi::Error` prevents a JavaScript exception from being thrown. +/// +/// #### Example 1A - Throwing a C++ exception: +/// +/// Napi::Env env = ... +/// throw Napi::Error::New(env, "Example exception"); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propataged as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 2A - Propagating a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// +/// Following C++ statements will not be executed. The exception will bubble +/// up as a C++ exception of type `Napi::Error`, until it is either caught +/// while still in C++, or else automatically propagated as a JavaScript +/// exception when the callback returns to JavaScript. +/// +/// #### Example 3A - Handling a Node-API C++ exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result; +/// try { +/// result = jsFunctionThatThrows({ arg1, arg2 }); +/// } catch (const Napi::Error& e) { +/// cerr << "Caught JavaScript exception: " + e.what(); +/// } +/// +/// Since the exception was caught here, it will not be propagated as a +/// JavaScript exception. +/// +/// ### Handling Errors Without C++ Exceptions +/// +/// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) +/// then this class does not extend `std::exception`, and APIs in the `Napi` +/// namespace do not throw C++ exceptions when they fail. Instead, they raise +/// _pending_ JavaScript exceptions and return _empty_ `Value`s. Calling code +/// should check `Value::IsEmpty()` before attempting to use a returned value, +/// and may use methods on the `Env` class to check for, get, and clear a +/// pending JavaScript exception. If the pending exception is not cleared, it +/// will be thrown when the native callback returns to JavaScript. +/// +/// #### Example 1B - Throwing a JS exception +/// +/// Napi::Env env = ... +/// Napi::Error::New(env, "Example +/// exception").ThrowAsJavaScriptException(); return; +/// +/// After throwing a JS exception, the code should generally return +/// immediately from the native callback, after performing any necessary +/// cleanup. +/// +/// #### Example 2B - Propagating a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) return; +/// +/// An empty value result from a Node-API call indicates an error occurred, +/// and a JavaScript exception is pending. To let the exception propagate, the +/// code should generally return immediately from the native callback, after +/// performing any necessary cleanup. +/// +/// #### Example 3B - Handling a Node-API JS exception: +/// +/// Napi::Function jsFunctionThatThrows = someObj.As(); +/// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); +/// if (result.IsEmpty()) { +/// Napi::Error e = env.GetAndClearPendingException(); +/// cerr << "Caught JavaScript exception: " + e.Message(); +/// } +/// +/// Since the exception was cleared here, it will not be propagated as a +/// JavaScript exception after the native callback returns. +class Error : public ObjectReference +#ifdef NAPI_CPP_EXCEPTIONS + , + public std::exception +#endif // NAPI_CPP_EXCEPTIONS +{ + public: + static Error New(napi_env env); + static Error New(napi_env env, const char* message); + static Error New(napi_env env, const std::string& message); + + static NAPI_NO_RETURN void Fatal(const char* location, const char* message); + + Error(); + Error(napi_env env, napi_value value); + + // An error can be moved or copied. + Error(Error&& other); + Error& operator=(Error&& other); + Error(const Error&); + Error& operator=(const Error&); + + const std::string& Message() const NAPI_NOEXCEPT; + void ThrowAsJavaScriptException() const; + + Object Value() const; + +#ifdef NAPI_CPP_EXCEPTIONS + const char* what() const NAPI_NOEXCEPT override; +#endif // NAPI_CPP_EXCEPTIONS + + protected: + /// !cond INTERNAL + using create_error_fn = napi_status (*)(napi_env envb, + napi_value code, + napi_value msg, + napi_value* result); + + template + static TError New(napi_env env, + const char* message, + size_t length, + create_error_fn create_error); + /// !endcond + + private: + static inline const char* ERROR_WRAP_VALUE() NAPI_NOEXCEPT; + mutable std::string _message; +}; + +class TypeError : public Error { + public: + static TypeError New(napi_env env, const char* message); + static TypeError New(napi_env env, const std::string& message); + + TypeError(); + TypeError(napi_env env, napi_value value); +}; + +class RangeError : public Error { + public: + static RangeError New(napi_env env, const char* message); + static RangeError New(napi_env env, const std::string& message); + + RangeError(); + RangeError(napi_env env, napi_value value); +}; + +#if NAPI_VERSION > 8 +class SyntaxError : public Error { + public: + static SyntaxError New(napi_env env, const char* message); + static SyntaxError New(napi_env env, const std::string& message); + + SyntaxError(); + SyntaxError(napi_env env, napi_value value); +}; +#endif // NAPI_VERSION > 8 + +class CallbackInfo { + public: + CallbackInfo(napi_env env, napi_callback_info info); + ~CallbackInfo(); + + // Disallow copying to prevent multiple free of _dynamicArgs + NAPI_DISALLOW_ASSIGN_COPY(CallbackInfo) + + Napi::Env Env() const; + Value NewTarget() const; + bool IsConstructCall() const; + size_t Length() const; + const Value operator[](size_t index) const; + Value This() const; + void* Data() const; + void SetData(void* data); + explicit operator napi_callback_info() const; + + private: + const size_t _staticArgCount = 6; + napi_env _env; + napi_callback_info _info; + napi_value _this; + size_t _argc; + napi_value* _argv; + napi_value _staticArgs[6]; + napi_value* _dynamicArgs; + void* _data; +}; + +class PropertyDescriptor { + public: + using GetterCallback = Napi::Value (*)(const Napi::CallbackInfo& info); + using SetterCallback = void (*)(const Napi::CallbackInfo& info); + +#ifndef NODE_ADDON_API_DISABLE_DEPRECATED + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + napi_value name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + napi_value name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +#endif // !NODE_ADDON_API_DISABLE_DEPRECATED + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + const std::string& utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Name name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Accessor( + Napi::Env env, + Napi::Object object, + Name name, + Getter getter, + Setter setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const char* utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + const std::string& utf8name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor Function( + Napi::Env env, + Napi::Object object, + Name name, + Callable cb, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor Value( + const char* utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + const std::string& utf8name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + napi_value name, + napi_value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor Value( + Name name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + PropertyDescriptor(napi_property_descriptor desc); + + operator napi_property_descriptor&(); + operator const napi_property_descriptor&() const; + + private: + napi_property_descriptor _desc; +}; + +/// Property descriptor for use with `ObjectWrap::DefineClass()`. +/// +/// This is different from the standalone `PropertyDescriptor` because it is +/// specific to each `ObjectWrap` subclass. This prevents using descriptors +/// from a different class when defining a new class (preventing the callbacks +/// from having incorrect `this` pointers). +template +class ClassPropertyDescriptor { + public: + ClassPropertyDescriptor(napi_property_descriptor desc) : _desc(desc) {} + + operator napi_property_descriptor&() { return _desc; } + operator const napi_property_descriptor&() const { return _desc; } + + private: + napi_property_descriptor _desc; +}; + +template +struct MethodCallbackData { + TCallback callback; + void* data; +}; + +template +struct AccessorCallbackData { + TGetterCallback getterCallback; + TSetterCallback setterCallback; + void* data; +}; + +template +class InstanceWrap { + public: + using InstanceVoidMethodCallback = void (T::*)(const CallbackInfo& info); + using InstanceMethodCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceGetterCallback = Napi::Value (T::*)(const CallbackInfo& info); + using InstanceSetterCallback = void (T::*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + const char* utf8name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceMethod( + Symbol name, + InstanceMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor InstanceAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor InstanceValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + + protected: + static void AttachPropData(napi_env env, + napi_value value, + const napi_property_descriptor* prop); + + private: + using This = InstanceWrap; + + using InstanceVoidMethodCallbackData = + MethodCallbackData; + using InstanceMethodCallbackData = + MethodCallbackData; + using InstanceAccessorCallbackData = + AccessorCallbackData; + + static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value InstanceSetterCallbackWrapper(napi_env env, + napi_callback_info info); + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct SetterTag {}; + + template + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapSetter(SetterTag) NAPI_NOEXCEPT { + return nullptr; + } +}; + +/// Base class to be extended by C++ classes exposed to JavaScript; each C++ +/// class instance gets "wrapped" by a JavaScript object that is managed by this +/// class. +/// +/// At initialization time, the `DefineClass()` method must be used to +/// hook up the accessor and method callbacks. It takes a list of +/// property descriptors, which can be constructed via the various +/// static methods on the base class. +/// +/// #### Example: +/// +/// class Example: public Napi::ObjectWrap { +/// public: +/// static void Initialize(Napi::Env& env, Napi::Object& target) { +/// Napi::Function constructor = DefineClass(env, "Example", { +/// InstanceAccessor<&Example::GetSomething, +/// &Example::SetSomething>("value"), +/// InstanceMethod<&Example::DoSomething>("doSomething"), +/// }); +/// target.Set("Example", constructor); +/// } +/// +/// Example(const Napi::CallbackInfo& info); // Constructor +/// Napi::Value GetSomething(const Napi::CallbackInfo& info); +/// void SetSomething(const Napi::CallbackInfo& info, const Napi::Value& +/// value); Napi::Value DoSomething(const Napi::CallbackInfo& info); +/// } +template +class ObjectWrap : public InstanceWrap, public Reference { + public: + ObjectWrap(const CallbackInfo& callbackInfo); + virtual ~ObjectWrap(); + + static T* Unwrap(Object wrapper); + + // Methods exposed to JavaScript must conform to one of these callback + // signatures. + using StaticVoidMethodCallback = void (*)(const CallbackInfo& info); + using StaticMethodCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticGetterCallback = Napi::Value (*)(const CallbackInfo& info); + using StaticSetterCallback = void (*)(const CallbackInfo& info, + const Napi::Value& value); + + using PropertyDescriptor = ClassPropertyDescriptor; + + static Function DefineClass( + Napi::Env env, + const char* utf8name, + const std::initializer_list& properties, + void* data = nullptr); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const std::vector& properties, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + const char* utf8name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticMethod( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + const char* utf8name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + const char* utf8name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + template + static PropertyDescriptor StaticAccessor( + Symbol name, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticValue( + const char* utf8name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); + static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); + virtual void Finalize(Napi::Env env); + + private: + using This = ObjectWrap; + + static napi_value ConstructorCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticVoidMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticMethodCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticGetterCallbackWrapper(napi_env env, + napi_callback_info info); + static napi_value StaticSetterCallbackWrapper(napi_env env, + napi_callback_info info); + static void FinalizeCallback(napi_env env, void* data, void* hint); + static Function DefineClass(Napi::Env env, + const char* utf8name, + const size_t props_count, + const napi_property_descriptor* props, + void* data = nullptr); + + using StaticVoidMethodCallbackData = + MethodCallbackData; + using StaticMethodCallbackData = MethodCallbackData; + + using StaticAccessorCallbackData = + AccessorCallbackData; + + template + static napi_value WrappedMethod(napi_env env, + napi_callback_info info) NAPI_NOEXCEPT; + + template + struct StaticSetterTag {}; + + template + static napi_callback WrapStaticSetter(StaticSetterTag) NAPI_NOEXCEPT { + return &This::WrappedMethod; + } + static napi_callback WrapStaticSetter(StaticSetterTag) + NAPI_NOEXCEPT { + return nullptr; + } + + bool _construction_failed = true; +}; + +class HandleScope { + public: + HandleScope(napi_env env, napi_handle_scope scope); + explicit HandleScope(Napi::Env env); + ~HandleScope(); + + // Disallow copying to prevent double close of napi_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(HandleScope) + + operator napi_handle_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_handle_scope _scope; +}; + +class EscapableHandleScope { + public: + EscapableHandleScope(napi_env env, napi_escapable_handle_scope scope); + explicit EscapableHandleScope(Napi::Env env); + ~EscapableHandleScope(); + + // Disallow copying to prevent double close of napi_escapable_handle_scope + NAPI_DISALLOW_ASSIGN_COPY(EscapableHandleScope) + + operator napi_escapable_handle_scope() const; + + Napi::Env Env() const; + Value Escape(napi_value escapee); + + private: + napi_env _env; + napi_escapable_handle_scope _scope; +}; + +#if !defined (__OHOS__) +#if (NAPI_VERSION > 2) +class CallbackScope { + public: + CallbackScope(napi_env env, napi_callback_scope scope); + CallbackScope(napi_env env, napi_async_context context); + virtual ~CallbackScope(); + + // Disallow copying to prevent double close of napi_callback_scope + NAPI_DISALLOW_ASSIGN_COPY(CallbackScope) + + operator napi_callback_scope() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_callback_scope _scope; +}; +#endif + +class AsyncContext { + public: + explicit AsyncContext(napi_env env, const char* resource_name); + explicit AsyncContext(napi_env env, + const char* resource_name, + const Object& resource); + virtual ~AsyncContext(); + + AsyncContext(AsyncContext&& other); + AsyncContext& operator=(AsyncContext&& other); + NAPI_DISALLOW_ASSIGN_COPY(AsyncContext) + + operator napi_async_context() const; + + Napi::Env Env() const; + + private: + napi_env _env; + napi_async_context _context; +}; +#endif // !defined (__OHOS__) + +#if NAPI_HAS_THREADS +class AsyncWorker { + public: + virtual ~AsyncWorker(); + + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) + + operator napi_async_work() const; + + Napi::Env Env() const; + + void Queue(); + void Cancel(); + void SuppressDestruct(); + + ObjectReference& Receiver(); + FunctionReference& Callback(); + + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, napi_status status); + + protected: + explicit AsyncWorker(const Function& callback); + explicit AsyncWorker(const Function& callback, const char* resource_name); + explicit AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncWorker(const Object& receiver, const Function& callback); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + + virtual void Execute() = 0; + virtual void OnOK(); + virtual void OnError(const Error& e); + virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); + + void SetError(const std::string& error); + + private: + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); + + napi_env _env; + napi_async_work _work; + ObjectReference _receiver; + FunctionReference _callback; + std::string _error; + bool _suppress_destruct; +}; +#endif // NAPI_HAS_THREADS + +#if (NAPI_VERSION > 3 && NAPI_HAS_THREADS) +class ThreadSafeFunction { + public: + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + Finalizer finalizeCallback, + FinalizerDataType* data); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback); + + // This API may only be called from the main thread. + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data); + + ThreadSafeFunction(); + ThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall() const; + + // This API may be called from any thread. + template + napi_status BlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status BlockingCall(DataType* data, Callback callback) const; + + // This API may be called from any thread. + napi_status NonBlockingCall() const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(Callback callback) const; + + // This API may be called from any thread. + template + napi_status NonBlockingCall(DataType* data, Callback callback) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + struct ConvertibleContext { + template + operator T*() { + return static_cast(context); + } + void* context; + }; + + // This API may be called from any thread. + ConvertibleContext GetContext() const; + + private: + using CallbackWrapper = std::function; + + template + static ThreadSafeFunction New(napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + napi_status CallInternal(CallbackWrapper* callbackWrapper, + napi_threadsafe_function_call_mode mode) const; + + static void CallJS(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + napi_threadsafe_function _tsfn; +}; + +// A TypedThreadSafeFunction by default has no context (nullptr) and can +// accept any type (void) to its CallJs. +template +class TypedThreadSafeFunction { + public: + // This API may only be called from the main thread. + // Helper function that returns nullptr if running Node-API 5+, otherwise a + // non-empty, no-op Function. This provides the ability to specify at + // compile-time a callback parameter to `New` that safely does no action + // when targeting _any_ Node-API version. +#if NAPI_VERSION > 4 + static std::nullptr_t EmptyFunctionFactory(Napi::Env env); +#else + static Napi::Function EmptyFunctionFactory(Napi::Env env); +#endif + static Napi::Function FunctionOrEmpty(Napi::Env env, + Napi::Function& callback); + +#if NAPI_VERSION > 4 + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [missing] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); +#endif + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [missing] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [missing] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + // This API may only be called from the main thread. + // Creates a new threadsafe function with: + // Callback [passed] Resource [passed] Finalizer [passed] + template + static TypedThreadSafeFunction New( + napi_env env, + CallbackType callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data = nullptr); + + TypedThreadSafeFunction(); + TypedThreadSafeFunction(napi_threadsafe_function tsFunctionValue); + + operator napi_threadsafe_function() const; + + // This API may be called from any thread. + napi_status BlockingCall(DataType* data = nullptr) const; + + // This API may be called from any thread. + napi_status NonBlockingCall(DataType* data = nullptr) const; + + // This API may only be called from the main thread. + void Ref(napi_env env) const; + + // This API may only be called from the main thread. + void Unref(napi_env env) const; + + // This API may be called from any thread. + napi_status Acquire() const; + + // This API may be called from any thread. + napi_status Release() const; + + // This API may be called from any thread. + napi_status Abort() const; + + // This API may be called from any thread. + ContextType* GetContext() const; + + private: + template + static TypedThreadSafeFunction New( + napi_env env, + const Function& callback, + const Object& resource, + ResourceString resourceName, + size_t maxQueueSize, + size_t initialThreadCount, + ContextType* context, + Finalizer finalizeCallback, + FinalizerDataType* data, + napi_finalize wrapper); + + static void CallJsInternal(napi_env env, + napi_value jsCallback, + void* context, + void* data); + + protected: + napi_threadsafe_function _tsfn; +}; +template +class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { + return _asyncprogressworker; + } + DataType* data() { return _data; } + + private: + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; + }; + void OnWorkComplete(Napi::Env env, napi_status status) override; + + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); +#endif + + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); + + napi_status NonBlockingCall(DataType* data); + + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize( + Napi::Env env, void* data, AsyncProgressWorkerBase* context); +}; + +template +class AsyncProgressWorker : public AsyncProgressWorkerBase { + public: + virtual ~AsyncProgressWorker(); + + class ExecutionProgress { + friend class AsyncProgressWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; + }; + + void OnWorkProgress(void*) override; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal(); + void SendProgress_(const T* data, size_t count); + + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + bool _signaled; +}; + +template +class AsyncProgressQueueWorker + : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker(){} + + class ExecutionProgress { + friend class AsyncProgressQueueWorker; + + public: + void Signal() const; + void Send(const T* data, size_t count) const; + + private: + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) + : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; + }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after +// NAPI_VERSION 4. Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); +}; +#endif // NAPI_VERSION > 3 && NAPI_HAS_THREADS + +#if (!defined(__OHOS__)) +// Memory management. +class MemoryManagement { + public: + static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); +}; + +// Version management +class VersionManagement { + public: + static uint32_t GetNapiVersion(Env env); + static const napi_node_version* GetNodeVersion(Env env); +}; + +#if NAPI_VERSION > 5 +template +class Addon : public InstanceWrap { + public: + static inline Object Init(Env env, Object exports); + static T* Unwrap(Object wrapper); + + protected: + using AddonProp = ClassPropertyDescriptor; + void DefineAddon(Object exports, + const std::initializer_list& props); + Napi::Object DefineProperties(Object object, + const std::initializer_list& props); + + private: + Object entry_point_; +}; +#endif // NAPI_VERSION > 5 +#endif // !defined(__OHOS__) + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif + +} // namespace Napi + +// Inline implementations of all the above class methods are included here. +#include "napi-inl.h" + +#endif // SRC_NAPI_H_ diff --git a/sdk/ohos/src/ohos_webrtc/.clang-format b/sdk/ohos/src/ohos_webrtc/.clang-format new file mode 100755 index 0000000000000000000000000000000000000000..7d08d8337ff63b950392ef303af82fb704bc591a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/.clang-format @@ -0,0 +1,33 @@ +BasedOnStyle: LLVM +UseTab: Never +SortIncludes: Never +IndentWidth: 4 +TabWidth: 4 +IndentCaseLabels: true +ColumnLimit: 120 +AccessModifierOffset: -4 +DerivePointerAlignment: false +PointerAlignment: Left +ReferenceAlignment: Left +CompactNamespaces: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AlignAfterOpenBracket: AlwaysBreak +BreakConstructorInitializers: BeforeColon +AlwaysBreakTemplateDeclarations: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterStruct: false + AfterClass: false + AfterFunction: true + AfterNamespace: false + AfterCaseLabel: false + AfterUnion: false + AfterExternBlock: true + AfterControlStatement: MultiLine + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false +PackConstructorInitializers: NextLine diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_certificate.h b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_certificate.h new file mode 100644 index 0000000000000000000000000000000000000000..f8644491e172f6c6902098ee63e903814d9409f6 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_certificate.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ASYNC_WORKER_CERTIFICATE_H +#define WEBRTC_ASYNC_WORKER_CERTIFICATE_H + +#include "certificate.h" + +#include "api/rtc_error.h" +#include "napi.h" +#include "api/peer_connection_interface.h" + +namespace webrtc { + +class AsyncWorkerCertificate : public Napi::AsyncWorker { +public: + AsyncWorkerCertificate(Napi::Env env, const char* resourceName) + : AsyncWorker(env, resourceName), deferred_(Napi::Promise::Deferred::New(env)), error_(RTCError::OK()) + { + } + + Napi::Promise GetPromise() + { + return deferred_.Promise(); + } + + Napi::Promise::Deferred GetDeferred() + { + return deferred_; + } + + void SetError(RTCError error) + { + error_ = std::move(error); + AsyncWorker::SetError(std::string(error_.message())); + } + + void start(const rtc::KeyParams& key_params, absl::optional expires_ms) + { + key_params_ = key_params; + expires_ms_ = expires_ms; + Queue(); + } + +protected: + void Execute() override + { + certificate_ = certificateGenerator->GenerateCertificate(key_params_, expires_ms_); + } + + void OnOK() override + { + auto result = NapiCertificate::NewInstance(Env(), certificate_); + + deferred_.Resolve(result); + } + + void OnError(const Napi::Error& e) override + { + deferred_.Reject(e.Value()); + } + +private: + Napi::Promise::Deferred deferred_; + RTCError error_; + std::unique_ptr certificateGenerator; + rtc::scoped_refptr certificate_; + rtc::KeyParams key_params_; + absl::optional expires_ms_; +}; + +} // namespace webrtc + +#endif // WEBRTC_ASYNC_WORKER_CERTIFICATE_H diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.cpp b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.cpp new file mode 100644 index 0000000000000000000000000000000000000000..56a6ac7c241b8881bbb2855322d473ee90b2d929 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "async_worker_enumerate_devices.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +AsyncWorkerEnumerateDevices* AsyncWorkerEnumerateDevices::Create(Napi::Env env, const char* resourceName) +{ + auto asyncWorker = new AsyncWorkerEnumerateDevices(env, resourceName); + return asyncWorker; +} + +AsyncWorkerEnumerateDevices::AsyncWorkerEnumerateDevices(Napi::Env env, const char* resourceName) + : AsyncWorker(env, resourceName), deferred_(Napi::Promise::Deferred::New(env)) +{ +} + +void AsyncWorkerEnumerateDevices::Execute() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + cameraDevices_ = CameraEnumerator::GetDevices(); + audioDevices_ = AudioDeviceEnumerator::GetDevices(); +} + +void AsyncWorkerEnumerateDevices::OnOK() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto result = Array::New(Env(), cameraDevices_.size() + audioDevices_.size()); + + for (uint32_t index = 0; index < cameraDevices_.size() ; index++) { + auto obj = Object::New(Env()); + obj.Set("deviceId", String::New(Env(), cameraDevices_[index].deviceId)); + obj.Set("groupId", String::New(Env(), cameraDevices_[index].groupId)); + obj.Set("label", String::New(Env(), cameraDevices_[index].label)); + obj.Set("kind", String::New(Env(), "videoinput")); + obj.Set("getCapabilities", Function::New(Env(), [](const CallbackInfo& info) { + RTC_DLOG(LS_VERBOSE) << "getCapabilities"; + NAPI_THROW(Error::New(info.Env(), "Not implemented"), info.Env().Undefined()); + })); + result.Set(index, obj); + } + + for (uint32_t index = 0; index < audioDevices_.size(); index++) { + auto obj = Object::New(Env()); + obj.Set("deviceId", String::New(Env(), audioDevices_[index].deviceId)); + obj.Set("groupId", String::New(Env(), audioDevices_[index].groupId)); + obj.Set("label", String::New(Env(), audioDevices_[index].label)); + if (audioDevices_[index].role == AudioDeviceRole::Input) { + obj.Set("kind", String::New(Env(), "audioinput")); + obj.Set("getCapabilities", Function::New(Env(), [](const CallbackInfo& info) { + RTC_DLOG(LS_VERBOSE) << "getCapabilities"; + NAPI_THROW(Error::New(info.Env(), "Not implemented"), info.Env().Undefined()); + })); + } else if (audioDevices_[index].role == AudioDeviceRole::Output) { + obj.Set("kind", String::New(Env(), "audiooutput")); + } else { + RTC_LOG(LS_WARNING) << "Invalid audio device role"; + } + result.Set(cameraDevices_.size() + index, obj); + } + + deferred_.Resolve(result); +} + +void AsyncWorkerEnumerateDevices::OnError(const Napi::Error& e) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + deferred_.Reject(e.Value()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.h b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.h new file mode 100644 index 0000000000000000000000000000000000000000..de05d89310eb7c8246a402d4dd01741d18e1956f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_enumerate_devices.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ASYNC_WORKER_ENUMERATE_DEVICES_H +#define WEBRTC_ASYNC_WORKER_ENUMERATE_DEVICES_H + +#include "../camera/camera_enumerator.h" +#include "../audio_device/audio_device_enumerator.h" + +#include + +#include "napi.h" + +namespace webrtc { + +class AsyncWorkerEnumerateDevices : public Napi::AsyncWorker { +public: + static AsyncWorkerEnumerateDevices* Create(Napi::Env env, const char* resourceName); + + Napi::Promise GetPromise() + { + return deferred_.Promise(); + } + +protected: + AsyncWorkerEnumerateDevices(Napi::Env env, const char* resourceName); + + void Execute() override; + void OnOK() override; + void OnError(const Napi::Error& e) override; + +private: + Napi::Promise::Deferred deferred_; + + std::vector cameraDevices_; + std::vector audioDevices_; +}; + +} // namespace webrtc + +#endif //WEBRTC_ASYNC_WORKER_ENUMERATE_DEVICES_H diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.cpp b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eeb15678bd563fc6b61c39fe21e5acf9d540ec65 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.cpp @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "async_worker_get_display_media.h" +#include "../peer_connection_factory.h" +#include "../media_stream.h" +#include "../media_stream_track.h" +#include "../desktop_capture/desktop_capturer.h" +#include "../video/video_track_source.h" +#include "../user_media/media_constraints_util.h" +#include "../render/egl_env.h" + +#include "rtc_base/logging.h" +#include "rtc_base/helpers.h" + +namespace webrtc { + +AsyncWorkerGetDisplayMedia* AsyncWorkerGetDisplayMedia::Create(Napi::Env env, const char* resourceName) +{ + auto asyncWorker = new AsyncWorkerGetDisplayMedia(env, resourceName); + return asyncWorker; +} + +AsyncWorkerGetDisplayMedia::AsyncWorkerGetDisplayMedia(Napi::Env env, const char* resourceName) + : AsyncWorker(env, resourceName), deferred_(Napi::Promise::Deferred::New(env)) +{ +} + +void AsyncWorkerGetDisplayMedia::Start(MediaTrackConstraints audio, MediaTrackConstraints video) +{ + audioConstraints_ = std::move(audio); + videoConstraints_ = std::move(video); + Queue(); +} + +void AsyncWorkerGetDisplayMedia::Execute() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto factoryWrapper = PeerConnectionFactoryWrapper::GetDefault(); + if (!factoryWrapper) { + SetError("Internal error"); + return; + } + auto factory = factoryWrapper->GetFactory(); + if (!factory) { + SetError("Internal error"); + return; + } + + stream_ = factory->CreateLocalMediaStream(rtc::CreateRandomUuid()); + if (!stream_) { + SetError("Failed to create media stream"); + return; + } + + if (!audioConstraints_.IsNull()) { + cricket::AudioOptions options; + CopyConstraintsIntoAudioOptions(audioConstraints_, options); + + auto audioSource = factoryWrapper->GetFactory()->CreateAudioSource(options); + if (!audioSource) { + SetError("Failed to create audio source"); + return; + } + auto audioTrack = factoryWrapper->GetFactory()->CreateAudioTrack(rtc::CreateRandomUuid(), audioSource.get()); + if (!audioTrack) { + SetError("Failed to create audio track"); + return; + } + + stream_->AddTrack(audioTrack); + } + + if (!videoConstraints_.IsNull()) { + // default size + int width = 720; + int height = 1080; + GetScreenCaptureConstraints(videoConstraints_, width, height); + + video::VideoProfile profile; + profile.resolution.width = width; + profile.resolution.height = height; + profile.format = video::PixelFormat::RGBA; + auto capturer = DesktopCapturer::Create(profile); + if (!capturer) { + SetError("Failed to create camera capturer"); + return; + } + + auto videoSource = OhosVideoTrackSource::Create( + std::move(capturer), factoryWrapper->GetSignalingThread(), EglEnv::GetDefault().GetContext()); + if (!videoSource) { + SetError("Failed to create video source"); + return; + } + + auto videoTrack = factoryWrapper->GetFactory()->CreateVideoTrack(videoSource, rtc::CreateRandomUuid()); + if (!videoTrack) { + SetError("Failed to create video track"); + return; + } + + stream_->AddTrack(videoTrack); + } +} + +void AsyncWorkerGetDisplayMedia::OnOK() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto mediaStream = NapiMediaStream::NewInstance(Env(), stream_); + if (Env().IsExceptionPending()) { + deferred_.Reject(Env().GetAndClearPendingException().Value()); + return; + } + + deferred_.Resolve(mediaStream); +} + +void AsyncWorkerGetDisplayMedia::OnError(const Napi::Error& e) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + deferred_.Reject(e.Value()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.h b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.h new file mode 100644 index 0000000000000000000000000000000000000000..7ac04ed58a92c7bc14fad79128d4039eade34c02 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_display_media.h @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ASYNC_WORKER_GET_DISPLAY_MEDIA_H +#define WEBRTC_ASYNC_WORKER_GET_DISPLAY_MEDIA_H + + +#include "../user_media/media_constraints.h" + +#include "napi.h" + +#include "api/media_stream_interface.h" + +namespace webrtc { + +class AsyncWorkerGetDisplayMedia : public Napi::AsyncWorker { +public: + static AsyncWorkerGetDisplayMedia* Create(Napi::Env env, const char* resourceName); + + Napi::Promise GetPromise() + { + return deferred_.Promise(); + } + + Napi::Promise::Deferred GetDeferred() + { + return deferred_; + } + + void Start(MediaTrackConstraints audio, MediaTrackConstraints video); + +protected: + AsyncWorkerGetDisplayMedia(Napi::Env env, const char* resourceName); + + void Execute() override; + void OnOK() override; + void OnError(const Napi::Error& e) override; + +private: + Napi::Promise::Deferred deferred_; + + MediaTrackConstraints audioConstraints_; + MediaTrackConstraints videoConstraints_; + rtc::scoped_refptr stream_; +}; + +} // namespace webrtc + +#endif //WEBRTC_ASYNC_WORKER_GET_DISPLAY_MEDIA_H diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.cpp b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b143878ae0b4c2154b0a8c2a5c479230140aea02 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.cpp @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "async_worker_get_stats.h" + +namespace webrtc { + +using namespace Napi; + +const char kAttributeNameId[] = "id"; +const char kAttributeNameType[] = "type"; +const char kAttributeNameTimestamp[] = "timestamp"; + +class NapiMap : public Napi::Object { +public: + NapiMap(napi_env env, napi_value value) : Object(env, value) {} + + static NapiMap Create(Napi::Env env) + { + return env.Global().Get("Map").As().New({}).As(); + } + + void Set(Napi::Value key, Napi::Value value) + { + Get("set").As().Call(*this, {key, value}); + } +}; + +class GetStatsCallback : public RTCStatsCollectorCallback { +public: + explicit GetStatsCallback(AsyncWorkerGetStats* asyncWorker) : asyncWorker_(asyncWorker) {} + +protected: + void OnStatsDelivered(const rtc::scoped_refptr& report) override + { + if (!asyncWorker_) { + return; + } + + asyncWorker_->SetReport(report); + asyncWorker_->Queue(); + asyncWorker_ = nullptr; + } + +private: + AsyncWorkerGetStats* asyncWorker_; +}; + +AsyncWorkerGetStats* AsyncWorkerGetStats::Create(Napi::Env env, const char* resourceName) +{ + auto asyncWorker = new AsyncWorkerGetStats(env, resourceName); + asyncWorker->callback_ = rtc::make_ref_counted(asyncWorker); + return asyncWorker; +} + +AsyncWorkerGetStats::AsyncWorkerGetStats(Napi::Env env, const char* resourceName) + : AsyncWorker(env, resourceName), deferred_(Napi::Promise::Deferred::New(env)), callback_() +{ +} + +rtc::scoped_refptr AsyncWorkerGetStats::GetCallback() const +{ + return callback_; +} + +void AsyncWorkerGetStats::SetReport(const rtc::scoped_refptr& report) +{ + report_ = report; +} + +void AsyncWorkerGetStats::Execute() +{ + // do nothing +} + +void AsyncWorkerGetStats::OnOK() +{ + auto jsStatsReportObj = Napi::Object::New(Env()); + auto jsStatsMap = NapiMap::Create(Env()); + + if (report_) { + for (auto it = report_->begin(); it != report_->end(); it++) { + auto jsStats = Napi::Object::New(Env()); + jsStats.Set(kAttributeNameId, Napi::String::New(Env(), it->id())); + jsStats.Set(kAttributeNameType, Napi::String::New(Env(), it->type())); + jsStats.Set(kAttributeNameTimestamp, Napi::Number::New(Env(), it->timestamp().ms())); + + for (const auto& member : it->Members()) { + if (!member || !member->is_defined()) { + continue; + } + + switch (member->type()) { + case RTCStatsMemberInterface::kBool: + jsStats.Set( + member->name(), Napi::Boolean::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kInt32: + jsStats.Set( + member->name(), Napi::Number::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kUint32: + jsStats.Set( + member->name(), Napi::Number::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kInt64: + jsStats.Set( + member->name(), Napi::Number::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kUint64: + jsStats.Set( + member->name(), Napi::Number::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kDouble: + jsStats.Set( + member->name(), Napi::Number::New(Env(), *member->cast_to>())); + break; + case RTCStatsMemberInterface::kString: + jsStats.Set(member->name(), Napi::String::New(Env(), member->ValueToString())); + break; + default: + jsStats.Set(member->name(), Napi::String::New(Env(), member->ValueToJson())); + break; + } + } + + jsStatsMap.Set(Napi::String::New(Env(), it->id()), jsStats); + } + } + + jsStatsReportObj.Set("stats", jsStatsMap); + deferred_.Resolve(jsStatsReportObj); +} + +void AsyncWorkerGetStats::OnError(const Napi::Error& e) +{ + deferred_.Reject(e.Value()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.h b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.h new file mode 100644 index 0000000000000000000000000000000000000000..08b72f69c6bd3453180724cf5c2472b9b87c2ad0 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_stats.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ASYNC_WORKER_GET_STATS_H +#define WEBRTC_ASYNC_WORKER_GET_STATS_H + +#include "napi.h" + +#include "api/peer_connection_interface.h" + +namespace webrtc { + +class AsyncWorkerGetStats : public Napi::AsyncWorker { +public: + static AsyncWorkerGetStats* Create(Napi::Env env, const char* resourceName); + + Napi::Promise GetPromise() + { + return deferred_.Promise(); + } + + rtc::scoped_refptr GetCallback() const; + +protected: + AsyncWorkerGetStats(Napi::Env env, const char* resourceName); + + friend class GetStatsCallback; + void SetReport(const rtc::scoped_refptr& report); + +protected: + void Execute() override; + void OnOK() override; + void OnError(const Napi::Error& e) override; + +private: + Napi::Promise::Deferred deferred_; + rtc::scoped_refptr callback_; + rtc::scoped_refptr report_; +}; + +} // namespace webrtc + +#endif // WEBRTC_ASYNC_WORKER_GET_STATS_H diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.cpp b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90429a8dcca56814979cd3cda1cfb3f126b91dca --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.cpp @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "async_worker_get_user_media.h" +#include "../peer_connection_factory.h" +#include "../media_stream.h" +#include "../media_stream_track.h" +#include "../camera/camera_enumerator.h" +#include "../camera/camera_capturer.h" +#include "../video/video_track_source.h" +#include "../user_media/media_constraints_util.h" +#include "../render/egl_env.h" + +#include "rtc_base/logging.h" +#include "rtc_base/helpers.h" + +namespace webrtc { + +AsyncWorkerGetUserMedia* AsyncWorkerGetUserMedia::Create(Napi::Env env, const char* resourceName) +{ + auto asyncWorker = new AsyncWorkerGetUserMedia(env, resourceName); + return asyncWorker; +} + +AsyncWorkerGetUserMedia::AsyncWorkerGetUserMedia(Napi::Env env, const char* resourceName) + : AsyncWorker(env, resourceName), deferred_(Napi::Promise::Deferred::New(env)) +{ +} + +void AsyncWorkerGetUserMedia::Start(MediaTrackConstraints audio, MediaTrackConstraints video) +{ + audioConstraints_ = std::move(audio); + videoConstraints_ = std::move(video); + Queue(); +} + +void AsyncWorkerGetUserMedia::Execute() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto factoryWrapper = PeerConnectionFactoryWrapper::GetDefault(); + if (!factoryWrapper) { + SetError("Internal error"); + return; + } + auto factory = factoryWrapper->GetFactory(); + if (!factory) { + SetError("Internal error"); + return; + } + + stream_ = factory->CreateLocalMediaStream(rtc::CreateRandomUuid()); + if (!stream_) { + SetError("Failed to create media stream"); + return; + } + + if (!audioConstraints_.IsNull()) { + cricket::AudioOptions options; + CopyConstraintsIntoAudioOptions(audioConstraints_, options); + + auto audioSource = factoryWrapper->GetFactory()->CreateAudioSource(options); + if (!audioSource) { + SetError("Failed to create audio source"); + return; + } + auto audioTrack = factoryWrapper->GetFactory()->CreateAudioTrack(rtc::CreateRandomUuid(), audioSource.get()); + if (!audioTrack) { + SetError("Failed to create audio track"); + return; + } + + stream_->AddTrack(audioTrack); + } + + if (!videoConstraints_.IsNull()) { + auto cameraDevices = CameraEnumerator::GetDevices(); + + CameraCaptureSettings selectedSetting; + std::string failedConstraintName; + if (SelectSettingsForVideo( + cameraDevices, videoConstraints_, 640, 480, 30, selectedSetting, failedConstraintName)) + { + RTC_DLOG(LS_INFO) << "Selected camera device: " << selectedSetting.deviceId + << ", resolution = " << selectedSetting.profile.resolution.width << "x" + << selectedSetting.profile.resolution.height + << ", format = " << selectedSetting.profile.format + << ", framerate = " << selectedSetting.profile.frameRateRange.min << "-" + << selectedSetting.profile.frameRateRange.max; + } else { + RTC_LOG(LS_ERROR) << "Failed to select settings for video: " << failedConstraintName; + SetError(std::string("Unsatisfied constraint: ") + failedConstraintName); + } + + auto capturer = CameraCapturer::Create(selectedSetting.deviceId, selectedSetting.profile); + if (!capturer) { + SetError("Failed to create camera capturer"); + return; + } + + auto videoSource = OhosVideoTrackSource::Create( + std::move(capturer), factoryWrapper->GetSignalingThread(), EglEnv::GetDefault().GetContext()); + if (!videoSource) { + SetError("Failed to create video source"); + return; + } + + auto videoTrack = factoryWrapper->GetFactory()->CreateVideoTrack(videoSource, rtc::CreateRandomUuid()); + if (!videoTrack) { + SetError("Failed to create video track"); + return; + } + + stream_->AddTrack(videoTrack); + } +} + +void AsyncWorkerGetUserMedia::OnOK() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto mediaStream = NapiMediaStream::NewInstance(Env(), stream_); + if (Env().IsExceptionPending()) { + deferred_.Reject(Env().GetAndClearPendingException().Value()); + return; + } + + deferred_.Resolve(mediaStream); +} + +void AsyncWorkerGetUserMedia::OnError(const Napi::Error& e) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + deferred_.Reject(e.Value()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.h b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.h new file mode 100644 index 0000000000000000000000000000000000000000..bb9c0e3d4117fd14dc74a2271eb550d4a2918e7e --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/async_work/async_worker_get_user_media.h @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ASYNC_WORKER_GET_USER_MEDIA_H +#define WEBRTC_ASYNC_WORKER_GET_USER_MEDIA_H + +#include "../user_media/media_constraints.h" + +#include "napi.h" + +#include "api/media_stream_interface.h" + +namespace webrtc { + +class AsyncWorkerGetUserMedia : public Napi::AsyncWorker { +public: + static AsyncWorkerGetUserMedia* Create(Napi::Env env, const char* resourceName); + + Napi::Promise GetPromise() + { + return deferred_.Promise(); + } + + Napi::Promise::Deferred GetDeferred() + { + return deferred_; + } + + void Start(MediaTrackConstraints audio, MediaTrackConstraints video); + +protected: + AsyncWorkerGetUserMedia(Napi::Env env, const char* resourceName); + + void Execute() override; + void OnOK() override; + void OnError(const Napi::Error& e) override; + +private: + Napi::Promise::Deferred deferred_; + + MediaTrackConstraints audioConstraints_; + MediaTrackConstraints videoConstraints_; + rtc::scoped_refptr stream_; +}; + +} // namespace webrtc + +#endif // WEBRTC_ASYNC_WORKER_GET_USER_MEDIA_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.cpp b/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a6de67b04151534520d0750babe3aafc08bb0a4f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.cpp @@ -0,0 +1,393 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio_capturer.h" +#include "audio_common.h" + +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/logging.h" +#include +#include + +namespace webrtc { + +using namespace Napi; + +std::unique_ptr +AudioCapturer::Create(int source, int format, int sampleRate, bool useStereoInput, bool useLowLatency) +{ + return std::unique_ptr(new AudioCapturer(source, format, sampleRate, useStereoInput, useLowLatency)); +} + +int32_t AudioCapturer::OnReadData1(OH_AudioCapturer* stream, void* userData, void* buffer, int32_t length) +{ + AudioCapturer* self = reinterpret_cast(userData); + return self->OnReadData(stream, buffer, length); +} + +int32_t AudioCapturer::OnStreamEvent1(OH_AudioCapturer* stream, void* userData, OH_AudioStream_Event event) +{ + AudioCapturer* self = reinterpret_cast(userData); + return self->OnStreamEvent(stream, event); +} + +int32_t AudioCapturer::OnInterruptEvent1( + OH_AudioCapturer* stream, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) +{ + AudioCapturer* self = reinterpret_cast(userData); + return self->OnInterruptEvent(stream, type, hint); +} + +int32_t AudioCapturer::OnError1(OH_AudioCapturer* stream, void* userData, OH_AudioStream_Result error) +{ + AudioCapturer* self = reinterpret_cast(userData); + return self->OnError(stream, error); +} + +AudioCapturer::AudioCapturer(int source, int format, int sampleRate, bool useStereoInput, bool useLowLatency) + : audioSource_(source), audioFormat_(format), sampleRate_(sampleRate), channelCount_(useStereoInput ? 2 : 1), + useLowLatency_(useLowLatency) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // Detach from this thread since construction is allowed to happen on a different thread. + threadChecker_.Detach(); +} + +AudioCapturer::~AudioCapturer() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + Terminate(); +} + +int32_t AudioCapturer::Init() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + return 0; +} + +int32_t AudioCapturer::Terminate() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + StopRecording(); + threadChecker_.Detach(); + + return 0; +} + +int32_t AudioCapturer::InitRecording() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (initialized_) { + // Already initialized. + return 0; + } + RTC_DCHECK(!recording_); + + OH_AudioStreamBuilder* builder = nullptr; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_CAPTURER), + NotifyError(AudioErrorType::INIT, "system error"), -1); + + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetCapturerInfo(builder, static_cast(audioSource_)), + NotifyError(AudioErrorType::INIT, "failed to set capturer info"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetSamplingRate(builder, sampleRate_), + NotifyError(AudioErrorType::INIT, "failed to set sample rate"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetChannelCount(builder, channelCount_), + NotifyError(AudioErrorType::INIT, "failed to set channel count"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetSampleFormat(builder, (OH_AudioStream_SampleFormat)audioFormat_), + NotifyError(AudioErrorType::INIT, "failed to set sample format"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetLatencyMode( + builder, useLowLatency_ ? AUDIOSTREAM_LATENCY_MODE_FAST : AUDIOSTREAM_LATENCY_MODE_NORMAL), + NotifyError(AudioErrorType::INIT, "failed to set latency mode"), -1); + + OH_AudioCapturer_Callbacks callbacks; + callbacks.OH_AudioCapturer_OnReadData = OnReadData1; + callbacks.OH_AudioCapturer_OnStreamEvent = OnStreamEvent1; + callbacks.OH_AudioCapturer_OnInterruptEvent = OnInterruptEvent1; + callbacks.OH_AudioCapturer_OnError = OnError1; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetCapturerCallback(builder, callbacks, this), + NotifyError(AudioErrorType::INIT, "failed to set capture callback"), -1); + + OH_AudioCapturer* stream = nullptr; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_GenerateCapturer(builder, &stream), NotifyError(AudioErrorType::INIT, "system error"), + -1); + + // check configuration + int32_t rate; + OH_RESULT_CHECK( + OH_AudioCapturer_GetSamplingRate(stream, &rate), NotifyError(AudioErrorType::INIT, "failed to get sample rate"), + -1); + if (rate != sampleRate_) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested sample rate"; + NotifyError(AudioErrorType::INIT, "unmatched sample rate"); + return -1; + } + + int32_t channelCount; + OH_RESULT_CHECK( + OH_AudioCapturer_GetChannelCount(stream, &channelCount), + NotifyError(AudioErrorType::INIT, "failed to get channel count"), -1); + if (channelCount != static_cast(channelCount_)) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested channel count"; + NotifyError(AudioErrorType::INIT, "unmatched channel count"); + return -1; + } + + OH_AudioStream_SampleFormat sampleFormat; + OH_RESULT_CHECK( + OH_AudioCapturer_GetSampleFormat(stream, &sampleFormat), + NotifyError(AudioErrorType::INIT, "failed to get sample format"), -1); + if (sampleFormat != AUDIOSTREAM_SAMPLE_S16LE) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested format"; + NotifyError(AudioErrorType::INIT, "unmatched sample format"); + return -1; + } + + capturer_ = stream; + initialized_ = true; + + RTC_DLOG(LS_VERBOSE) << "current state: " << StateToString(GetCurrentState()); + + return 0; +} + +bool AudioCapturer::RecordingIsInitialized() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " initialized_ = " << initialized_; + return initialized_; +} + +int32_t AudioCapturer::StartRecording() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (recording_) { + // Already recording. + return 0; + } + + if (!initialized_) { + RTC_DLOG(LS_WARNING) << "Recording can not start since InitRecording must succeed first"; + return 0; + } + + if (fineAudioBuffer_) { + fineAudioBuffer_->ResetPlayout(); + } + + OH_AudioStream_State state = GetCurrentState(); + if (state != AUDIOSTREAM_STATE_PREPARED) { + RTC_LOG(LS_ERROR) << "Invalid state: " << StateToString(state); + NotifyError(AudioErrorType::START_STATE_MISMATCH, std::string("invalid state: ") + StateToString(state)); + return -1; + } + + OH_RESULT_CHECK( + OH_AudioCapturer_Start(capturer_), NotifyError(AudioErrorType::START_EXCEPTION, "system error"), -1); + RTC_DLOG(LS_VERBOSE) << "current state: " << StateToString(GetCurrentState()); + recording_ = true; + + NotifyStateChange(AudioStateType::START); + + return 0; +} + +int32_t AudioCapturer::StopRecording() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (!initialized_ || !recording_) { + return 0; + } + + OH_RESULT_CHECK(OH_AudioCapturer_Stop(capturer_), -1); + OH_RESULT_CHECK(OH_AudioCapturer_Release(capturer_), -1); + + initialized_ = false; + recording_ = false; + + NotifyStateChange(AudioStateType::STOP); + + return 0; +} + +bool AudioCapturer::Recording() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " recording_ = " << recording_; + return recording_; +} + +void AudioCapturer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " audioBuffer = " << audioBuffer; + + RTC_DCHECK(threadChecker_.IsCurrent()); + audioDeviceBuffer_ = audioBuffer; + audioDeviceBuffer_->SetRecordingSampleRate(sampleRate_); + audioDeviceBuffer_->SetRecordingChannels(channelCount_); + fineAudioBuffer_ = std::make_unique(audioDeviceBuffer_); +} + +int32_t AudioCapturer::MicrophoneMuteIsAvailable(bool* available) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = true; + return 0; +} + +int32_t AudioCapturer::SetMicrophoneMute(bool mute) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + microphoneMute_ = mute; + return 0; +} + +int32_t AudioCapturer::MicrophoneMute(bool* enabled) const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *enabled = microphoneMute_; + return 0; +} + +void AudioCapturer::RegisterObserver(Observer* obs) +{ + if (!obs) { + return; + } + + observers_.insert(obs); +} + +void AudioCapturer::UnregisterObserver(Observer* obs) +{ + if (!obs) { + return; + } + + observers_.erase(obs); +} + +int32_t AudioCapturer::OnReadData(OH_AudioCapturer* stream, void* buffer, int32_t length) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " bufferLen=" << length; + + (void)stream; + + int64_t position; + int64_t timestamp; + OH_RESULT_CHECK(OH_AudioCapturer_GetTimestamp(capturer_, CLOCK_MONOTONIC, &position, ×tamp), 0); + + double latencyMillis = static_cast(rtc::TimeNanos() - timestamp) / rtc::kNumNanosecsPerMillisec; + RTC_DLOG(LS_VERBOSE) << "latencyMillis=" << latencyMillis; + captureDelayMs_ = static_cast(std::ceil(latencyMillis)); + + if (microphoneMute_) { + std::memset(buffer, 0, length); + } + + fineAudioBuffer_->DeliverRecordedData( + rtc::MakeArrayView(static_cast(buffer), length / sizeof(int16_t)), captureDelayMs_); + + auto data = new rtc::CopyOnWriteBuffer((uint8_t*)buffer, length); + NotifySamplesReady(sampleRate_, audioFormat_, channelCount_, data); + + return -1; +} + +int32_t AudioCapturer::OnStreamEvent(OH_AudioCapturer* stream, OH_AudioStream_Event event) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " event=" << event; + + (void)stream; + + return 0; +} + +int32_t +AudioCapturer::OnInterruptEvent(OH_AudioCapturer* stream, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " type=" << type; + + (void)stream; + (void)hint; + + return 0; +} + +int32_t AudioCapturer::OnError(OH_AudioCapturer* stream, OH_AudioStream_Result error) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " error=" << error; + + (void)stream; + + NotifyError(AudioErrorType::GENERAL, "system error"); + + return 0; +} + +OH_AudioStream_State AudioCapturer::GetCurrentState() const +{ + OH_AudioStream_State state = AUDIOSTREAM_STATE_INVALID; + if (capturer_) { + OH_AudioCapturer_GetCurrentState(capturer_, &state); + } + + return state; +} + +void AudioCapturer::NotifyError(AudioErrorType error, const std::string& message) +{ + for (auto& obs : observers_) { + obs->OnAudioInputError(error, message); + } +} + +void AudioCapturer::NotifyStateChange(AudioStateType state) +{ + for (auto& obs : observers_) { + obs->OnAudioInputStateChange(state); + } +} + +void AudioCapturer::NotifySamplesReady( + int32_t sampleRate, int32_t format, int32_t channelCount, rtc::CopyOnWriteBuffer* data) +{ + for (auto& obs : observers_) { + obs->OnAudioInputSamplesReady(sampleRate, format, channelCount, data); + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.h new file mode 100644 index 0000000000000000000000000000000000000000..f678ee6369f8ebc932c00269ca53e0fbf7aba008 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_capturer.h @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_CAPTURER_H +#define WEBRTC_AUDIO_CAPTURER_H + +#include "audio_device_module.h" + +#include +#include + +#include +#include +#include + +#include "napi.h" + +#include "api/sequence_checker.h" +#include "modules/audio_device/audio_device_buffer.h" +#include "modules/audio_device/fine_audio_buffer.h" + +namespace webrtc { + +class AudioCapturer : public AudioInput { +public: + static std::unique_ptr + Create(int32_t source, int32_t format, int32_t sampleRate, bool useStereoInput, bool useLowLatency); + + ~AudioCapturer() override; + + int32_t Init() override; + int32_t Terminate() override; + + int32_t InitRecording() override; + bool RecordingIsInitialized() const override; + + int32_t StartRecording() override; + int32_t StopRecording() override; + bool Recording() const override; + + void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override; + + int32_t MicrophoneMuteIsAvailable(bool* available) override; + int32_t SetMicrophoneMute(bool mute) override; + int32_t MicrophoneMute(bool* enabled) const override; + + void RegisterObserver(Observer* obs) override; + void UnregisterObserver(Observer* obs) override; + +protected: + AudioCapturer(int32_t source, int32_t format, int32_t sampleRate, bool useStereoInput, bool useLowLatency); + + static int32_t OnReadData1(OH_AudioCapturer* stream, void* userData, void* buffer, int32_t lenth); + static int32_t OnStreamEvent1(OH_AudioCapturer* capturer, void* userData, OH_AudioStream_Event event); + static int32_t OnInterruptEvent1( + OH_AudioCapturer* stream, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint); + static int32_t OnError1(OH_AudioCapturer* stream, void* userData, OH_AudioStream_Result error); + + int32_t OnReadData(OH_AudioCapturer* stream, void* buffer, int32_t lenth); + int32_t OnStreamEvent(OH_AudioCapturer* stream, OH_AudioStream_Event event); + int32_t OnInterruptEvent(OH_AudioCapturer* stream, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint); + int32_t OnError(OH_AudioCapturer* stream, OH_AudioStream_Result error); + + OH_AudioStream_State GetCurrentState() const; + + void NotifyError(AudioErrorType error, const std::string& message); + void NotifyStateChange(AudioStateType state); + void NotifySamplesReady(int32_t sampleRate, int32_t format, int32_t channelCount, rtc::CopyOnWriteBuffer* data); + +private: + SequenceChecker threadChecker_; + + const int32_t audioSource_; + const int32_t audioFormat_; + const int32_t sampleRate_; + const int32_t channelCount_; + const bool useLowLatency_; + + // Delay estimate of the input + std::atomic captureDelayMs_{0}; + + // Sets all recorded samples to zero if `microphoneMute_` is true, i.e., ensures that + // the microphone is muted. + std::atomic microphoneMute_{false}; + + bool initialized_{false}; + bool recording_{false}; + + AudioDeviceBuffer* audioDeviceBuffer_{nullptr}; + std::unique_ptr fineAudioBuffer_; + + OH_AudioCapturer* capturer_{nullptr}; + + std::set observers_; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_CAPTURER_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_common.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_common.h new file mode 100644 index 0000000000000000000000000000000000000000..da2c0838b26b19e5d02f53aac10f74b28cc83727 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_common.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_COMMON_H +#define WEBRTC_AUDIO_COMMON_H + +#include +#include +#include + +#include + +namespace webrtc { + +const int kDefaultSampleRate = 44100; + +const int kLowLatencyModeDelayEstimateInMilliseconds = 25; +const int kHighLatencyModeDelayEstimateInMilliseconds = 75; + +#define OH_RESULT_CHECK(op, ...) \ + do { \ + OH_AudioStream_Result result = (op); \ + if (result != AUDIOSTREAM_SUCCESS) { \ + RTC_LOG(LS_ERROR) << #op << ": " << result; \ + return __VA_ARGS__; \ + } \ + } while (0) + +inline const char* StateToString(OH_AudioStream_State state) +{ + switch (state) { + case AUDIOSTREAM_STATE_INVALID: + return "INVALID"; + case AUDIOSTREAM_STATE_PREPARED: + return "PREPARED"; + case AUDIOSTREAM_STATE_RUNNING: + return "RUNNING"; + case AUDIOSTREAM_STATE_STOPPED: + return "STOPPED"; + case AUDIOSTREAM_STATE_RELEASED: + return "RELEASED"; + case AUDIOSTREAM_STATE_PAUSED: + return "PAUSED"; + default: + return "UNKNOWN"; + } +} + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_COMMON_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.cpp b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16bb9bd958755583dbf01ee1512ff7c177ff2b6c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.cpp @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio_device_enumerator.h" + +#include +#include + +#include "rtc_base/logging.h" + +namespace webrtc { + +AudioDeviceRole NativeAudioDeviceRoleToAudioDeviceRole(OH_AudioDevice_Role role) +{ + switch (role) { + case AUDIO_DEVICE_ROLE_INPUT: + return AudioDeviceRole::Input; + case AUDIO_DEVICE_ROLE_OUTPUT: + return AudioDeviceRole::Output; + default: + return AudioDeviceRole::Unknown; + } +} + +std::string AudioDeviceTypeToString(OH_AudioDevice_Type type) +{ + switch (type) { + case AUDIO_DEVICE_TYPE_INVALID: + return "Invalid"; + case AUDIO_DEVICE_TYPE_EARPIECE: + return "Earpiece"; + case AUDIO_DEVICE_TYPE_SPEAKER: + return "Speaker"; + case AUDIO_DEVICE_TYPE_WIRED_HEADSET: + return "Headset"; + case AUDIO_DEVICE_TYPE_WIRED_HEADPHONES: + return "Wired headphones"; + case AUDIO_DEVICE_TYPE_BLUETOOTH_SCO: + return "Bluetooth SCO"; + case AUDIO_DEVICE_TYPE_BLUETOOTH_A2DP: + return "Bluetooth A2DP"; + case AUDIO_DEVICE_TYPE_MIC: + return "Microphone"; + case AUDIO_DEVICE_TYPE_USB_HEADSET: + return "USB headset"; + case AUDIO_DEVICE_TYPE_DISPLAY_PORT: + return "Display port"; + case AUDIO_DEVICE_TYPE_REMOTE_CAST: + return "Remote cast"; + case AUDIO_DEVICE_TYPE_DEFAULT: + return "Default"; + default: + break; + } + return "Unspecified"; +} + +std::vector AudioDeviceEnumerator::GetDevices() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::vector result; + + OH_AudioRoutingManager* audioRoutingManager = nullptr; + OH_AudioCommon_Result ret = OH_AudioManager_GetAudioRoutingManager(&audioRoutingManager); + if (ret != AUDIOCOMMON_RESULT_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to get audio routing manager: " << ret; + return result; + } + + OH_AudioDeviceDescriptorArray* audioDeviceDescriptorArray = nullptr; + ret = OH_AudioRoutingManager_GetDevices(audioRoutingManager, AUDIO_DEVICE_FLAG_ALL, &audioDeviceDescriptorArray); + if (ret != AUDIOCOMMON_RESULT_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to get audio devices: " << ret; + return result; + } + + RTC_DLOG(LS_VERBOSE) << "audio devices: " << audioDeviceDescriptorArray->size; + + for (uint32_t index = 0; index < audioDeviceDescriptorArray->size; index++) { + AudioDeviceInfo device; + device.groupId = "default"; + + OH_AudioDeviceDescriptor* descriptor = audioDeviceDescriptorArray->descriptors[index]; + + char* address = nullptr; + ret = OH_AudioDeviceDescriptor_GetDeviceAddress(descriptor, &address); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device mac address: " << address; + } + + uint32_t deviceId = 0; + ret = OH_AudioDeviceDescriptor_GetDeviceId(descriptor, &deviceId); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device id: " << deviceId; + device.deviceId = std::to_string(deviceId); + } + + char* name = nullptr; + ret = OH_AudioDeviceDescriptor_GetDeviceName(descriptor, &name); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device name: " << name; + device.label = name; + } + + char* displayName = nullptr; + ret = OH_AudioDeviceDescriptor_GetDeviceDisplayName(descriptor, &displayName); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device display name: " << displayName; + } + + OH_AudioDevice_Role role = AUDIO_DEVICE_ROLE_INPUT; + ret = OH_AudioDeviceDescriptor_GetDeviceRole(descriptor, &role); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device role: " << role; + device.role = NativeAudioDeviceRoleToAudioDeviceRole(role); + } + + OH_AudioDevice_Type type = AUDIO_DEVICE_TYPE_INVALID; + ret = OH_AudioDeviceDescriptor_GetDeviceType(descriptor, &type); + if (ret == AUDIOCOMMON_RESULT_SUCCESS) { + RTC_DLOG(LS_VERBOSE) << "audio device type: " << type; + } + + if (device.label.length() == 0) { + device.label = AudioDeviceTypeToString(type) + " (" + device.deviceId + ")"; + } + + result.push_back(device); + } + + OH_AudioRoutingManager_ReleaseDevices(audioRoutingManager, audioDeviceDescriptorArray); + + return result; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.h new file mode 100644 index 0000000000000000000000000000000000000000..67742a869e236986ea7fc96d09def2bb71f3c09b --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_enumerator.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_DEVICE_ENUMERATOR_H +#define WEBRTC_AUDIO_DEVICE_ENUMERATOR_H + +#include "audio_device_info.h" + +#include +#include + +namespace webrtc { + +class AudioDeviceEnumerator { +public: + static std::vector GetDevices(); +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_DEVICE_ENUMERATOR_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_info.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_info.h new file mode 100644 index 0000000000000000000000000000000000000000..ff42b9aaa8359bf45cdc2af83b403338416550a5 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_info.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_DEVICE_INFO_H +#define WEBRTC_AUDIO_DEVICE_INFO_H + +#include + +namespace webrtc { + +enum class AudioDeviceRole { + Unknown = -1, + Input = 1, + Output = 2 +}; + +struct AudioDeviceInfo { + std::string deviceId; + std::string groupId; + std::string label; + AudioDeviceRole role; + // OH_AudioDevice_Type type; +}; + +} // namespace webrtc + +#endif //WEBRTC_AUDIO_DEVICE_INFO_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.cpp b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..991af79e76c427df04779b90e25016a2aee111f7 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.cpp @@ -0,0 +1,1060 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio_device_module.h" +#include "audio_common.h" +#include "audio_capturer.h" +#include "audio_renderer.h" + +#include +#include + +#include "api/sequence_checker.h" +#include "api/make_ref_counted.h" +#include "api/task_queue/default_task_queue_factory.h" +#include "modules/audio_device/audio_device_buffer.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "AudioDeviceModule"; + +const char kAttributeNameOnCapturerError[] = "oncapturererror"; +const char kAttributeNameOnCapturerStateChange[] = "oncapturerstatechange"; +const char kAttributeNameOnCapturerSamplesReady[] = "oncapturersamplesready"; +const char kAttributeNameOnRendererError[] = "onrenderererror"; +const char kAttributeNameOnRendererStateChange[] = "onrendererstatechange"; + +const char kMethodNameSetSpeakerMute[] = "setSpeakerMute"; +const char kMethodNameSetMicrophoneMute[] = "setMicrophoneMute"; +const char kMethodNameSetNoiseSuppressorEnabled[] = "setNoiseSuppressorEnabled"; +const char kMethodNameIsBuiltInAcousticEchoCancelerSupported[] = "isBuiltInAcousticEchoCancelerSupported"; +const char kMethodNameIsBuiltInNoiseSuppressorSupported[] = "isBuiltInNoiseSuppressorSupported"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEventNameCapturerError[] = "capturererror"; +const char kEventNameCapturerStateChange[] = "capturerstatechange"; +const char kEventNameCapturerSamplesReady[] = "capturersamplesready"; +const char kEventNameRendererError[] = "renderererror"; +const char kEventNameRendererStateChange[] = "rendererstatechange"; + +const char kAttributeNameAudioSource[] = "audioSource"; +const char kAttributeNameAudioFormat[] = "audioFormat"; +const char kAttributeNameInputSampleRate[] = "inputSampleRate"; +const char kAttributeNameUseStereoInput[] = "useStereoInput"; +const char kAttributeNameOutputSampleRate[] = "outputSampleRate"; +const char kAttributeNameUseStereoOutput[] = "useStereoOutput"; +const char kAttributeNameRendererUsage[] = "rendererUsage"; +const char kAttributeNameUseLowLatency[] = "useLowLatency"; +const char kAttributeNameUseHardwareAcousticEchoCanceler[] = "useHardwareAcousticEchoCanceler"; +const char kAttributeNameUseHardwareNoiseSuppressor[] = "useHardwareNoiseSuppressor"; + +const char kEnumAudioErrorTypeInit[] = "init"; +const char kEnumAudioErrorTypeStartException[] = "start-exception"; +const char kEnumAudioErrorTypeStartStateMismatch[] = "start-state-mismatch"; +const char kEnumAudioErrorTypeGeneral[] = "general"; + +const char kEnumAudioStateStart[] = "start"; +const char kEnumAudioStateStop[] = "stop"; + +const char* AudioErrorTypeToString(AudioErrorType type) +{ + switch (type) { + case AudioErrorType::INIT: + return kEnumAudioErrorTypeInit; + case AudioErrorType::START_EXCEPTION: + return kEnumAudioErrorTypeStartException; + case AudioErrorType::START_STATE_MISMATCH: + return kEnumAudioErrorTypeStartStateMismatch; + case AudioErrorType::GENERAL: + return kEnumAudioErrorTypeGeneral; + default: + break; + } + + return "unknown"; +} + +const char* AudioStateToString(AudioStateType state) +{ + switch (state) { + case AudioStateType::START: + return kEnumAudioStateStart; + case AudioStateType::STOP: + return kEnumAudioStateStop; + default: + break; + } + + return "unknown"; +} + +class OhosAudioDeviceModule : public AudioDeviceModule { +public: + enum class InitStatus { OK = 0, PLAYOUT_ERROR = 1, RECORDING_ERROR = 2, OTHER_ERROR = 3, NUM_STATUSES = 4 }; + + OhosAudioDeviceModule( + AudioDeviceModule::AudioLayer audioLayer, bool isStereoPlayoutSupported, bool isStereoRecordSupported, + std::unique_ptr audioInput, std::unique_ptr audioOutput) + : audioLayer_(audioLayer), isStereoPlayoutSupported_(isStereoPlayoutSupported), + isStereoRecordSupported_(isStereoRecordSupported), taskQueueFactory_(CreateDefaultTaskQueueFactory()), + input_(std::move(audioInput)), output_(std::move(audioOutput)), initialized_(false) + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(input_); + RTC_DCHECK(output_); + + threadChecker_.Detach(); + } + + ~OhosAudioDeviceModule() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + } + + int32_t ActiveAudioLayer(AudioDeviceModule::AudioLayer* audioLayer) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *audioLayer = audioLayer_; + return 0; + } + + int32_t RegisterAudioCallback(AudioTransport* audioCallback) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return audioDeviceBuffer_->RegisterAudioCallback(audioCallback); + } + + int32_t Init() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + + if (initialized_) { + return 0; + } + + audioDeviceBuffer_ = std::make_unique(taskQueueFactory_.get()); + AttachAudioBuffer(); + + InitStatus status; + if (output_->Init() != 0) { + status = InitStatus::PLAYOUT_ERROR; + } else if (input_->Init() != 0) { + output_->Terminate(); + status = InitStatus::RECORDING_ERROR; + } else { + initialized_ = true; + status = InitStatus::OK; + } + + if (status != InitStatus::OK) { + RTC_LOG(LS_ERROR) << "Audio device initialization failed."; + return -1; + } + + return 0; + } + + int32_t Terminate() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return 0; + } + + RTC_DCHECK(threadChecker_.IsCurrent()); + + int32_t err = input_->Terminate(); + err |= output_->Terminate(); + if (err != 0) { + RTC_LOG(LS_ERROR) << "error: " << err; + } + + initialized_ = false; + threadChecker_.Detach(); + audioDeviceBuffer_.reset(nullptr); + + return err; + } + + bool Initialized() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << ":" << initialized_; + + return initialized_; + } + + int16_t PlayoutDevices() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return 1; + } + + int16_t RecordingDevices() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return 1; + } + + int32_t PlayoutDeviceName(uint16_t index, char name[kAdmMaxDeviceNameSize], char guid[kAdmMaxGuidSize]) override + { + (void)index; + (void)name; + (void)guid; + RTC_CHECK_NOTREACHED(); + } + + int32_t RecordingDeviceName(uint16_t index, char name[kAdmMaxDeviceNameSize], char guid[kAdmMaxGuidSize]) override + { + (void)index; + (void)name; + (void)guid; + RTC_CHECK_NOTREACHED(); + } + + int32_t SetPlayoutDevice(uint16_t index) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << index << ")"; + + // no effect + return 0; + } + + int32_t SetPlayoutDevice(AudioDeviceModule::WindowsDeviceType device) override + { + (void)device; + RTC_CHECK_NOTREACHED(); + } + + int32_t SetRecordingDevice(uint16_t index) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << index << ")"; + + // no effect + return 0; + } + + int32_t SetRecordingDevice(AudioDeviceModule::WindowsDeviceType device) override + { + (void)device; + RTC_CHECK_NOTREACHED(); + } + + int32_t PlayoutIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = true; + return 0; + } + + int32_t InitPlayout() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + if (!initialized_) { + return -1; + } + + if (PlayoutIsInitialized()) { + return 0; + } + + int32_t result = output_->InitPlayout(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + return result; + } + + bool PlayoutIsInitialized() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return output_->PlayoutIsInitialized(); + } + + int32_t RecordingIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = true; + return 0; + } + + int32_t InitRecording() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return -1; + } + + if (RecordingIsInitialized()) { + return 0; + } + + int32_t result = input_->InitRecording(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + return result; + } + + bool RecordingIsInitialized() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return input_->RecordingIsInitialized(); + } + + int32_t StartPlayout() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return -1; + } + + if (Playing()) { + return 0; + } + + int32_t result = output_->StartPlayout(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + if (result == 0) { + // Only start playing the audio device buffer if starting the audio output succeeded. + audioDeviceBuffer_->StartPlayout(); + } + + return result; + } + + int32_t StopPlayout() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return -1; + } + + if (!Playing()) { + return 0; + } + + audioDeviceBuffer_->StopPlayout(); + int32_t result = output_->StopPlayout(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + return result; + } + + bool Playing() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return output_->Playing(); + } + + int32_t StartRecording() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return -1; + } + + if (Recording()) { + return 0; + } + + int32_t result = input_->StartRecording(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + if (result == 0) { + // Only start recording the audio device buffer if starting the audio input succeeded. + audioDeviceBuffer_->StartRecording(); + } + + return result; + } + + int32_t StopRecording() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + return -1; + } + + if (!Recording()) { + return 0; + } + + audioDeviceBuffer_->StopRecording(); + int32_t result = input_->StopRecording(); + RTC_DLOG(LS_VERBOSE) << "output: " << result; + + return result; + } + + bool Recording() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return input_->Recording(); + } + + int32_t InitSpeaker() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return initialized_ ? 0 : -1; + } + + bool SpeakerIsInitialized() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return initialized_; + } + + int32_t InitMicrophone() override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return initialized_ ? 0 : -1; + } + + bool MicrophoneIsInitialized() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return initialized_; + } + + int32_t SpeakerVolumeIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = false; + return 0; + } + + int32_t SetSpeakerVolume(uint32_t volume) override + { + (void)volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t SpeakerVolume(uint32_t* output_volume) const override + { + (void)output_volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MaxSpeakerVolume(uint32_t* output_max_volume) const override + { + (void)output_max_volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MinSpeakerVolume(uint32_t* output_min_volume) const override + { + (void)output_min_volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MicrophoneVolumeIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = false; + return -1; + } + + int32_t SetMicrophoneVolume(uint32_t volume) override + { + (void)volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MicrophoneVolume(uint32_t* volume) const override + { + (void)volume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MaxMicrophoneVolume(uint32_t* maxVolume) const override + { + (void)maxVolume; + RTC_CHECK_NOTREACHED(); + } + + int32_t MinMicrophoneVolume(uint32_t* minVolume) const override + { + (void)minVolume; + RTC_CHECK_NOTREACHED(); + } + + int32_t SpeakerMuteIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return output_->SpeakerMuteIsAvailable(available); + } + + int32_t SetSpeakerMute(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + + return output_->setSpeakerMute(enable); + } + + int32_t SpeakerMute(bool* enabled) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return output_->SpeakerMute(enabled); + } + + int32_t MicrophoneMuteIsAvailable(bool* available) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return input_->MicrophoneMuteIsAvailable(available); + } + + int32_t SetMicrophoneMute(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + + return input_->SetMicrophoneMute(enable); + } + + int32_t MicrophoneMute(bool* enabled) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return input_->MicrophoneMute(enabled); + } + + int32_t StereoPlayoutIsAvailable(bool* available) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = isStereoPlayoutSupported_; + RTC_DLOG(LS_VERBOSE) << "output: " << *available; + + return 0; + } + + int32_t SetStereoPlayout(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + + bool available = isStereoPlayoutSupported_; + if (enable != available) { + RTC_LOG(LS_WARNING) << "changing stereo playout not supported"; + return -1; + } + + return 0; + } + + int32_t StereoPlayout(bool* enabled) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *enabled = isStereoPlayoutSupported_; + RTC_DLOG(LS_VERBOSE) << "output: " << *enabled; + + return 0; + } + + int32_t StereoRecordingIsAvailable(bool* available) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = isStereoRecordSupported_; + RTC_DLOG(LS_VERBOSE) << "output: " << *available; + + return 0; + } + + int32_t SetStereoRecording(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + + bool available = isStereoRecordSupported_; + if (enable != available) { + RTC_LOG(LS_WARNING) << "changing stereo recording not supported"; + return -1; + } + + return 0; + } + + int32_t StereoRecording(bool* enabled) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *enabled = isStereoRecordSupported_; + RTC_DLOG(LS_VERBOSE) << "output: " << *enabled; + + return 0; + } + + int32_t PlayoutDelay(uint16_t* delayMs) const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return output_->PlayoutDelay(delayMs); + } + + bool BuiltInAECIsAvailable() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return false; + } + + bool BuiltInAGCIsAvailable() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return false; + } + + bool BuiltInNSIsAvailable() const override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return false; + } + + int32_t EnableBuiltInAEC(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + RTC_LOG_F(LS_ERROR) << "Not supported on this platform"; + + return -1; + } + + int32_t EnableBuiltInAGC(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + RTC_LOG_F(LS_ERROR) << "Not supported on this platform"; + + return -1; + } + + int32_t EnableBuiltInNS(bool enable) override + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << "(" << enable << ")"; + RTC_LOG_F(LS_ERROR) << "Not supported on this platform"; + + return -1; + } + + int32_t GetPlayoutUnderrunCount() const override + { + if (!initialized_) { + return -1; + } + + return output_->GetPlayoutUnderrunCount(); + } + + absl::optional GetStats() const override + { + if (!initialized_) { + return absl::nullopt; + } + + return output_->GetStats(); + } + + int32_t AttachAudioBuffer() + { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + output_->AttachAudioBuffer(audioDeviceBuffer_.get()); + input_->AttachAudioBuffer(audioDeviceBuffer_.get()); + + return 0; + } + +private: + SequenceChecker threadChecker_; + + const AudioDeviceModule::AudioLayer audioLayer_; + const bool isStereoPlayoutSupported_; + const bool isStereoRecordSupported_; + const std::unique_ptr taskQueueFactory_; + const std::unique_ptr input_; + const std::unique_ptr output_; + std::unique_ptr audioDeviceBuffer_; + + bool initialized_; +}; + +FunctionReference NapiAudioDeviceModule::constructor_; + +void NapiAudioDeviceModule::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiAudioDeviceModule::GetEventHandler, &NapiAudioDeviceModule::SetEventHandler>( + kAttributeNameOnCapturerError, napi_default, (void*)kEventNameCapturerError), + InstanceAccessor<&NapiAudioDeviceModule::GetEventHandler, &NapiAudioDeviceModule::SetEventHandler>( + kAttributeNameOnCapturerStateChange, napi_default, (void*)kEventNameCapturerStateChange), + InstanceAccessor<&NapiAudioDeviceModule::GetEventHandler, &NapiAudioDeviceModule::SetEventHandler>( + kAttributeNameOnCapturerSamplesReady, napi_default, (void*)kEventNameCapturerSamplesReady), + InstanceAccessor<&NapiAudioDeviceModule::GetEventHandler, &NapiAudioDeviceModule::SetEventHandler>( + kAttributeNameOnRendererError, napi_default, (void*)kEventNameRendererError), + InstanceAccessor<&NapiAudioDeviceModule::GetEventHandler, &NapiAudioDeviceModule::SetEventHandler>( + kAttributeNameOnRendererStateChange, napi_default, (void*)kEventNameRendererStateChange), + InstanceMethod<&NapiAudioDeviceModule::SetSpeakerMute>(kMethodNameSetSpeakerMute), + InstanceMethod<&NapiAudioDeviceModule::SetMicrophoneMute>(kMethodNameSetMicrophoneMute), + InstanceMethod<&NapiAudioDeviceModule::SetNoiseSuppressorEnabled>(kMethodNameSetNoiseSuppressorEnabled), + InstanceMethod<&NapiAudioDeviceModule::ToJson>(kMethodNameToJson), + StaticMethod<&NapiAudioDeviceModule::isBuiltInAcousticEchoCancelerSupported>( + kMethodNameIsBuiltInAcousticEchoCancelerSupported), + StaticMethod<&NapiAudioDeviceModule::isBuiltInNoiseSuppressorSupported>( + kMethodNameIsBuiltInNoiseSuppressorSupported), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiAudioDeviceModule::NapiAudioDeviceModule(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + int32_t audioSource = AUDIOSTREAM_SOURCE_TYPE_VOICE_COMMUNICATION; + int32_t audioFormat = AUDIOSTREAM_SAMPLE_S16LE; + int32_t inputSampleRate = 48000; + int32_t outputSampleRate = 48000; + int32_t rendererUsage = AUDIOSTREAM_USAGE_VOICE_COMMUNICATION; + bool useStereoInput = false; + bool useStereoOutput = false; + bool useLowLatency = false; + bool useHardwareAcousticEchoCanceler = false; + bool useHardwareNoiseSuppressor = false; + + if (info.Length() > 0) { + auto options = ((Napi::Value)info[0]).As(); + if (options.Has(kAttributeNameAudioSource)) { + audioSource = options.Get(kAttributeNameAudioSource).As().Int32Value(); + } + if (options.Has(kAttributeNameAudioFormat)) { + audioFormat = options.Get(kAttributeNameAudioFormat).As().Int32Value(); + } + if (options.Has(kAttributeNameInputSampleRate)) { + inputSampleRate = options.Get(kAttributeNameInputSampleRate).As().Int32Value(); + } + if (options.Has(kAttributeNameUseStereoInput)) { + useStereoInput = options.Get(kAttributeNameUseStereoInput).As().Value(); + } + if (options.Has(kAttributeNameOutputSampleRate)) { + outputSampleRate = options.Get(kAttributeNameOutputSampleRate).As().Int32Value(); + } + if (options.Has(kAttributeNameUseStereoOutput)) { + useStereoOutput = options.Get(kAttributeNameUseStereoOutput).As().Value(); + } + if (options.Has(kAttributeNameRendererUsage)) { + rendererUsage = options.Get(kAttributeNameRendererUsage).As().Int32Value(); + } + if (options.Has(kAttributeNameUseLowLatency)) { + useLowLatency = options.Get(kAttributeNameUseLowLatency).As().Value(); + } + if (options.Has(kAttributeNameUseHardwareAcousticEchoCanceler)) { + useHardwareAcousticEchoCanceler = + options.Get(kAttributeNameUseHardwareAcousticEchoCanceler).As().Value(); + } + if (options.Has(kAttributeNameUseHardwareNoiseSuppressor)) { + useHardwareNoiseSuppressor = options.Get(kAttributeNameUseHardwareNoiseSuppressor).As().Value(); + } + } + + AudioParameters output_parameters; + output_parameters.reset(outputSampleRate, 1); + + auto audioInput = AudioCapturer::Create(audioSource, audioFormat, inputSampleRate, useStereoInput, useLowLatency); + audioInput->RegisterObserver(this); + + auto audioOutput = AudioRenderer::Create(rendererUsage, outputSampleRate, useStereoOutput, useLowLatency); + audioOutput->RegisterObserver(this); + + adm_ = rtc::make_ref_counted( + AudioDeviceModule::kPlatformDefaultAudio, useStereoOutput, useStereoInput, std::move(audioInput), + std::move(audioOutput)); + if (!adm_) { + NAPI_THROW_VOID(Error::New(info.Env(), "failed to create audio device module")); + } +} + +NapiAudioDeviceModule::~NapiAudioDeviceModule() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } +} + +Napi::Value NapiAudioDeviceModule::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + const auto type = (const char*)info.Data(); + + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + return it->second.ref.Value(); + } + + return info.Env().Null(); +} + +void NapiAudioDeviceModule::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!value.IsFunction()) { + NAPI_THROW_VOID(TypeError::New(info.Env(), "First argument is not Function")); + } + + const auto type = (const char*)info.Data(); + + { + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + it->second.tsfn.Release(); + eventHandlers_.erase(it); + } + } + + Function cb = value.As(); + + Reference* context = new Reference; + *context = Persistent(info.This()); + + EventHandler handler; + handler.ref = Persistent(cb); + handler.tsfn = ThreadSafeFunction::New( + info.Env(), cb, type, 0, 1, context, [](Napi::Env env, Reference* ctx) { + (void)env; + ctx->Reset(); + delete ctx; + }); + + std::lock_guard lock(mutex_); + eventHandlers_[type] = std::move(handler); +} + +Napi::Value NapiAudioDeviceModule::SetSpeakerMute(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + adm_->SetSpeakerMute(info[0].As().Value()); + return info.Env().Undefined(); +} + +Napi::Value NapiAudioDeviceModule::SetMicrophoneMute(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + adm_->SetMicrophoneMute(info[0].As().Value()); + return info.Env().Undefined(); +} + +Napi::Value NapiAudioDeviceModule::SetNoiseSuppressorEnabled(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // Noise suppressor is not supported. + return Boolean::New(info.Env(), false); +} + +Napi::Value NapiAudioDeviceModule::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto result = Object::New(info.Env()); +#ifndef NDEBUG + result.Set("__native_class__", String::New(info.Env(), "NapiAudioDeviceModule")); +#endif + + return result; +} + +Napi::Value NapiAudioDeviceModule::isBuiltInAcousticEchoCancelerSupported(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return Boolean::New(info.Env(), false); +} + +Napi::Value NapiAudioDeviceModule::isBuiltInNoiseSuppressorSupported(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return Boolean::New(info.Env(), false); +} + +void NapiAudioDeviceModule::OnAudioInputError(AudioErrorType type, const std::string& message) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " error=" << type << ", " << message; + + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameCapturerError); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context, type, message](Napi::Env env, Napi::Function jsCallback) { + RTC_DLOG(LS_VERBOSE) << "error=" << type << ", " << message; + auto jsError = Object::New(env); + jsError.Set("type", String::New(env, AudioErrorTypeToString(type))); + jsError.Set("message", String::New(env, message)); + + auto jsEvent = Object::New(env); + jsEvent.Set("error", jsError); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiAudioDeviceModule::OnAudioInputStateChange(AudioStateType newState) +{ + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameCapturerStateChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context, newState](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("state", String::New(env, AudioStateToString(newState))); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiAudioDeviceModule::OnAudioInputSamplesReady( + int32_t sampleRate, int32_t format, int32_t channelCount, rtc::CopyOnWriteBuffer* data) +{ + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameCapturerSamplesReady); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall( + [context, sampleRate, format, channelCount, data](Napi::Env env, Napi::Function jsCallback) { + auto arrayBuffer = ArrayBuffer::New( + env, static_cast(data->MutableData()), data->size(), + [](Napi::Env /*env*/, void* /*data*/, rtc::CopyOnWriteBuffer* hint) { + RTC_DLOG(LS_VERBOSE) << "release rtc::CopyOnWriteBuffer"; + delete hint; + }, + data); + + auto jsAudioSamples = Object::New(env); + jsAudioSamples.Set("sampleRate", Number::New(env, sampleRate)); + jsAudioSamples.Set("audioFormat", Number::New(env, format)); + jsAudioSamples.Set("channelCount", Number::New(env, channelCount)); + jsAudioSamples.Set("data", arrayBuffer); + + auto jsEvent = Object::New(env); + jsEvent.Set("samples", jsAudioSamples); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiAudioDeviceModule::OnAudioOutputError(AudioErrorType type, const std::string& message) +{ + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameRendererError); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context, type, message](Napi::Env env, Napi::Function jsCallback) { + auto jsError = Object::New(env); + jsError.Set("type", String::New(env, AudioErrorTypeToString(type))); + jsError.Set("message", String::New(env, message)); + + auto jsEvent = Object::New(env); + jsEvent.Set("error", jsError); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiAudioDeviceModule::OnAudioOutputStateChange(AudioStateType newState) +{ + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameRendererStateChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context, newState](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("state", String::New(env, AudioStateToString(newState))); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.h new file mode 100644 index 0000000000000000000000000000000000000000..8adc075b6b0d860675d9715fb2e9e29c1a4ebc7a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_device_module.h @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_DEVICE_MODULE_H +#define WEBRTC_AUDIO_DEVICE_MODULE_H + +#include +#include +#include + +#include "napi.h" + +#include "absl/types/optional.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "modules/audio_device/include/audio_device.h" + +namespace webrtc { + +class AudioDeviceBuffer; + +enum class AudioErrorType { INIT, START_EXCEPTION, START_STATE_MISMATCH, GENERAL }; + +enum class AudioStateType { START, STOP }; + +class AudioInput { +public: + class Observer { + public: + virtual ~Observer() = default; + virtual void OnAudioInputError(AudioErrorType type, const std::string& message) = 0; + virtual void OnAudioInputStateChange(AudioStateType newState) = 0; + virtual void OnAudioInputSamplesReady( + int32_t sampleRate, int32_t format, int32_t channelCount, rtc::CopyOnWriteBuffer* data) = 0; + }; + + virtual ~AudioInput() {} + + virtual int32_t Init() = 0; + virtual int32_t Terminate() = 0; + + virtual int32_t InitRecording() = 0; + virtual bool RecordingIsInitialized() const = 0; + + virtual int32_t StartRecording() = 0; + virtual int32_t StopRecording() = 0; + virtual bool Recording() const = 0; + + virtual void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) = 0; + + virtual int32_t MicrophoneMuteIsAvailable(bool* available) = 0; + virtual int32_t SetMicrophoneMute(bool mute) = 0; + virtual int32_t MicrophoneMute(bool* enabled) const = 0; + + virtual void RegisterObserver(Observer* obs) = 0; + virtual void UnregisterObserver(Observer* obs) = 0; +}; + +class AudioOutput { +public: + class Observer { + public: + virtual ~Observer() = default; + virtual void OnAudioOutputError(AudioErrorType type, const std::string& message) = 0; + virtual void OnAudioOutputStateChange(AudioStateType newState) = 0; + }; + + virtual ~AudioOutput() {} + + virtual int32_t Init() = 0; + virtual int32_t Terminate() = 0; + + virtual int32_t InitPlayout() = 0; + virtual bool PlayoutIsInitialized() const = 0; + + virtual int32_t StartPlayout() = 0; + virtual int32_t StopPlayout() = 0; + virtual bool Playing() const = 0; + + virtual int32_t SpeakerMuteIsAvailable(bool* available) = 0; + virtual int32_t setSpeakerMute(bool mute) = 0; + virtual int32_t SpeakerMute(bool* enabled) const = 0; + + virtual void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) = 0; + + virtual int32_t PlayoutDelay(uint16_t* delay_ms) const = 0; + virtual int GetPlayoutUnderrunCount() = 0; + + virtual absl::optional GetStats() const + { + return absl::nullopt; + } + + virtual void RegisterObserver(Observer* obs) = 0; + virtual void UnregisterObserver(Observer* obs) = 0; +}; + +class NapiAudioDeviceModule : public Napi::ObjectWrap, + public AudioInput::Observer, + public AudioOutput::Observer { +public: + static void Init(Napi::Env env, Napi::Object exports); + + ~NapiAudioDeviceModule() override; + + rtc::scoped_refptr Get() const + { + return adm_; + } + +protected: + friend class ObjectWrap; + + explicit NapiAudioDeviceModule(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value SetSpeakerMute(const Napi::CallbackInfo& info); + Napi::Value SetMicrophoneMute(const Napi::CallbackInfo& info); + Napi::Value SetNoiseSuppressorEnabled(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + static Napi::Value isBuiltInAcousticEchoCancelerSupported(const Napi::CallbackInfo& info); + static Napi::Value isBuiltInNoiseSuppressorSupported(const Napi::CallbackInfo& info); + +protected: + void OnAudioInputError(AudioErrorType type, const std::string& message) override; + void OnAudioInputStateChange(AudioStateType newState) override; + void OnAudioInputSamplesReady( + int32_t sampleRate, int32_t format, int32_t channelCount, rtc::CopyOnWriteBuffer* data) override; + void OnAudioOutputError(AudioErrorType type, const std::string& message) override; + void OnAudioOutputStateChange(AudioStateType newState) override; + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr adm_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex mutex_; + std::map eventHandlers_; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_DEVICE_MODULE_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.cpp b/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f098fc5bccce609b3855d1ac1985fb126c8053c9 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.cpp @@ -0,0 +1,396 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio_renderer.h" +#include "audio_common.h" + +#include "rtc_base/logging.h" +#include "modules/audio_device/audio_device_buffer.h" +#include "system_wrappers/include/field_trial.h" + +namespace webrtc { + +std::unique_ptr AudioRenderer::Create(int32_t usage, int32_t sampleRate, bool useStereoOutput, bool useLowLatency) +{ + return std::unique_ptr(new AudioRenderer(usage, sampleRate, useStereoOutput, useLowLatency)); +} + +int32_t AudioRenderer::OnWriteData1(OH_AudioRenderer* renderer, void* userData, void* buffer, int32_t length) +{ + auto self = (AudioRenderer*)userData; + return self->OnWriteData(renderer, buffer, length); +} + +int32_t AudioRenderer::OnStreamEvent1(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Event event) +{ + auto self = (AudioRenderer*)userData; + return self->OnStreamEvent(renderer, event); +} + +int32_t AudioRenderer::OnInterruptEvent1( + OH_AudioRenderer* renderer, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) +{ + auto self = (AudioRenderer*)userData; + return self->OnInterruptEvent(renderer, type, hint); +} + +int32_t AudioRenderer::OnError1(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Result error) +{ + auto self = (AudioRenderer*)userData; + return self->OnError(renderer, error); +} + +AudioRenderer::AudioRenderer(int32_t usage, int32_t sampleRate, bool useStereoOutput, bool useLowLatency) + : rendererUsage_(usage), sampleRate_(sampleRate), channelCount_(useStereoOutput ? 2 : 1), useLowLatency_(useLowLatency) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + threadChecker_.Detach(); +} + +AudioRenderer::~AudioRenderer() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + Terminate(); +} + +int32_t AudioRenderer::Init() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + return 0; +} + +int32_t AudioRenderer::Terminate() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + StopPlayout(); + threadChecker_.Detach(); + + return 0; +} + +int32_t AudioRenderer::InitPlayout() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (initialized_) { + // Already initialized. + return 0; + } + RTC_DCHECK(!playing_); + + OH_AudioStreamBuilder* builder = nullptr; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_Create(&builder, AUDIOSTREAM_TYPE_RENDERER), + NotifyError(AudioErrorType::INIT, "System error"), -1); + + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetRendererInfo(builder, static_cast(rendererUsage_)), + NotifyError(AudioErrorType::INIT, "failed to set renderer info"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetSamplingRate(builder, sampleRate_), + NotifyError(AudioErrorType::INIT, "failed to set sample rate"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetChannelCount(builder, channelCount_), + NotifyError(AudioErrorType::INIT, "failed to set channel count"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetSampleFormat(builder, AUDIOSTREAM_SAMPLE_S16LE), + NotifyError(AudioErrorType::INIT, "failed to set sample format"), -1); + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetLatencyMode( + builder, useLowLatency_ ? AUDIOSTREAM_LATENCY_MODE_FAST : AUDIOSTREAM_LATENCY_MODE_NORMAL), + NotifyError(AudioErrorType::INIT, "failed to set latency mode"), -1); + + OH_AudioRenderer_Callbacks callbacks; + callbacks.OH_AudioRenderer_OnWriteData = OnWriteData1; + callbacks.OH_AudioRenderer_OnStreamEvent = OnStreamEvent1; + callbacks.OH_AudioRenderer_OnInterruptEvent = OnInterruptEvent1; + callbacks.OH_AudioRenderer_OnError = OnError1; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_SetRendererCallback(builder, callbacks, this), + NotifyError(AudioErrorType::INIT, "failed to set renderer callback"), -1); + + OH_AudioRenderer* stream = nullptr; + OH_RESULT_CHECK( + OH_AudioStreamBuilder_GenerateRenderer(builder, &stream), + NotifyError(AudioErrorType::INIT, "failed to generate renderer"), -1); + + // check configuration + int32_t rate; + OH_RESULT_CHECK( + OH_AudioRenderer_GetSamplingRate(stream, &rate), + NotifyError(AudioErrorType::INIT, "failed to get sampling rate"), -1); + if (rate != sampleRate_) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested sample rate"; + NotifyError(AudioErrorType::INIT, "unmatched sampling rate"); + return -1; + } + + int32_t channelCount; + OH_RESULT_CHECK( + OH_AudioRenderer_GetChannelCount(stream, &channelCount), + NotifyError(AudioErrorType::INIT, "failed to get channel count"), -1); + if (channelCount != static_cast(channelCount_)) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested channel count"; + NotifyError(AudioErrorType::INIT, "unmatched channel count"); + return -1; + } + + OH_AudioStream_SampleFormat sampleFormat; + OH_RESULT_CHECK( + OH_AudioRenderer_GetSampleFormat(stream, &sampleFormat), + NotifyError(AudioErrorType::INIT, "failed to get sample format"), -1); + if (sampleFormat != AUDIOSTREAM_SAMPLE_S16LE) { + RTC_LOG(LS_ERROR) << "Stream unable to use requested format"; + NotifyError(AudioErrorType::INIT, "unmatched sample format"); + return -1; + } + + renderer_ = stream; + initialized_ = true; + + RTC_DLOG(LS_VERBOSE) << "current state: " << StateToString(GetCurrentState()); + + return 0; +} + +bool AudioRenderer::PlayoutIsInitialized() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return initialized_; +} + +int32_t AudioRenderer::StartPlayout() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (playing_) { + // Already playing. + return 0; + } + + if (!initialized_) { + RTC_DLOG(LS_WARNING) << "Playout can not start since InitPlayout must succeed first"; + return 0; + } + + if (fineAudioBuffer_) { + fineAudioBuffer_->ResetPlayout(); + } + + OH_AudioStream_State state = GetCurrentState(); + if (state != AUDIOSTREAM_STATE_PREPARED) { + RTC_LOG(LS_ERROR) << "Invalid state: " << StateToString(state); + NotifyError(AudioErrorType::START_STATE_MISMATCH, std::string("Invalid state: ") + StateToString(state)); + return -1; + } + + OH_RESULT_CHECK( + OH_AudioRenderer_Start(renderer_), NotifyError(AudioErrorType::START_EXCEPTION, "System error"), -1); + RTC_DLOG(LS_VERBOSE) << "Current state: " << StateToString(GetCurrentState()); + + playing_ = true; + + NotifyStateChange(AudioStateType::START); + + return 0; +} + +int32_t AudioRenderer::StopPlayout() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + if (!initialized_ || !playing_) { + return 0; + } + + OH_RESULT_CHECK(OH_AudioRenderer_Stop(renderer_), -1); + OH_RESULT_CHECK(OH_AudioRenderer_Release(renderer_), -1); + + initialized_ = false; + playing_ = false; + + NotifyStateChange(AudioStateType::STOP); + + return 0; +} + +bool AudioRenderer::Playing() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return playing_; +} + +void AudioRenderer::AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK(threadChecker_.IsCurrent()); + audioDeviceBuffer_ = audioBuffer; + RTC_CHECK(audioDeviceBuffer_); + audioDeviceBuffer_->SetPlayoutSampleRate(sampleRate_); + audioDeviceBuffer_->SetPlayoutChannels(channelCount_); + + fineAudioBuffer_ = std::make_unique(audioDeviceBuffer_); +} + +int32_t AudioRenderer::SpeakerMuteIsAvailable(bool* available) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *available = true; + return 0; +} + +int32_t AudioRenderer::setSpeakerMute(bool mute) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + speakerMute_ = mute; + return 0; +} + +int32_t AudioRenderer::SpeakerMute(bool* enabled) const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *enabled = speakerMute_; + return 0; +} + +int32_t AudioRenderer::PlayoutDelay(uint16_t* delayMs) const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + *delayMs = rendererDelayMs_; + return 0; +} + +int AudioRenderer::GetPlayoutUnderrunCount() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return 0; +} + +void AudioRenderer::RegisterObserver(Observer* obs) +{ + if (!obs) { + return; + } + + observers_.insert(obs); +} + +void AudioRenderer::UnregisterObserver(Observer* obs) +{ + if (!obs) { + return; + } + + observers_.erase(obs); +} + +int32_t AudioRenderer::OnWriteData(OH_AudioRenderer* renderer, void* buffer, int32_t length) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " length=" << length; + + (void)renderer; + + int64_t position; + int64_t timestamp; + OH_RESULT_CHECK(OH_AudioRenderer_GetTimestamp(renderer_, CLOCK_MONOTONIC, &position, ×tamp), 0); + + double latencyMillis = static_cast(timestamp - rtc::TimeNanos()) / rtc::kNumNanosecsPerMillisec; + RTC_DLOG(LS_VERBOSE) << "latencyMillis=" << latencyMillis; + rendererDelayMs_ = static_cast(std::ceil(latencyMillis)); + + fineAudioBuffer_->GetPlayoutData( + rtc::MakeArrayView(static_cast(buffer), length / sizeof(int16_t)), rendererDelayMs_); + + if (speakerMute_) { + std::memset(buffer, 0, length); + } + + return 0; +} + +int32_t AudioRenderer::OnStreamEvent(OH_AudioRenderer* renderer, OH_AudioStream_Event event) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " event=" << event; + + (void)renderer; + + return 0; +} + +int32_t AudioRenderer::OnInterruptEvent( + OH_AudioRenderer* renderer, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " type=" << type; + + (void)renderer; + (void)hint; + + return 0; +} + +int32_t AudioRenderer::OnError(OH_AudioRenderer* renderer, OH_AudioStream_Result error) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " error=" << error; + + (void)renderer; + + NotifyError(AudioErrorType::GENERAL, "System error"); + + return 0; +} + +OH_AudioStream_State AudioRenderer::GetCurrentState() const +{ + OH_AudioStream_State state = AUDIOSTREAM_STATE_INVALID; + if (renderer_) { + OH_AudioRenderer_GetCurrentState(renderer_, &state); + } + + return state; +} + +void AudioRenderer::NotifyError(AudioErrorType error, const std::string& message) +{ + for (auto& obs : observers_) { + obs->OnAudioOutputError(error, message); + } +} + +void AudioRenderer::NotifyStateChange(AudioStateType state) +{ + for (auto& obs : observers_) { + obs->OnAudioOutputStateChange(state); + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.h b/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.h new file mode 100644 index 0000000000000000000000000000000000000000..8e05e02fffe5c554d1f12fb6341be376e1edf445 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_device/audio_renderer.h @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_RENDERER_H +#define WEBRTC_AUDIO_RENDERER_H + +#include "audio_device_module.h" + +#include +#include + +#include +#include +#include + +#include "napi.h" + +#include "api/sequence_checker.h" +#include "modules/audio_device/fine_audio_buffer.h" + +namespace webrtc { + +class AudioRenderer : public AudioOutput { +public: + static std::unique_ptr Create(int32_t usage, int32_t sampleRate, bool useStereoOutput, bool useLowLatency); + + ~AudioRenderer() override; + + int32_t Init() override; + int32_t Terminate() override; + + int32_t InitPlayout() override; + bool PlayoutIsInitialized() const override; + + int32_t StartPlayout() override; + int32_t StopPlayout() override; + bool Playing() const override; + + void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) override; + + int32_t SpeakerMuteIsAvailable(bool* available) override; + int32_t setSpeakerMute(bool mute) override; + int32_t SpeakerMute(bool* enabled) const override; + + int32_t PlayoutDelay(uint16_t* delayMs) const override; + int GetPlayoutUnderrunCount() override; + + void RegisterObserver(Observer* obs) override; + void UnregisterObserver(Observer* obs) override; + +protected: + AudioRenderer(int32_t usage, int32_t sampleRate, bool useStereoOutput, bool useLowLatency); + + static int32_t OnWriteData1(OH_AudioRenderer* renderer, void* userData, void* buffer, int32_t length); + static int32_t OnStreamEvent1(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Event event); + static int32_t OnInterruptEvent1( + OH_AudioRenderer* renderer, void* userData, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint); + static int32_t OnError1(OH_AudioRenderer* renderer, void* userData, OH_AudioStream_Result error); + + int32_t OnWriteData(OH_AudioRenderer* renderer, void* buffer, int32_t length); + int32_t OnStreamEvent(OH_AudioRenderer* renderer, OH_AudioStream_Event event); + int32_t OnInterruptEvent(OH_AudioRenderer* renderer, OH_AudioInterrupt_ForceType type, OH_AudioInterrupt_Hint hint); + int32_t OnError(OH_AudioRenderer* renderer, OH_AudioStream_Result error); + + OH_AudioStream_State GetCurrentState() const; + + void NotifyError(AudioErrorType error, const std::string& message); + void NotifyStateChange(AudioStateType state); + +private: + SequenceChecker threadChecker_; + + const int32_t rendererUsage_; + const int32_t sampleRate_; + const int32_t channelCount_; + const bool useLowLatency_; + + // Delay estimate of the output + std::atomic rendererDelayMs_{0}; + + // Samples to be played are replaced by zeros if `speakerMute_` is set to true. + // Can be used to ensure that the speaker is fully muted. + std::atomic speakerMute_{false}; + + bool initialized_{false}; + bool playing_{false}; + + AudioDeviceBuffer* audioDeviceBuffer_{nullptr}; + std::unique_ptr fineAudioBuffer_; + + OH_AudioRenderer* renderer_{nullptr}; + + std::set observers_; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_RENDERER_H diff --git a/sdk/ohos/src/ohos_webrtc/audio_processing_factory.cpp b/sdk/ohos/src/ohos_webrtc/audio_processing_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c05d1924c4af114abafd0676c7d749d2aa315b62 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_processing_factory.cpp @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "audio_processing_factory.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiAudioProcessing::constructor_; + +void NapiAudioProcessing::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiAudioProcessing::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiAudioProcessing::NewInstance(Napi::Env env, rtc::scoped_refptr audioProcessing) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!audioProcessing) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto external = External::New( + env, audioProcessing.release(), [](Napi::Env /*env*/, AudioProcessing* apm) { apm->Release(); }); + + return constructor_.New({external}); +} + +NapiAudioProcessing::NapiAudioProcessing(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + if (info.Length() == 0 || !info[0].IsExternal()) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + audioProcessing_ = info[0].As>().Data(); +} + +rtc::scoped_refptr NapiAudioProcessing::Get() const +{ + return audioProcessing_; +} + +Napi::Value NapiAudioProcessing::ToJson(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiAudioProcessing"); +#endif + + return json; +} + +FunctionReference NapiAudioProcessingFactory::constructor_; + +void NapiAudioProcessingFactory::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiAudioProcessingFactory::Create>(kMethodNameCreate), + InstanceMethod<&NapiAudioProcessingFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiAudioProcessingFactory::NapiAudioProcessingFactory(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) +{ +} + +Napi::Value NapiAudioProcessingFactory::Create(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + AudioProcessing::Config apmConfig; + if (info.Length() > 0 && info[0].IsObject()) { + auto jsOptions = info[0].As(); + (void)jsOptions; + // nothing to do + } + + return NapiAudioProcessing::NewInstance(info.Env(), AudioProcessingBuilder().SetConfig(apmConfig).Create()); +} + +Napi::Value NapiAudioProcessingFactory::ToJson(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiAudioProcessingFactory"); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/audio_processing_factory.h b/sdk/ohos/src/ohos_webrtc/audio_processing_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..3a9840f4a760909a362162f3691f2cb660f7c385 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/audio_processing_factory.h @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_PROCESSING_FACTORY_H +#define WEBRTC_AUDIO_PROCESSING_FACTORY_H + +#include "utils/marcos.h" + +#include "modules/audio_processing/include/audio_processing.h" + +#include "napi.h" + +namespace webrtc { + +class NapiAudioProcessing : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(AudioProcessing); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr audioProcessing); + + rtc::scoped_refptr Get() const; + +protected: + friend class ObjectWrap; + explicit NapiAudioProcessing(const Napi::CallbackInfo& info); + + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr audioProcessing_; +}; + +class NapiAudioProcessingFactory : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(AudioProcessingFactory); + NAPI_METHOD_NAME_DECLARE(Create, create); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + +protected: + friend class ObjectWrap; + explicit NapiAudioProcessingFactory(const Napi::CallbackInfo& info); + + Napi::Value Create(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_PROCESSING_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.cpp b/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d119e9913dd4bc45ae6a54092b7d4c645c86802d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.cpp @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "camera_capturer.h" +#include "../video/video_frame_receiver_gl.h" +#include "../utils/marcos.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace ohos; + +std::unique_ptr CameraCapturer::Create(std::string deviceId, video::VideoProfile profile) +{ + return std::unique_ptr(new CameraCapturer(deviceId, profile)); +} + +CameraCapturer::CameraCapturer(std::string deviceId, video::VideoProfile profile) + : deviceId_(deviceId), profile_(profile) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << ": " << this; +} + +CameraCapturer::~CameraCapturer() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Release(); +} + +void CameraCapturer::NotifyCapturedStart(bool success) +{ + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnCapturerStarted(success); + } +} + +void CameraCapturer::NotifyCapturedStop() +{ + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnCapturerStopped(); + } +} + +void CameraCapturer::Init(std::unique_ptr dataReceiver, VideoCapturer::Observer* observer) +{ + RTC_LOG(LS_INFO) << __FUNCTION__; + + { + UNUSED std::lock_guard lock(obsMutex_); + observer_ = observer; + } + + dataReceiver_ = std::move(dataReceiver); + dataReceiver_->SetVideoFrameSize(profile_.resolution.width, profile_.resolution.height); + dataReceiver_->SetCallback(this); + + isInitialized_ = true; +} + +void CameraCapturer::Release() +{ + RTC_LOG(LS_INFO) << __FUNCTION__; + + Stop(); + + { + UNUSED std::lock_guard lock(obsMutex_); + observer_ = nullptr; + } + + dataReceiver_.reset(); + + isInitialized_ = false; +} + +void CameraCapturer::Start() +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << ": this=" << this; + + StartInternal(); +} + +void CameraCapturer::Stop() +{ + RTC_LOG(LS_INFO) << __FUNCTION__ << ": this=" << this; + + StopInternal(); +} + +bool CameraCapturer::IsScreencast() +{ + return false; +} + +void CameraCapturer::StartInternal() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!isInitialized_) { + RTC_LOG(LS_ERROR) << "Not initialized"; + NotifyCapturedStart(false); + return; + } + + if (isStarted_) { + RTC_LOG(LS_WARNING) << "Capture session is started"; + return; + } + + int32_t deviceIndex = -1; + Camera_Device* device = nullptr; + auto devices = CameraManager::GetInstance().GetSupportedCameras(); + for (std::size_t i = 0; i < devices.Size(); i++) { + if (deviceId_ == devices[i]->cameraId) { + deviceIndex = i; + device = devices[i]; + break; + } + } + + if (deviceIndex == -1) { + RTC_LOG(LS_ERROR) << "No specific camera device found"; + return; + } + + RTC_LOG(LS_VERBOSE) << "device: " << deviceIndex << ", " << device->cameraId; + + auto sceneModes = CameraManager::GetInstance().GetSupportedSceneModes(device); + for (std::size_t i = 0; i < sceneModes.Size(); i++) { + RTC_DLOG(LS_VERBOSE) << "supported scene mode: " << sceneModes[i]; + if (sceneModes[i] == NORMAL_VIDEO) { + // something is wrong in video mode in OpenHarmony + // useVideoSceneMode_ = true; + } + } + + Camera_Profile* previewProfile = nullptr; + Camera_VideoProfile* videoProfile = nullptr; + auto capability = CameraManager::GetInstance().GetSupportedCameraOutputCapability(device); + if (useVideoSceneMode_) { + for (uint32_t i = 0; i < capability.VideoProfileSize(); i++) { + Camera_VideoProfile* p = capability.GetVideoProfile(i); + RTC_DLOG(LS_VERBOSE) << "video format: " << p->format; + RTC_DLOG(LS_VERBOSE) << "video size: " << p->size.width << "x" << p->size.height; + RTC_DLOG(LS_VERBOSE) << "video fps: " << p->range.min << "-" << p->range.max; + + if (profile_.format == NativeCameraFormatToPixelFormat(p->format) && + profile_.resolution.width == p->size.width && profile_.resolution.height == p->size.height && + profile_.frameRateRange.min >= p->range.min && profile_.frameRateRange.max <= p->range.max) + { + videoProfile = p; + break; + } + } + } else { + for (uint32_t i = 0; i < capability.PreviewProfileSize(); i++) { + Camera_Profile* p = capability.GetPreviewProfile(i); + RTC_DLOG(LS_VERBOSE) << "video format: " << p->format; + RTC_DLOG(LS_VERBOSE) << "video size: " << p->size.width << "x" << p->size.height; + + if (profile_.format == NativeCameraFormatToPixelFormat(p->format) && + profile_.resolution.width == p->size.width && profile_.resolution.height == p->size.height) + { + previewProfile = p; + break; + } + } + } + + if (!videoProfile && !previewProfile) { + RTC_LOG(LS_ERROR) << "No specific camera device found"; + return; + } + + input_ = CameraManager::GetInstance().CreateCameraInput(device); + if (input_.IsEmpty()) { + RTC_LOG(LS_ERROR) << "Failed to create camera input"; + return; + } + + if (!input_.Open()) { + RTC_LOG(LS_ERROR) << "Failed to open camera input"; + return; + } + + auto surfaceId = std::to_string(dataReceiver_->GetSurfaceId()); + + if (useVideoSceneMode_) { + videoOutput_ = CameraManager::GetInstance().CreateVideoOutput(videoProfile, surfaceId); + if (videoOutput_.IsEmpty()) { + RTC_LOG(LS_ERROR) << "Failed to create camera video output"; + return; + } + + videoOutput_.AddObserver(this); + } else { + previewOutput_ = CameraManager::GetInstance().CreatePreviewOutput(previewProfile, surfaceId); + if (previewOutput_.IsEmpty()) { + RTC_LOG(LS_ERROR) << "Failed to create camera preview output"; + return; + } + previewOutput_.AddObserver(this); + } + + captureSession_ = CameraManager::GetInstance().CreateCaptureSession(); + if (OH_CaptureSession_SetSessionMode(captureSession_.Raw(), NORMAL_VIDEO) != CAMERA_OK) { + RTC_LOG(LS_ERROR) << "Failed to set scene mode"; + } + + if (!captureSession_.BeginConfig()) { + RTC_LOG(LS_ERROR) << "Failed to begin capture session config"; + return; + } + + captureSession_.AddInput(input_); + + if (useVideoSceneMode_) { + captureSession_.AddVideoOutput(videoOutput_); + } else { + captureSession_.AddPreviewOutput(previewOutput_); + } + + if (!captureSession_.CommitConfig()) { + RTC_LOG(LS_ERROR) << "Failed to commit capture session config"; + return; + } + + if (!captureSession_.Start()) { + RTC_LOG(LS_ERROR) << "Failed to start capture session"; + NotifyCapturedStart(false); + return; + } + + isStarted_ = true; +} + +void CameraCapturer::StopInternal() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!isStarted_) { + RTC_LOG(LS_ERROR) << "Capture session is not started"; + return; + } + + if (!captureSession_.Stop()) { + RTC_LOG(LS_ERROR) << "Failed to stop capture session"; + return; + } + + if (useVideoSceneMode_) { + videoOutput_.RemoveObserver(this); + videoOutput_.Reset(); + } else { + previewOutput_.RemoveObserver(this); + previewOutput_.Reset(); + } + + input_.Close(); + input_.Reset(); + captureSession_.Reset(); + + isStarted_ = false; +} + +void CameraCapturer::OnCameraManagerStatusCallback(Camera_StatusInfo* status) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void CameraCapturer::OnPreviewOutputFrameStart() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + NotifyCapturedStart(true); +} + +void CameraCapturer::OnPreviewOutputFrameEnd(int32_t frameCount) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + NotifyCapturedStop(); +} + +void CameraCapturer::OnPreviewOutputError(Camera_ErrorCode errorCode) +{ + RTC_LOG(LS_ERROR) << __FUNCTION__ << ": errorCode=" << errorCode; +} + +void CameraCapturer::OnVideoOutputFrameStart() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + NotifyCapturedStart(true); +} + +void CameraCapturer::OnVideoOutputFrameEnd(int32_t frameCount) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + NotifyCapturedStop(); +} + +void CameraCapturer::OnVideoOutputError(Camera_ErrorCode errorCode) +{ + RTC_LOG(LS_ERROR) << __FUNCTION__ << ": errorCode=" << errorCode; +} + +void CameraCapturer::OnFrameAvailable( + rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnFrameCaptured(buffer, timestampUs, rotation); + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.h b/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.h new file mode 100644 index 0000000000000000000000000000000000000000..509d72db32662e9fbaf92bb5a8b2635597f6b21f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_capturer.h @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CAMERA_CAPTURER_H +#define WEBRTC_CAMERA_CAPTURER_H + +#include "camera_device_info.h" +#include "../video/video_capturer.h" +#include "../video/video_frame_receiver.h" +#include "../render/egl_context.h" +#include "../helper/camera.h" + +#include +#include +#include + +#include + +#include "api/video/video_frame_buffer.h" +#include "rtc_base/thread.h" + +namespace webrtc { + +class CameraCapturer : public VideoCapturer, + public VideoFrameReceiver::Callback, + public ohos::CameraPreviewOutput::Observer, + public ohos::CameraVideoOutput::Observer { +public: + static std::unique_ptr Create(std::string deviceId, video::VideoProfile profile); + + ~CameraCapturer() override; + + void Init(std::unique_ptr, VideoCapturer::Observer* observer) override; + void Release() override; + void Start() override; + void Stop() override; + bool IsScreencast() override; + +protected: + CameraCapturer(std::string deviceId, video::VideoProfile profile); + + void NotifyCapturedStart(bool success); + void NotifyCapturedStop(); + +protected: + void StartInternal(); + void StopInternal(); + + void OnCameraManagerStatusCallback(Camera_StatusInfo* status); + void OnPreviewOutputFrameStart() override; + void OnPreviewOutputFrameEnd(int32_t frameCount) override; + void OnPreviewOutputError(Camera_ErrorCode errorCode) override; + void OnVideoOutputFrameStart() override; + void OnVideoOutputFrameEnd(int32_t frameCount) override; + void OnVideoOutputError(Camera_ErrorCode errorCode) override; + + void + OnFrameAvailable(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) override; + +private: + const std::string deviceId_; + const video::VideoProfile profile_; + + bool isInitialized_{false}; + bool isStarted_{false}; + + bool useVideoSceneMode_{false}; + + ohos::CameraInput input_; + ohos::CameraPreviewOutput previewOutput_; + ohos::CameraVideoOutput videoOutput_; + ohos::CameraCaptureSession captureSession_; + + std::unique_ptr dataReceiver_; + + VideoCapturer::Observer* observer_{}; + std::mutex obsMutex_; +}; + +} // namespace webrtc + +#endif // WEBRTC_CAMERA_CAPTURER_H diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.cpp b/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ae533d91b6e8df9e27898df911789869f474e746 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.cpp @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "camera_device_info.h" + +namespace webrtc { + +std::string CameraPositionToString(Camera_Position position) +{ + switch (position) { + case CAMERA_POSITION_BACK: + return "Back"; + case CAMERA_POSITION_FRONT: + return "Front"; + default: + return "Unspecified"; + } +} + +std::string CameraConnectionTypeToString(Camera_Connection connectionType) +{ + switch (connectionType) { + case CAMERA_CONNECTION_BUILT_IN: + return "Build_in"; + case CAMERA_CONNECTION_USB_PLUGIN: + return "USB"; + case CAMERA_CONNECTION_REMOTE: + return "Remote"; + default: + return "Unspecified"; + } +} + +video::PixelFormat NativeCameraFormatToPixelFormat(Camera_Format format) +{ + switch (format) { + case CAMERA_FORMAT_RGBA_8888: + return video::PixelFormat::RGBA; + case CAMERA_FORMAT_YUV_420_SP: + return video::PixelFormat::NV12; // NV21? + default: + return video::PixelFormat::Unsupported; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.h b/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.h new file mode 100644 index 0000000000000000000000000000000000000000..b3d6c59117c2e0c9d1d56e2c2156bbef9116b587 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_device_info.h @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CAMERA_DEVICE_INFO_H +#define WEBRTC_CAMERA_DEVICE_INFO_H + +#include "../video/video_info.h" + +#include +#include + +#include + +namespace webrtc { + +enum class FacingMode { kNone, kUser, kEnvironment, kLeft, kRight }; + +struct CameraDeviceInfo { + std::string deviceId; + std::string groupId; + std::string label; + FacingMode facingMode; + std::vector profiles; +}; + +std::string CameraPositionToString(Camera_Position position); + +std::string CameraConnectionTypeToString(Camera_Connection connectionType); + +video::PixelFormat NativeCameraFormatToPixelFormat(Camera_Format format); + +} // namespace webrtc + +#endif // WEBRTC_CAMERA_DEVICE_INFO_H diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.cpp b/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..07c4bfdb90d29ce2cd9bef11297b5f743419136f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "camera_enumerator.h" +#include "../user_media/media_constraints.h" +#include "../helper/camera.h" + +#include +#include + +namespace webrtc { + +std::vector CameraEnumerator::GetDevices() +{ + std::vector result; + + auto cameras = ohos::CameraManager::GetInstance().GetSupportedCameras(); + for (std::size_t i = 0; i < cameras.Size(); i++) { + auto camera = cameras[i]; + RTC_DLOG(LS_VERBOSE) << "camera id: " << camera->cameraId; + RTC_DLOG(LS_VERBOSE) << "camera type: " << camera->cameraType; + RTC_DLOG(LS_VERBOSE) << "camera position: " << camera->cameraPosition; + RTC_DLOG(LS_VERBOSE) << "camera connection type: " << camera->connectionType; + + CameraDeviceInfo device; + device.deviceId = camera->cameraId; + device.groupId = "default"; + device.label = CameraConnectionTypeToString(camera->connectionType) + " " + + CameraPositionToString(camera->cameraPosition) + " (" + device.deviceId + ")"; + switch (camera->cameraPosition) { + case CAMERA_POSITION_FRONT: + device.facingMode = FacingMode::kUser; + break; + case CAMERA_POSITION_BACK: + device.facingMode = FacingMode::kEnvironment; + break; + default: + break; + } + + auto capability = ohos::CameraManager::GetInstance().GetSupportedCameraOutputCapability(camera); + for (uint32_t j = 0; j < capability.VideoProfileSize(); j++) { + Camera_VideoProfile* profile = capability.GetVideoProfile(j); + RTC_DLOG(LS_VERBOSE) << "video format: " << profile->format; + RTC_DLOG(LS_VERBOSE) << "video size: " << profile->size.width << "x" << profile->size.height; + RTC_DLOG(LS_VERBOSE) << "video fps: " << profile->range.min << "-" << profile->range.max; + + video::VideoProfile p; + p.format = NativeCameraFormatToPixelFormat(profile->format); + p.resolution = video::Resolution{profile->size.width, profile->size.height}; + p.frameRateRange = video::FrameRateRange{profile->range.min, profile->range.max}; + device.profiles.push_back(p); + } + + result.push_back(device); + } + + return result; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.h b/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.h new file mode 100644 index 0000000000000000000000000000000000000000..bb516c5685eab50d9a248468f2d40d2f3a0979cc --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/camera/camera_enumerator.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CAMERA_ENUMERATOR_H +#define WEBRTC_CAMERA_ENUMERATOR_H + +#include "../camera/camera_device_info.h" +#include +#include + +namespace webrtc { + +class CameraEnumerator { +public: + static std::vector GetDevices(); +}; + +} // namespace webrtc + +#endif // WEBRTC_CAMERA_ENUMERATOR_H diff --git a/sdk/ohos/src/ohos_webrtc/certificate.cpp b/sdk/ohos/src/ohos_webrtc/certificate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2b5862329a950a03c6ec938f0a56d9453bce8932 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/certificate.cpp @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "certificate.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiCertificate::constructor_; + +void NapiCertificate::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, "RTCCertificate", + { + InstanceAccessor<&NapiCertificate::GetExpires>("expires"), + InstanceMethod<&NapiCertificate::GetFingerprints>("getFingerprints"), + }); + exports.Set("RTCCertificate", func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiCertificate::NewInstance(Napi::Env env, rtc::scoped_refptr certificate) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto externalCertificate = + External::New(env, certificate.release(), [](Napi::Env /*env*/, rtc::RTCCertificate* dc) { + auto status = dc->Release(); + RTC_DLOG(LS_VERBOSE) << "RTCCertificate release status=" << status; + }); + + return constructor_.New({externalCertificate}); +} + +NapiCertificate::NapiCertificate(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info[0].IsExternal()) { + auto certificate = info[0].As>().Data(); + certificate_ = rtc::scoped_refptr(certificate); + auto status = certificate->Release(); + RTC_LOG(LS_VERBOSE) << "RtpSenderInterface release status=" << status; + } +} + +rtc::scoped_refptr NapiCertificate::Get() const +{ + return certificate_; +} + +Napi::Value NapiCertificate::GetExpires(const Napi::CallbackInfo& info) +{ + // 30 * 24 * 60 * 60 * 1000 + return Number::New(info.Env(), certificate_->Expires()); +} + +Napi::Value NapiCertificate::GetFingerprints(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto size = certificate_->GetSSLCertificateChain().GetSize(); + + Napi::Array array = Napi::Array::New(info.Env(), size); + for (size_t i = 0; i < size; ++i) { + auto stats = certificate_->GetSSLCertificateChain().Get(i).GetStats(); + auto statObj = Object::New(info.Env()); + + statObj.Set("algorithm", String::New(info.Env(), stats->fingerprint_algorithm)); + statObj.Set("value", String::New(info.Env(), stats->fingerprint)); + + array[i] = statObj; + } + + return array; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/certificate.h b/sdk/ohos/src/ohos_webrtc/certificate.h new file mode 100644 index 0000000000000000000000000000000000000000..634ce65364525b2f45d17bc09396fb92358a4884 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/certificate.h @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CERTIFICATE_H +#define WEBRTC_CERTIFICATE_H + +#include "napi.h" + +#include "api/peer_connection_interface.h" + +namespace webrtc { + +class NapiCertificate : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr certificate); + + explicit NapiCertificate(const Napi::CallbackInfo& info); + + rtc::scoped_refptr Get() const; + +protected: + Napi::Value GetExpires(const Napi::CallbackInfo& info); + Napi::Value GetFingerprints(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr certificate_; +}; + +} // namespace webrtc + +#endif // WEBRTC_CERTIFICATE_H diff --git a/sdk/ohos/src/ohos_webrtc/configuration.cpp b/sdk/ohos/src/ohos_webrtc/configuration.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5c14842ea0b5a933dd510cc122dca1ddeab0f130 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/configuration.cpp @@ -0,0 +1,279 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "configuration.h" +#include "certificate.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kAttributeNameIceServers[] = "iceServers"; +const char kAttributeNameUrls[] = "urls"; +const char kAttributeNameUserName[] = "username"; +const char kAttributeNameCredential[] = "credential"; +const char kAttributeNameIceTransportPolicy[] = "iceTransportPolicy"; +const char kAttributeNameBundlePolicy[] = "bundlePolicy"; +const char kAttributeNameRtcpMuxPolicy[] = "rtcpMuxPolicy"; +const char kAttributeNameCertificates[] = "certificates"; +const char kAttributeNameIceCandidatePoolSize[] = "iceCandidatePoolSize"; + +const char kEnumIceTransportPolicyAll[] = "all"; +const char kEnumIceTransportPolicyRelay[] = "relay"; +const char kEnumBundlePolicyBalanced[] = "balanced"; +const char kEnumBundlePolicyMaxBundle[] = "max-bundle"; +const char kEnumBundlePolicyMaxCompact[] = "max-compat"; +const char kEnumRtcpMuxPolicyRequire[] = "require"; + +bool JsToNativeIceServer(const Object& jsIceServer, PeerConnectionInterface::IceServer& iceServer) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto jsUrls = jsIceServer.Get(kAttributeNameUrls); + if (jsUrls.IsString()) { + auto url = jsUrls.As().Utf8Value(); + iceServer.urls.push_back(url); + } else if (jsUrls.IsArray()) { + auto jsUrlArray = jsUrls.As(); + for (uint32_t i = 0; i < jsUrlArray.Length(); i++) { + Napi::Value jsUrl = jsUrlArray[i]; + if (jsUrl.IsString()) { + auto url = jsUrls.As().Utf8Value(); + iceServer.urls.push_back(url); + } else { + RTC_LOG(LS_WARNING) << "element of urls is not string"; + } + } + } else { + RTC_LOG(LS_WARNING) << "urls is not string nor array"; + } + + if (jsIceServer.Has(kAttributeNameUserName)) { + auto jsUserName = jsIceServer.Get(kAttributeNameUserName); + if (jsUserName.IsString()) { + auto username = jsUserName.As().Utf8Value(); + iceServer.username = username; + } else { + RTC_LOG(LS_WARNING) << "username is not string"; + } + } + + if (jsIceServer.Has(kAttributeNameCredential)) { + auto jsCredential = jsIceServer.Get(kAttributeNameCredential); + if (jsCredential.IsString()) { + auto password = jsCredential.As().Utf8Value(); + iceServer.password = password; + } else { + RTC_LOG(LS_WARNING) << "credential is not string"; + } + } + + return true; +} + +bool NativeToJsIceServer(const PeerConnectionInterface::IceServer& iceServer, Object& jsIceServer) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Napi::Env env = jsIceServer.Env(); + + if (iceServer.urls.size() == 1) { + jsIceServer.Set(kAttributeNameUrls, String::New(env, iceServer.urls[0])); + } else { + auto jsUrlArray = Array::New(env, iceServer.urls.size()); + for (uint32_t i = 0; i < iceServer.urls.size(); i++) { + jsUrlArray[i] = String::New(env, iceServer.urls[i]); + } + jsIceServer.Set(kAttributeNameUrls, jsUrlArray); + } + + if (!iceServer.username.empty()) { + jsIceServer.Set(kAttributeNameUserName, String::New(env, iceServer.username)); + } + + if (!iceServer.password.empty()) { + jsIceServer.Set(kAttributeNameCredential, String::New(env, iceServer.password)); + } + + return true; +} + +bool JsToNativeConfiguration( + const Napi::Object& jsConfiguration, PeerConnectionInterface::RTCConfiguration& configuration) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (jsConfiguration.Has(kAttributeNameIceServers)) { + auto jsIceServers = jsConfiguration.Get(kAttributeNameIceServers); + if (jsIceServers.IsArray()) { + auto array = jsIceServers.As(); + for (size_t i = 0; i < array.Length(); ++i) { + Napi::Value jsIceServer = array[i]; + if (jsIceServer.IsObject()) { + PeerConnectionInterface::IceServer iceServer; + JsToNativeIceServer(jsIceServer.As(), iceServer); + configuration.servers.push_back(iceServer); + } else { + RTC_LOG(LS_WARNING) << "element of iceServers is not object"; + } + } + } else { + RTC_LOG(LS_WARNING) << "iceServers is not array"; + } + } + + if (jsConfiguration.Has(kAttributeNameIceTransportPolicy)) { + auto jsIceTransportPolicy = jsConfiguration.Get(kAttributeNameIceTransportPolicy); + if (jsIceTransportPolicy.IsString()) { + auto iceTransportPolicy = jsIceTransportPolicy.As().Utf8Value(); + if (iceTransportPolicy == kEnumIceTransportPolicyAll) { + configuration.type = PeerConnectionInterface::kAll; + } else if (iceTransportPolicy == kEnumIceTransportPolicyRelay) { + configuration.type = PeerConnectionInterface::kRelay; + } else { + RTC_LOG(LS_WARNING) << "Invalid iceTransportPolicy"; + } + } else { + RTC_LOG(LS_WARNING) << "iceTransportPolicy is not string"; + } + } + + if (jsConfiguration.Has(kAttributeNameBundlePolicy)) { + auto jsBundlePolicy = jsConfiguration.Get(kAttributeNameBundlePolicy); + if (jsBundlePolicy.IsString()) { + auto bundlePolicy = jsBundlePolicy.As().Utf8Value(); + if (bundlePolicy == kEnumBundlePolicyBalanced) { + configuration.bundle_policy = PeerConnectionInterface::kBundlePolicyBalanced; + } else if (bundlePolicy == kEnumBundlePolicyMaxCompact) { + configuration.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxCompat; + } else if (bundlePolicy == kEnumBundlePolicyMaxBundle) { + configuration.bundle_policy = PeerConnectionInterface::kBundlePolicyMaxBundle; + } else { + RTC_LOG(LS_WARNING) << "Invalid bundlePolicy"; + } + } else { + RTC_LOG(LS_WARNING) << "bundlePolicy is not string"; + } + } + + if (jsConfiguration.Has(kAttributeNameRtcpMuxPolicy)) { + auto jsRtcpMuxPolicy = jsConfiguration.Get(kAttributeNameRtcpMuxPolicy); + if (jsRtcpMuxPolicy.IsString()) { + auto rtcpMuxPolicy = jsRtcpMuxPolicy.As().Utf8Value(); + if (rtcpMuxPolicy == kEnumRtcpMuxPolicyRequire) { + configuration.rtcp_mux_policy = PeerConnectionInterface::kRtcpMuxPolicyRequire; + } else { + RTC_LOG(LS_WARNING) << "Invalid rtcpMuxPolicy"; + } + } else { + RTC_LOG(LS_WARNING) << "rtcpMuxPolicy is not string"; + } + } + + if (jsConfiguration.Has(kAttributeNameCertificates)) { + auto jsCertificates = jsConfiguration.Get(kAttributeNameCertificates); + if (jsCertificates.IsArray()) { + auto jsCertificateArray = jsCertificates.As(); + for (uint32_t i = 0; i < jsCertificateArray.Length(); i++) { + Napi::Value jsCertificate = jsCertificateArray[i]; + auto certificate = NapiCertificate::Unwrap(jsCertificate.As()); + if (certificate) { + configuration.certificates.push_back(certificate->Get()); + } + } + } + } + + if (jsConfiguration.Has(kAttributeNameIceCandidatePoolSize)) { + auto jsIceCandidatePoolSize = jsConfiguration.Get(kAttributeNameIceCandidatePoolSize); + if (jsIceCandidatePoolSize.IsNumber()) { + configuration.ice_candidate_pool_size = jsIceCandidatePoolSize.As().Int32Value(); + } + } + + return true; +} + +bool NativeToJsConfiguration( + const PeerConnectionInterface::RTCConfiguration& configuration, Napi::Object& jsConfiguration) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Napi::Env env = jsConfiguration.Env(); + + if (configuration.servers.size() > 0) { + auto jsIceServerArray = Array::New(env, configuration.servers.size()); + for (uint32_t i = 0; i < configuration.servers.size(); i++) { + auto jsIceServer = Object::New(env); + NativeToJsIceServer(configuration.servers[i], jsIceServer); + jsIceServerArray[i] = jsIceServer; + } + jsConfiguration.Set(kAttributeNameIceServers, jsIceServerArray); + } + + switch (configuration.type) { + case PeerConnectionInterface::kAll: + jsConfiguration.Set(kAttributeNameIceTransportPolicy, String::New(env, kEnumIceTransportPolicyAll)); + break; + case PeerConnectionInterface::kRelay: + jsConfiguration.Set(kAttributeNameIceTransportPolicy, String::New(env, kEnumIceTransportPolicyRelay)); + break; + default: + RTC_LOG(LS_ERROR) << "Invalid value of " << kAttributeNameIceTransportPolicy; + break; + } + + switch (configuration.bundle_policy) { + case PeerConnectionInterface::kBundlePolicyBalanced: + jsConfiguration.Set(kAttributeNameBundlePolicy, String::New(env, kEnumBundlePolicyBalanced)); + break; + case PeerConnectionInterface::kBundlePolicyMaxBundle: + jsConfiguration.Set(kAttributeNameBundlePolicy, String::New(env, kEnumBundlePolicyMaxBundle)); + break; + case PeerConnectionInterface::kBundlePolicyMaxCompat: + jsConfiguration.Set(kAttributeNameBundlePolicy, String::New(env, kEnumBundlePolicyMaxCompact)); + break; + default: + RTC_LOG(LS_ERROR) << "Invalid value of " << kAttributeNameBundlePolicy; + break; + } + + switch (configuration.rtcp_mux_policy) { + case PeerConnectionInterface::kRtcpMuxPolicyRequire: + jsConfiguration.Set(kAttributeNameRtcpMuxPolicy, String::New(env, kEnumRtcpMuxPolicyRequire)); + break; + default: + RTC_LOG(LS_ERROR) << "Invalid value of " << kAttributeNameRtcpMuxPolicy; + break; + } + + if (configuration.certificates.size() > 0) { + auto jsCertificateArray = Array::New(env, configuration.certificates.size()); + for (uint32_t i = 0; i < configuration.certificates.size(); i++) { + auto certificate = configuration.certificates[i]; + auto jsCertificate = NapiCertificate::NewInstance(env, certificate); + jsCertificateArray[i] = jsCertificate; + } + jsConfiguration.Set(kAttributeNameCertificates, jsCertificateArray); + } + + jsConfiguration.Set(kAttributeNameIceCandidatePoolSize, Number::New(env, configuration.ice_candidate_pool_size)); + + return true; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/configuration.h b/sdk/ohos/src/ohos_webrtc/configuration.h new file mode 100644 index 0000000000000000000000000000000000000000..8b2ed1084f610c215b3aee3e103b7d4b86d9b573 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/configuration.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CONFIGURATION_H +#define WEBRTC_CONFIGURATION_H + +#include "napi.h" + +#include "api/peer_connection_interface.h" + +namespace webrtc { + +bool JsToNativeConfiguration( + const Napi::Object& jsConfiguration, PeerConnectionInterface::RTCConfiguration& configuration); + +bool NativeToJsConfiguration( + const PeerConnectionInterface::RTCConfiguration& configuration, Napi::Object& jsConfiguration); + +} // namespace webrtc + +#endif // WEBRTC_CONFIGURATION_H diff --git a/sdk/ohos/src/ohos_webrtc/data_channel.cpp b/sdk/ohos/src/ohos_webrtc/data_channel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..99818c61111a0e3a91945fbaabbd6d9f64a3ff4d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/data_channel.cpp @@ -0,0 +1,658 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "data_channel.h" +#include "utils/marcos.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumBinaryTypeBlob[] = "blob"; +const char kEnumBinaryTypeArrayBuffer[] = "arraybuffer"; + +const char kEnumDataChannelStateClosed[] = "closed"; +const char kEnumDataChannelStateClosing[] = "closing"; +const char kEnumDataChannelStateConnecting[] = "connecting"; +const char kEnumDataChannelStateOpen[] = "open"; + +const char kClassName[] = "RTCDataChannel"; + +const char kAttributeNameLabel[] = "label"; +const char kAttributeNameOrdered[] = "ordered"; +const char kAttributeNameMaxPacketLifeTime[] = "maxPacketLifeTime"; +const char kAttributeNameMaxRetransmits[] = "maxRetransmits"; +const char kAttributeNameProtocol[] = "protocol"; +const char kAttributeNameNegotiated[] = "negotiated"; +const char kAttributeNameId[] = "id"; +const char kAttributeNameReadyState[] = "readyState"; +const char kAttributeNameBufferedAmount[] = "bufferedAmount"; +const char kAttributeNameBufferedAmountLowThreshold[] = "bufferedAmountLowThreshold"; +const char kAttributeNameBinaryType[] = "binaryType"; +const char kAttributeNameOnBufferedAmountLow[] = "onbufferedamountlow"; +const char kAttributeNameOnClose[] = "onclose"; +const char kAttributeNameOnClosing[] = "onclosing"; +const char kAttributeNameOnOpen[] = "onopen"; +const char kAttributeNameOnMessage[] = "onmessage"; +const char kAttributeNameOnError[] = "onerror"; + +const char kMethodNameClose[] = "close"; +const char kMethodNameSend[] = "send"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEventNameBufferedAmountLow[] = "bufferedamountlow"; +const char kEventNameClose[] = "close"; +const char kEventNameClosing[] = "closing"; +const char kEventNameOpen[] = "open"; +const char kEventNameMessage[] = "message"; +const char kEventNameError[] = "error"; + +std::unique_ptr NapiDataChannel::Observer::Create(DataChannelInterface* dataChannel) +{ + auto observer = std::unique_ptr(new Observer(dataChannel)); + return observer; +} + +NapiDataChannel::Observer::Observer(DataChannelInterface* dataChannel) + : dataChannel_(dataChannel), binaryType_(kEnumBinaryTypeBlob) +{ +} + +NapiDataChannel::Observer::~Observer() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } +} + +bool NapiDataChannel::Observer::GetEventHandler(const std::string& type, Napi::Function& fn) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + return false; + } + + fn = it->second.ref.Value(); + + return true; +} + +bool NapiDataChannel::Observer::SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver) +{ + Reference* context = new Reference; + *context = Persistent(receiver); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = ThreadSafeFunction::New( + fn.Env(), fn, type, 0, 1, context, [](Napi::Env /*env*/, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + + { + UNUSED std::lock_guard lock(mutex_); + eventHandlers_[type] = std::move(handler); + } + + if (type == kEventNameMessage) { + auto messages = GetAndClearPendingMessages(); + for (auto& message : messages) { + auto jsEvent = Object::New(fn.Env()); + if (message.binary) { + auto externalData = new rtc::CopyOnWriteBuffer(message.data); + auto arrayBuffer = ArrayBuffer::New( + fn.Env(), static_cast(externalData->MutableData()), externalData->size(), + [](Napi::Env /*env*/, void* /*data*/, rtc::CopyOnWriteBuffer* hint) { + RTC_DLOG(LS_VERBOSE) << "release rtc::CopyOnWriteBuffer"; + delete hint; + }, + externalData); + jsEvent.Set("data", arrayBuffer); + } else { + jsEvent.Set("data", String::New(fn.Env(), std::string(message.data.data(), message.size()))); + } + RTC_DLOG(LS_VERBOSE) << "trigger event: " << type; + fn.Call(receiver, {jsEvent}); + } + } + + return true; +} + +bool NapiDataChannel::Observer::RemoveEventHandler(const std::string& type) +{ + UNUSED std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + return false; + } + + it->second.tsfn.Release(); + eventHandlers_.erase(it); + + return true; +} + +uint64_t NapiDataChannel::Observer::GetBufferedAmountLowThreshold() const +{ + return bufferedAmountLowThreshold_.load(); +} + +void NapiDataChannel::Observer::SetBufferedAmountLowThreshold(uint64_t bufferedAmountLowThreshold) +{ + bufferedAmountLowThreshold_.store(bufferedAmountLowThreshold); +} + +std::string NapiDataChannel::Observer::GetBinaryType() const +{ + UNUSED std::lock_guard lock(mutex_); + return binaryType_; +} + +void NapiDataChannel::Observer::SetBinaryType(std::string binaryType) +{ + UNUSED std::lock_guard lock(mutex_); + binaryType_ = std::move(binaryType); +} + +std::vector NapiDataChannel::Observer::GetAndClearPendingMessages() +{ + UNUSED std::lock_guard lock(mutex_); + auto result = pendingMessages_; + pendingMessages_.clear(); + return result; +} + +void NapiDataChannel::Observer::OnStateChange() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + static std::map STATE_EVENT_MAP = { + {DataChannelInterface::kOpen, kEventNameOpen}, + {DataChannelInterface::kClosing, kEventNameClosing}, + {DataChannelInterface::kClosed, kEventNameClose}, + }; + + auto state = dataChannel_->state(); + auto eventType = STATE_EVENT_MAP[state]; + + UNUSED std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(eventType); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiDataChannel::Observer::OnMessage(const DataBuffer& buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto state = dataChannel_->state(); + if (state != DataChannelInterface::kOpen) { + RTC_LOG(LS_WARNING) << "Invalid state"; + return; + } + + UNUSED std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(kEventNameMessage); + if (it == eventHandlers_.end()) { + pendingMessages_.push_back(buffer); + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context, buffer](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + if (buffer.binary) { + auto externalData = new rtc::CopyOnWriteBuffer(buffer.data); + auto arrayBuffer = ArrayBuffer::New( + env, static_cast(externalData->MutableData()), externalData->size(), + [](Napi::Env /*env*/, void* /*data*/, rtc::CopyOnWriteBuffer* hint) { + RTC_DLOG(LS_VERBOSE) << "release rtc::CopyOnWriteBuffer"; + delete hint; + }, + externalData); + jsEvent.Set("data", arrayBuffer); + } else { + jsEvent.Set("data", String::New(env, std::string(buffer.data.data(), buffer.size()))); + } + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiDataChannel::Observer::OnBufferedAmountChange(uint64_t sent_data_size) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (sent_data_size > bufferedAmountLowThreshold_.load()) { + return; + } + + UNUSED std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameBufferedAmountLow); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +FunctionReference NapiDataChannel::constructor_; + +void NapiDataChannel::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiDataChannel::GetLabel>(kAttributeNameLabel), + InstanceAccessor<&NapiDataChannel::GetOrdered>(kAttributeNameOrdered), + InstanceAccessor<&NapiDataChannel::GetMaxPacketLifeTime>(kAttributeNameMaxPacketLifeTime), + InstanceAccessor<&NapiDataChannel::GetMaxRetransmits>(kAttributeNameMaxRetransmits), + InstanceAccessor<&NapiDataChannel::GetProtocol>(kAttributeNameProtocol), + InstanceAccessor<&NapiDataChannel::GetNegotiated>(kAttributeNameNegotiated), + InstanceAccessor<&NapiDataChannel::GetId>(kAttributeNameId), + InstanceAccessor<&NapiDataChannel::GetReadyState>(kAttributeNameReadyState), + InstanceAccessor<&NapiDataChannel::GetBufferedAmount>(kAttributeNameBufferedAmount), + InstanceAccessor< + &NapiDataChannel::GetBufferedAmountLowThreshold, &NapiDataChannel::SetBufferedAmountLowThreshold>( + kAttributeNameBufferedAmountLowThreshold), + InstanceAccessor<&NapiDataChannel::GetBinaryType, &NapiDataChannel::SetBinaryType>( + kAttributeNameBinaryType), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnBufferedAmountLow, napi_default, (void*)kEventNameBufferedAmountLow), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnClose, napi_default, (void*)kEventNameClose), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnClosing, napi_default, (void*)kEventNameClosing), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnOpen, napi_default, (void*)kEventNameOpen), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnMessage, napi_default, (void*)kEventNameMessage), + InstanceAccessor<&NapiDataChannel::GetEventHandler, &NapiDataChannel::SetEventHandler>( + kAttributeNameOnError, napi_default, (void*)kEventNameError), + InstanceMethod<&NapiDataChannel::Close>(kMethodNameClose), + InstanceMethod<&NapiDataChannel::Send>(kMethodNameSend), + InstanceMethod<&NapiDataChannel::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiDataChannel::NewInstance(Napi::Env env, rtc::scoped_refptr dataChannel) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto externalDataChannel = External::New( + env, dataChannel.release(), [](Napi::Env /*env*/, DataChannelInterface* dc) { + auto status = dc->Release(); + RTC_DLOG(LS_VERBOSE) << "DataChannelInterface release status=" << status; + }); + + return constructor_.New({externalDataChannel}); +} + +Napi::Object +NapiDataChannel::NewInstance(Napi::Env env, rtc::scoped_refptr dataChannel, Observer* observer) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto externalDataChannel = External::New( + env, dataChannel.release(), [](Napi::Env /*env*/, DataChannelInterface* dc) { + auto status = dc->Release(); + RTC_DLOG(LS_VERBOSE) << "DataChannelInterface release status=" << status; + }); + + auto externalObserver = External::New(env, observer); + + return constructor_.New({externalDataChannel, externalObserver}); +} + +NapiDataChannel::NapiDataChannel(const CallbackInfo& info) : ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr dc; + + if (info.Length() > 0 && info[0].IsExternal()) { + dc = info[0].As>().Data(); + } else { + // nothing to do now + } + + if (!dc) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + std::unique_ptr obs; + if (info.Length() > 1 && info[1].IsExternal()) { + obs = std::unique_ptr(info[1].As>().Data()); + } else { + obs = Observer::Create(dc.get()); + dc->RegisterObserver(obs.get()); + } + + if (!obs) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + dataChannel_ = dc; + observer_ = std::move(obs); +} + +NapiDataChannel::~NapiDataChannel() +{ + dataChannel_->UnregisterObserver(); +} + +rtc::scoped_refptr NapiDataChannel::Get() const +{ + return dataChannel_; +} + +// readonly label: string; +Napi::Value NapiDataChannel::GetLabel(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return String::New(info.Env(), dataChannel_->label()); +} + +// readonly ordered : boolean; +Napi::Value NapiDataChannel::GetOrdered(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return Boolean::New(info.Env(), dataChannel_->ordered()); +} + +// readonly maxPacketLifeTime ?: number; +Napi::Value NapiDataChannel::GetMaxPacketLifeTime(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + if (dataChannel_->maxPacketLifeTime()) { + return Number::New(info.Env(), dataChannel_->maxPacketLifeTime().value()); + } + return info.Env().Undefined(); +} + +// readonly maxRetransmits ?: number; +Napi::Value NapiDataChannel::GetMaxRetransmits(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + if (dataChannel_->maxRetransmitsOpt()) { + return Number::New(info.Env(), dataChannel_->maxRetransmitsOpt().value()); + } + return info.Env().Undefined(); +} + +// readonly protocol : string; +Napi::Value NapiDataChannel::GetProtocol(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return String::New(info.Env(), dataChannel_->protocol()); +} + +// readonly negotiated : boolean; +Napi::Value NapiDataChannel::GetNegotiated(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return Boolean::New(info.Env(), dataChannel_->negotiated()); +} + +// readonly id ?: number; +Napi::Value NapiDataChannel::GetId(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + if (dataChannel_->id() != -1) { + return Number::New(info.Env(), dataChannel_->id()); + } + return info.Env().Undefined(); +} + +// readonly readyState : DataChannelState; +Napi::Value NapiDataChannel::GetReadyState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (dataChannel_->state()) { + case DataChannelInterface::kConnecting: + return String::New(info.Env(), kEnumDataChannelStateConnecting); + case DataChannelInterface::kOpen: + return String::New(info.Env(), kEnumDataChannelStateOpen); + case DataChannelInterface::kClosing: + return String::New(info.Env(), kEnumDataChannelStateClosing); + case DataChannelInterface::kClosed: + return String::New(info.Env(), kEnumDataChannelStateClosed); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +// readonly bufferedAmount : number; +Napi::Value NapiDataChannel::GetBufferedAmount(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return Number::New(info.Env(), dataChannel_->buffered_amount()); +} + +// bufferedAmountLowThreshold : number; +Napi::Value NapiDataChannel::GetBufferedAmountLowThreshold(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return Number::New(info.Env(), observer_->GetBufferedAmountLowThreshold()); +} + +void NapiDataChannel::SetBufferedAmountLowThreshold(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!value.IsNumber()) { + NAPI_THROW_VOID(TypeError::New(info.Env(), "The argument is not number")); + } + + auto bufferedAmountLowThreshold = value.As().Int64Value(); + if (bufferedAmountLowThreshold < 0) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + observer_->SetBufferedAmountLowThreshold(bufferedAmountLowThreshold); +} + +// binaryType : BinaryType; +Napi::Value NapiDataChannel::GetBinaryType(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return String::New(info.Env(), observer_->GetBinaryType()); +} + +void NapiDataChannel::SetBinaryType(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!value.IsString()) { + NAPI_THROW_VOID(TypeError::New(info.Env(), "The argument is not string")); + } + + auto binaryType = value.As().Utf8Value(); + if (binaryType != kEnumBinaryTypeBlob && binaryType != kEnumBinaryTypeArrayBuffer) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + observer_->SetBinaryType(binaryType); +} + +Napi::Value NapiDataChannel::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Function fn; + if (observer_->GetEventHandler((const char*)info.Data(), fn)) { + return fn; + } + + return info.Env().Null(); +} + +void NapiDataChannel::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + const auto type = (const char*)info.Data(); + + observer_->RemoveEventHandler(type); + + if (value.IsFunction()) { + Function cb = value.As(); + if (!observer_->SetEventHandler(type, std::move(cb), info.This())) { + NAPI_THROW_VOID(Napi::Error::New(info.Env(), "Failed to set event handler")); + } + } +} + +Napi::Value NapiDataChannel::Close(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + dataChannel_->Close(); + return info.Env().Undefined(); +} + +Napi::Value NapiDataChannel::Send(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (dataChannel_->state() != DataChannelInterface::kOpen) { + NAPI_THROW(Error::New(info.Env(), "Datachannel state is not open"), info.Env().Undefined()); + } + + if (info[0].IsString()) { + RTC_DLOG(LS_VERBOSE) << "argument is string"; + auto jsStr = info[0].As(); + dataChannel_->SendAsync(DataBuffer(jsStr.Utf8Value()), [&](RTCError err) { + if (!err.ok()) { + RTC_LOG(LS_ERROR) << "send array buffer error: " << err.type() << ", " << err.message(); + } + }); + } else if (info[0].IsArrayBuffer()) { + RTC_DLOG(LS_VERBOSE) << "argument is array buffer"; + auto jsArrayBuffer = info[0].As(); + + auto data = jsArrayBuffer.Data(); + auto size = jsArrayBuffer.ByteLength(); + + dataChannel_->SendAsync(DataBuffer(rtc::CopyOnWriteBuffer((uint8_t*)data, size), true), [&](RTCError err) { + if (!err.ok()) { + RTC_LOG(LS_ERROR) << "send array buffer error: " << err.type() << ", " << err.message(); + } + }); + } else { + RTC_LOG(LS_WARNING) << "unknown type of argument"; + } + + return info.Env().Undefined(); +} + +Napi::Value NapiDataChannel::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiDataChannel"); +#endif + +return json; +} + +void JsToNativeDataChannelInit(const Napi::Object& jsDataChannelInit, DataChannelInit& init) +{ + if (jsDataChannelInit.Has(kAttributeNameOrdered)) { + auto jsOrdered = jsDataChannelInit.Get(kAttributeNameOrdered); + if (jsOrdered.IsBoolean()) { + init.ordered = jsOrdered.As().Value(); + } + } + + if (jsDataChannelInit.Has(kAttributeNameMaxPacketLifeTime)) { + auto jsMaxPacketLifeTime = jsDataChannelInit.Get(kAttributeNameMaxPacketLifeTime); + if (jsMaxPacketLifeTime.IsNumber()) { + init.maxRetransmitTime = jsMaxPacketLifeTime.As().Uint32Value(); + } + } + + if (jsDataChannelInit.Has(kAttributeNameMaxRetransmits)) { + auto jsMaxRetransmits = jsDataChannelInit.Get(kAttributeNameMaxRetransmits); + if (jsMaxRetransmits.IsNumber()) { + init.maxRetransmits = jsMaxRetransmits.As().Uint32Value(); + } + } + + if (jsDataChannelInit.Has(kAttributeNameProtocol)) { + auto jsProtocol = jsDataChannelInit.Get(kAttributeNameProtocol); + if (jsProtocol.IsString()) { + init.protocol = jsProtocol.As().Utf8Value(); + } + } + + if (jsDataChannelInit.Has(kAttributeNameNegotiated)) { + auto jsNegotiated = jsDataChannelInit.Get(kAttributeNameNegotiated); + if (jsNegotiated.IsBoolean()) { + init.negotiated = jsNegotiated.As().Value(); + } + } + + if (jsDataChannelInit.Has(kAttributeNameId)) { + auto jsId = jsDataChannelInit.Get(kAttributeNameId); + if (jsId.IsNumber()) { + init.id = jsId.As().Int32Value(); + } + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/data_channel.h b/sdk/ohos/src/ohos_webrtc/data_channel.h new file mode 100644 index 0000000000000000000000000000000000000000..750730d48b57aed447e62e1b3f4dfd23bf4845d7 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/data_channel.h @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_DATA_CHANNEL_H +#define WEBRTC_DATA_CHANNEL_H + +#include +#include +#include + +#include "napi.h" + +#include "api/data_channel_interface.h" + +namespace webrtc { + +class NapiDataChannel : public Napi::ObjectWrap { +public: + class Observer : public DataChannelObserver { + public: + // dataChannel should not be nullptr + static std::unique_ptr Create(DataChannelInterface* dataChannel); + + ~Observer(); + + bool GetEventHandler(const std::string& type, Napi::Function& fn); + bool SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver); + bool RemoveEventHandler(const std::string& type); + + uint64_t GetBufferedAmountLowThreshold() const; + void SetBufferedAmountLowThreshold(uint64_t bufferedAmountLowThreshold); + + std::string GetBinaryType() const; + void SetBinaryType(std::string binaryType); + + protected: + explicit Observer(DataChannelInterface* dataChannel); + + std::vector GetAndClearPendingMessages(); + + protected: + void OnStateChange() override; + void OnMessage(const DataBuffer& buffer) override; + void OnBufferedAmountChange(uint64_t sent_data_size) override; + + private: + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + DataChannelInterface* dataChannel_{}; + + mutable std::mutex mutex_; + std::map eventHandlers_; + std::atomic bufferedAmountLowThreshold_{0}; + std::string binaryType_; + std::vector pendingMessages_; + }; + + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr dataChannel); + static Napi::Object + NewInstance(Napi::Env env, rtc::scoped_refptr dataChannel, Observer* observer); + + explicit NapiDataChannel(const Napi::CallbackInfo& info); + ~NapiDataChannel() override; + + rtc::scoped_refptr Get() const; + +protected: + // Js + Napi::Value GetLabel(const Napi::CallbackInfo& info); + Napi::Value GetOrdered(const Napi::CallbackInfo& info); + Napi::Value GetMaxPacketLifeTime(const Napi::CallbackInfo& info); + Napi::Value GetMaxRetransmits(const Napi::CallbackInfo& info); + Napi::Value GetProtocol(const Napi::CallbackInfo& info); + Napi::Value GetNegotiated(const Napi::CallbackInfo& info); + Napi::Value GetId(const Napi::CallbackInfo& info); + Napi::Value GetReadyState(const Napi::CallbackInfo& info); + Napi::Value GetBufferedAmount(const Napi::CallbackInfo& info); + Napi::Value GetBufferedAmountLowThreshold(const Napi::CallbackInfo& info); + void SetBufferedAmountLowThreshold(const Napi::CallbackInfo& info, const Napi::Value& value); + Napi::Value GetBinaryType(const Napi::CallbackInfo& info); + void SetBinaryType(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value Close(const Napi::CallbackInfo& info); + Napi::Value Send(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr dataChannel_; + + std::unique_ptr observer_; +}; + +void JsToNativeDataChannelInit(const Napi::Object& jsDataChannelInit, DataChannelInit& init); + +} // namespace webrtc + +#endif // WEBRTC_DATA_CHANNEL_H diff --git a/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.cpp b/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dd995a3828a16321115652dcd71340a60b6843c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.cpp @@ -0,0 +1,293 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "desktop_capturer.h" +#include "../video/video_frame_receiver_native.h" +#include "../helper/native_window.h" +#include "../utils/marcos.h" + +#include + +namespace webrtc { + +OH_VideoSourceType PixelFormatToVideoSourceType(video::PixelFormat format) +{ + switch (format) { + case video::PixelFormat::RGBA: + return OH_VIDEO_SOURCE_SURFACE_RGBA; + default: + return OH_VIDEO_SOURCE_BUTT; + } +} + +std::unique_ptr DesktopCapturer::Create(video::VideoProfile profile) +{ + return std::unique_ptr(new DesktopCapturer(profile)); +} + +DesktopCapturer::DesktopCapturer(video::VideoProfile profile) + : profile_(profile), screenCapture_(ohos::AVScreenCapture::Create()) +{ +} + +DesktopCapturer::~DesktopCapturer() +{ + Release(); +} + +void DesktopCapturer::NotifyCapturedStart(bool success) +{ + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnCapturerStarted(success); + } +} + +void DesktopCapturer::NotifyCapturedStop() +{ + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnCapturerStopped(); + } +} + +void DesktopCapturer::Init(std::unique_ptr dataReceiver, Observer* observer) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + { + UNUSED std::lock_guard lock(obsMutex_); + observer_ = observer; + } + + dataReceiver_ = std::move(dataReceiver); + dataReceiver_->SetVideoFrameSize(profile_.resolution.width, profile_.resolution.height); + dataReceiver_->SetCallback(this); + + OH_AudioCaptureInfo micCapInfo{}; + micCapInfo.audioSampleRate = 16000; + micCapInfo.audioChannels = 2; + micCapInfo.audioSource = OH_MIC; + + OH_AudioInfo audioInfo{}; + audioInfo.micCapInfo = micCapInfo; + + OH_VideoCaptureInfo videoCapInfo{}; + videoCapInfo.videoFrameWidth = static_cast(profile_.resolution.width); + videoCapInfo.videoFrameHeight = static_cast(profile_.resolution.height); + videoCapInfo.videoSource = PixelFormatToVideoSourceType(profile_.format); + + OH_VideoInfo videoInfo{}; + videoInfo.videoCapInfo = videoCapInfo; + + OH_AVScreenCaptureConfig config{}; + config.captureMode = OH_CAPTURE_HOME_SCREEN; + config.dataType = OH_ORIGINAL_STREAM; + config.audioInfo = audioInfo; + config.videoInfo = videoInfo; + + OH_AVSCREEN_CAPTURE_ErrCode ret = OH_AVScreenCapture_Init(screenCapture_.Raw(), config); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to init: " << ret; + return; + } + + ret = OH_AVScreenCapture_SetMicrophoneEnabled(screenCapture_.Raw(), false); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set microphone enabled: " << ret; + return; + } + + ret = OH_AVScreenCapture_SetErrorCallback(screenCapture_.Raw(), DesktopCapturer::OnError1, this); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set error callback: " << ret; + return; + } + + ret = OH_AVScreenCapture_SetStateCallback(screenCapture_.Raw(), DesktopCapturer::OnStateChange1, this); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set state callback: " << ret; + return; + } + + ret = OH_AVScreenCapture_SetDataCallback(screenCapture_.Raw(), DesktopCapturer::OnBufferAvailable1, this); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set data callback: " << ret; + return; + } + + isInitialized_ = true; +} + +void DesktopCapturer::Release() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + screenCapture_.Reset(); + dataReceiver_.reset(); + + { + UNUSED std::lock_guard lock(obsMutex_); + observer_ = nullptr; + } +} + +void DesktopCapturer::Start() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (isStarted_) { + RTC_LOG(LS_WARNING) << "Capture is started"; + return; + } + + if (!isInitialized_) { + RTC_LOG(LS_ERROR) << "Not initialized"; + NotifyCapturedStart(false); + return; + } + + uint64_t surfaceId = dataReceiver_->GetSurfaceId(); + RTC_DLOG(LS_VERBOSE) << "surfaceId: " << surfaceId; + + OHNativeWindow* window = nullptr; + int32_t ret = OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, &window); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to create native window from surface id: " << ret; + return; + } + + ret = OH_AVScreenCapture_StartScreenCaptureWithSurface(screenCapture_.Raw(), window); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to start screen capture width surface: " << ret; + return; + } + + isStarted_ = true; +} + +void DesktopCapturer::Stop() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!isStarted_) { + RTC_LOG(LS_ERROR) << "Capture is not started"; + return; + } + + OH_AVSCREEN_CAPTURE_ErrCode ret = OH_AVScreenCapture_StopScreenCapture(screenCapture_.Raw()); + if (ret != AV_SCREEN_CAPTURE_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to stop screen capture: " << ret; + return; + } + + isStarted_ = false; +} + +bool DesktopCapturer::IsScreencast() +{ + return true; +} + +void DesktopCapturer::OnFrameAvailable( + rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(obsMutex_); + if (observer_) { + observer_->OnFrameCaptured(buffer, timestampUs, rotation); + } +} + +void DesktopCapturer::OnError1(OH_AVScreenCapture* capture, int32_t errorCode, void* userData) +{ + DesktopCapturer* self = (DesktopCapturer*)userData; + self->OnError(capture, errorCode); +} + +void DesktopCapturer::OnStateChange1( + struct OH_AVScreenCapture* capture, OH_AVScreenCaptureStateCode stateCode, void* userData) +{ + DesktopCapturer* self = (DesktopCapturer*)userData; + self->OnStateChange(capture, stateCode); +} + +void DesktopCapturer::OnBufferAvailable1( + OH_AVScreenCapture* capture, OH_AVBuffer* buffer, OH_AVScreenCaptureBufferType bufferType, int64_t timestamp, + void* userData) +{ + // surface模式时不触发 + DesktopCapturer* self = (DesktopCapturer*)userData; + self->OnBufferAvailable(capture, buffer, bufferType, timestamp); +} + +void DesktopCapturer::OnError(OH_AVScreenCapture* capture, int32_t errorCode) +{ + RTC_LOG(LS_ERROR) << "Error: " << errorCode; + (void)capture; +} + +void DesktopCapturer::OnStateChange(struct OH_AVScreenCapture* capture, OH_AVScreenCaptureStateCode stateCode) +{ + RTC_LOG(LS_INFO) << "State change: " << stateCode; + (void)capture; + + switch (stateCode) { + case OH_SCREEN_CAPTURE_STATE_STARTED: { + NotifyCapturedStart(true); + } break; + case OH_SCREEN_CAPTURE_STATE_CANCELED: { + NotifyCapturedStart(false); + } break; + case OH_SCREEN_CAPTURE_STATE_STOPPED_BY_USER: + case OH_SCREEN_CAPTURE_STATE_INTERRUPTED_BY_OTHER: { + NotifyCapturedStop(); + } break; + case OH_SCREEN_CAPTURE_STATE_STOPPED_BY_CALL: + // 通话中断状态处理 + break; + default: + break; + } +} + +void DesktopCapturer::OnBufferAvailable( + OH_AVScreenCapture* capture, OH_AVBuffer* buffer, OH_AVScreenCaptureBufferType bufferType, int64_t timestamp) +{ + RTC_DLOG(LS_VERBOSE) << "Buffer available: " << bufferType << ", " << timestamp; + (void)capture; + + OH_AVCodecBufferAttr attr; + OH_AVBuffer_GetBufferAttr(buffer, &attr); + RTC_DLOG(LS_VERBOSE) << "Buffer attr: offset=" << attr.offset << ", size=" << attr.size << ", pts=" << attr.pts + << ", flags=" << attr.flags; + + switch (bufferType) { + case OH_SCREEN_CAPTURE_BUFFERTYPE_AUDIO_INNER: + // 处理内录buffer + break; + case OH_SCREEN_CAPTURE_BUFFERTYPE_AUDIO_MIC: + // 处理麦克风buffer + break; + case OH_SCREEN_CAPTURE_BUFFERTYPE_VIDEO: + // 处理视频buffer + break; + default: + break; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.h b/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.h new file mode 100644 index 0000000000000000000000000000000000000000..0c220eb0aadeeb114428009931610c4cb8bbd782 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/desktop_capture/desktop_capturer.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_DESKTOP_CAPTURER_H +#define WEBRTC_DESKTOP_CAPTURER_H + +#include "../video/video_info.h" +#include "../video/video_capturer.h" +#include "../video/video_frame_receiver.h" +#include "../helper/screen_capture.h" + +#include +#include +#include + +#include +#include "api/video/video_frame_buffer.h" +#include "rtc_base/thread.h" + +namespace webrtc { + +class DesktopCapturer : public VideoCapturer, public VideoFrameReceiver::Callback { +public: + static std::unique_ptr Create(video::VideoProfile profile); + + ~DesktopCapturer() override; + +protected: + explicit DesktopCapturer(video::VideoProfile profile); + + void NotifyCapturedStart(bool success); + void NotifyCapturedStop(); + +protected: + void Init(std::unique_ptr, Observer* observer) override; + void Release() override; + + void Start() override; + void Stop() override; + + bool IsScreencast() override; + + void + OnFrameAvailable(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) override; + + static void OnError1(OH_AVScreenCapture* capture, int32_t errorCode, void* userData); + static void + OnStateChange1(struct OH_AVScreenCapture* capture, OH_AVScreenCaptureStateCode stateCode, void* userData); + static void OnBufferAvailable1( + OH_AVScreenCapture* capture, OH_AVBuffer* buffer, OH_AVScreenCaptureBufferType bufferType, int64_t timestamp, + void* userData); + + void OnError(OH_AVScreenCapture* capture, int32_t errorCode); + void OnStateChange(struct OH_AVScreenCapture* capture, OH_AVScreenCaptureStateCode stateCode); + void OnBufferAvailable( + OH_AVScreenCapture* capture, OH_AVBuffer* buffer, OH_AVScreenCaptureBufferType bufferType, int64_t timestamp); + +private: + const video::VideoProfile profile_; + + bool isInitialized_{false}; + bool isStarted_{false}; + + ohos::AVScreenCapture screenCapture_; + + std::unique_ptr dataReceiver_; + + Observer* observer_{}; + std::mutex obsMutex_; +}; + +} // namespace webrtc + +#endif // WEBRTC_DESKTOP_CAPTURER_H diff --git a/sdk/ohos/src/ohos_webrtc/dtls_transport.cpp b/sdk/ohos/src/ohos_webrtc/dtls_transport.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7fbcbcc2755e1e82678b72a90fb174d9235f565f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/dtls_transport.cpp @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dtls_transport.h" +#include "ice_transport.h" +#include "peer_connection.h" +#include "peer_connection_factory.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumDtlsTransportStateNew[] = "new"; +const char kEnumDtlsTransportStateConnecting[] = "connecting"; +const char kEnumDtlsTransportStateConnected[] = "connected"; +const char kEnumDtlsTransportStateClosed[] = "closed"; +const char kEnumDtlsTransportStateFailed[] = "failed"; + +const char kClassName[] = "RTCDtlsTransport"; + +const char kAttributeNameIceTransport[] = "iceTransport"; +const char kAttributeNameState[] = "state"; +const char kAttributeNameOnStateChange[] = "onstatechange"; +const char kAttributeNameOnError[] = "onerror"; + +const char kMethodNameGetRemoteCertificates[] = "getRemoteCertificates"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEventNameStateChange[] = "statechange"; +const char kEventNameError[] = "error"; + +FunctionReference NapiDtlsTransport::constructor_; + +void NapiDtlsTransport::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiDtlsTransport::GetIceTransport>(kAttributeNameIceTransport), + InstanceAccessor<&NapiDtlsTransport::GetState>(kAttributeNameState), + InstanceAccessor<&NapiDtlsTransport::GetEventHandler, &NapiDtlsTransport::SetEventHandler>( + kAttributeNameOnStateChange, napi_default, (void*)kEventNameStateChange), + InstanceAccessor<&NapiDtlsTransport::GetEventHandler, &NapiDtlsTransport::SetEventHandler>( + kAttributeNameOnError, napi_default, (void*)kEventNameError), + InstanceMethod<&NapiDtlsTransport::GetRemoteCertificates>(kMethodNameGetRemoteCertificates), + InstanceMethod<&NapiDtlsTransport::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiDtlsTransport::~NapiDtlsTransport() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + std::lock_guard lock(mutex_); + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } +} + +Napi::Object NapiDtlsTransport::NewInstance( + Napi::Env env, rtc::scoped_refptr dtlsTransport, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!dtlsTransport || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + auto externalTransport = External::New( + env, dtlsTransport.release(), [](Napi::Env env, DtlsTransportInterface* transport) { transport->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalTransport, externalPcWrapper}); +} + +NapiDtlsTransport::NapiDtlsTransport(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr dtlsTransport; + + if (info.Length() > 0 && info[0].IsExternal()) { + dtlsTransport = info[0].As>().Data(); + } else { + // nothing to do now + } + + if (!dtlsTransport) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + dtlsTransport_ = dtlsTransport; + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + } + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + factoryWrapper->GetNetworkThread()->PostTask([this] { dtlsTransport_->RegisterObserver(this); }); +} + +Napi::Value NapiDtlsTransport::GetIceTransport(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return NapiIceTransport::NewInstance(info.Env(), dtlsTransport_->ice_transport(), pcWrapper_); +} + +Napi::Value NapiDtlsTransport::GetState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto information = dtlsTransport_->Information(); + + switch (information.state()) { + case DtlsTransportState::kNew: + return String::New(info.Env(), kEnumDtlsTransportStateNew); + case DtlsTransportState::kConnecting: + return String::New(info.Env(), kEnumDtlsTransportStateConnecting); + case DtlsTransportState::kConnected: + return String::New(info.Env(), kEnumDtlsTransportStateConnected); + case DtlsTransportState::kClosed: + return String::New(info.Env(), kEnumDtlsTransportStateClosed); + case DtlsTransportState::kFailed: + return String::New(info.Env(), kEnumDtlsTransportStateFailed); + default: + break; + } + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiDtlsTransport::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find((const char*)info.Data()); + if (it == eventHandlers_.end()) { + return info.Env().Null(); + } + + return it->second.ref.Value(); +} + +void NapiDtlsTransport::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + const auto type = (const char*)info.Data(); + + { + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + it->second.tsfn.Release(); + eventHandlers_.erase(it); + } + } + + if (value.IsFunction()) { + auto fn = value.As(); + + Reference* context = new Reference; + *context = Persistent(info.This()); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = ThreadSafeFunction::New( + fn.Env(), fn, type, 0, 1, context, [](Napi::Env env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + std::lock_guard lock(mutex_); + eventHandlers_[type] = std::move(handler); + } else if (value.IsNull()) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " value is null"; + } else { + NAPI_THROW_VOID(Error::New(info.Env(), "value is error")); + } +} + +Napi::Value NapiDtlsTransport::GetRemoteCertificates(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto information = dtlsTransport_->Information(); + + auto certChain = information.remote_ssl_certificates(); + if (!certChain || certChain->GetSize() == 0) { + RTC_DLOG(LS_ERROR) << "Certificate chain is empty!"; + return info.Env().Null(); + } + + Napi::ArrayBuffer certArray = Napi::ArrayBuffer::New(info.Env(), certChain->GetSize()); + + for (size_t i = 0; i < certChain->GetSize(); i++) { + const rtc::SSLCertificate& certificate = certChain->Get(i); + certArray.Set(i, Napi::String::New(info.Env(), certificate.ToPEMString())); + } + + return certArray; +} + +Napi::Value NapiDtlsTransport::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiDtlsTransport"); +#endif + + return json; +} + +void NapiDtlsTransport::OnStateChange(DtlsTransportInformation info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameStateChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiDtlsTransport::OnError(RTCError error) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameError); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/dtls_transport.h b/sdk/ohos/src/ohos_webrtc/dtls_transport.h new file mode 100644 index 0000000000000000000000000000000000000000..d2898c7df9542be615f94abd0b07e25f67cf2228 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/dtls_transport.h @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_DTLS_TRANSPORT_H +#define WEBRTC_DTLS_TRANSPORT_H + +#include "napi.h" +#include +#include "api/dtls_transport_interface.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiDtlsTransport : public Napi::ObjectWrap, public DtlsTransportObserverInterface { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance( + Napi::Env env, rtc::scoped_refptr dtlsTransport, NapiPeerConnectionWrapper* pcWrapper); + + ~NapiDtlsTransport(); + +protected: + friend class ObjectWrap; + + explicit NapiDtlsTransport(const Napi::CallbackInfo& info); + +protected: + // JS + Napi::Value GetIceTransport(const Napi::CallbackInfo& info); + Napi::Value GetState(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value GetRemoteCertificates(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + void OnStateChange(DtlsTransportInformation info) override; + void OnError(RTCError error) override; + +private: + static Napi::FunctionReference constructor_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex mutex_; + std::map eventHandlers_; + + rtc::scoped_refptr dtlsTransport_; + NapiPeerConnectionWrapper* pcWrapper_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_DTLS_TRANSPORT_H diff --git a/sdk/ohos/src/ohos_webrtc/dtmf_sender.cpp b/sdk/ohos/src/ohos_webrtc/dtmf_sender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c2545d7bd8ae7661c66a94e981af845fa7c03b7 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/dtmf_sender.cpp @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dtmf_sender.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "RTCDTMFSender"; + +const char kAttributeNameCanInsertDTMF[] = "canInsertDTMF"; +const char kAttributeNameToneBuffer[] = "toneBuffer"; +const char kAttributeNameOnToneChange[] = "ontonechange"; + +const char kMethodNameInsertDTMF[] = "insertDTMF"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEventNameToneChange[] = "tonechange"; + +FunctionReference NapiDtmfSender::constructor_; + +void NapiDtmfSender::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiDtmfSender::GetCanInsertDTMF>(kAttributeNameCanInsertDTMF), + InstanceAccessor<&NapiDtmfSender::GetToneBuffer>(kAttributeNameToneBuffer), + InstanceAccessor<&NapiDtmfSender::GetEventHandler, &NapiDtmfSender::SetEventHandler>( + kAttributeNameOnToneChange, napi_default, (void*)kEventNameToneChange), + InstanceMethod<&NapiDtmfSender::InsertDTMF>(kMethodNameInsertDTMF), + InstanceMethod<&NapiDtmfSender::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiDtmfSender::~NapiDtmfSender() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + std::lock_guard lock(mutex_); + if (eventHandler_.tsfn) { + eventHandler_.tsfn.Release(); + } +} + +Napi::Object NapiDtmfSender::NewInstance(Napi::Env env, rtc::scoped_refptr dtmfSender) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!dtmfSender) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + auto external = External::New( + env, dtmfSender.release(), [](Napi::Env /*env*/, DtmfSenderInterface* sender) { sender->Release(); }); + + return constructor_.New({external}); +} + +NapiDtmfSender::NapiDtmfSender(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr dtmfSender; + + if (info.Length() > 0 && info[0].IsExternal()) { + dtmfSender = info[0].As>().Data(); + } else { + // nothing to do now + } + + if (!dtmfSender) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + dtmfSender_ = dtmfSender; + dtmfSender_->RegisterObserver(this); +} + +Napi::Value NapiDtmfSender::GetCanInsertDTMF(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto result = dtmfSender_->CanInsertDtmf(); + return Napi::Boolean::New(info.Env(), result); +} + +Napi::Value NapiDtmfSender::GetToneBuffer(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto tones = dtmfSender_->tones(); + return Napi::String::New(info.Env(), tones); +} + +Napi::Value NapiDtmfSender::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + if (eventHandler_.ref.IsEmpty()) { + return info.Env().Null(); + } + return eventHandler_.ref.Value(); +} + +void NapiDtmfSender::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + { + std::lock_guard lock(mutex_); + if (eventHandler_.tsfn) { + eventHandler_.tsfn.Release(); + } + } + + if (value.IsFunction()) { + auto fn = value.As(); + + Reference* context = new Reference; + *context = Persistent(info.This()); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = ThreadSafeFunction::New( + fn.Env(), fn, "SetEventHandler", 0, 1, context, [this](Napi::Env env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + std::lock_guard lock(mutex_); + eventHandler_ = std::move(handler); + } else if (value.IsNull()) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " value is null"; + } else { + NAPI_THROW_VOID(Error::New(info.Env(), "value is error")); + } +} + +Napi::Value NapiDtmfSender::InsertDTMF(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument types"), info.Env().Undefined()); + } + + if (!dtmfSender_->CanInsertDtmf()) { + NAPI_THROW(Error::New(info.Env(), "InvalidStateError"), info.Env().Undefined()); + } + + const std::string tones = info[0].As(); + + int duration = 100; + int inter_tone_gap = 70; + + if (info.Length() == 2) { + duration = info[1].As(); + if (duration > 6000) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " The value of duration is greater than 6000"; + duration = 6000; + } + if (duration < 40) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " The value of duration is less than 40"; + duration = 40; + } + } + if (info.Length() == 3) { + inter_tone_gap = info[2].As(); + if (inter_tone_gap < 30) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " The value of inter_tone_gap is less than 30"; + inter_tone_gap = 30; + } + if (inter_tone_gap > 6000) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " The value of inter_tone_gap is greater than 6000"; + inter_tone_gap = 6000; + } + } + + bool result = dtmfSender_->InsertDtmf(tones, duration, inter_tone_gap); + if (result) { + return Napi::Boolean::New(info.Env(), result); + } else { + NAPI_THROW(Error::New(info.Env(), "Failed to insert DTMF"), info.Env().Undefined()); + } +} + +Napi::Value NapiDtmfSender::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiDtmfSender"); +#endif + + return json; +} + +void NapiDtmfSender::OnToneChange(const std::string& tone, const std::string& tone_buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + auto& tsfn = eventHandler_.tsfn; + + if (!tsfn) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " ThreadSafeFunction is not initialized."; + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([tone, context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("tone", String::New(env, tone)); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/dtmf_sender.h b/sdk/ohos/src/ohos_webrtc/dtmf_sender.h new file mode 100644 index 0000000000000000000000000000000000000000..8c1a7d5c709d8ccf62802738c90c393887329d20 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/dtmf_sender.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_DTMF_SENDER_H +#define WEBRTC_DTMF_SENDER_H + +#include "napi.h" + +#include "api/dtmf_sender_interface.h" + +namespace webrtc { + +class NapiDtmfSender : public Napi::ObjectWrap, public DtmfSenderObserverInterface { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr dtmfSender); + + ~NapiDtmfSender(); + +protected: + friend class ObjectWrap; + + explicit NapiDtmfSender(const Napi::CallbackInfo& info); + +protected: + // JS + Napi::Value GetCanInsertDTMF(const Napi::CallbackInfo& info); + Napi::Value GetToneBuffer(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value InsertDTMF(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + void OnToneChange(const std::string& tone, const std::string& tone_buffer) override; + +private: + static Napi::FunctionReference constructor_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex mutex_; + EventHandler eventHandler_; + + rtc::scoped_refptr dtmfSender_; +}; + +} // namespace webrtc + +#endif // WEBRTC_DTMF_SENDER_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/avcodec.h b/sdk/ohos/src/ohos_webrtc/helper/avcodec.h new file mode 100644 index 0000000000000000000000000000000000000000..461f4313c4aaa031df358f71558ec80b2cff5ec9 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/avcodec.h @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_AVCODEC_H +#define WEBRTC_HELPER_AVCODEC_H + +#include "pointer_wrapper.h" +#include "error.h" + +#include +#include + +namespace ohos { + +class AVFormat : public PointerWrapper { +public: + static AVFormat Create(); + static AVFormat TakeOwnership(OH_AVFormat* format); + + AVFormat(); + explicit AVFormat(OH_AVFormat* format); + +private: + using PointerWrapper::PointerWrapper; +}; + +class AVCodec : public PointerWrapper { +public: + OH_AVErrCode SetCallback(OH_AVCodec* codec, OH_AVCodecAsyncCallback callback, void* userData); + +private: + using PointerWrapper::PointerWrapper; +}; + +class VideoEncoder : public AVCodec { +public: + static VideoEncoder CreateByName(const char* name); + + static VideoEncoder CreateByMime(const char* mime); + + static VideoEncoder TakeOwnership(OH_AVCodec* encoder); + + VideoEncoder(); + explicit VideoEncoder(OH_AVCodec* codec); + +private: + using AVCodec::AVCodec; +}; + +class VideoDecoder : public AVCodec { +public: + static VideoDecoder CreateByName(const char* name); + + static VideoDecoder CreateByMime(const char* mime); + + static VideoDecoder TakeOwnership(OH_AVCodec* decoder); + + VideoDecoder(); + explicit VideoDecoder(OH_AVCodec* codec); + +private: + using AVCodec::AVCodec; +}; + +inline AVFormat AVFormat::Create() +{ + OH_AVFormat* format = OH_AVFormat_Create(); + return TakeOwnership(format); +} + +inline AVFormat AVFormat::TakeOwnership(OH_AVFormat* format) +{ + NATIVE_THROW_IF_FAILED(format != nullptr, -1, "OH_AVCodec", "Invalid argument", AVFormat()); + return AVFormat(format, [](OH_AVFormat* format) { OH_AVFormat_Destroy(format); }); +} + +inline AVFormat::AVFormat() = default; + +inline AVFormat::AVFormat(OH_AVFormat* format) : PointerWrapper(format, NullDeleter) {} + +inline VideoEncoder VideoEncoder::CreateByName(const char* name) +{ + OH_AVCodec* encoder = OH_VideoEncoder_CreateByName(name); + return TakeOwnership(encoder); +} + +inline VideoEncoder VideoEncoder::CreateByMime(const char* mime) +{ + OH_AVCodec* encoder = OH_VideoEncoder_CreateByMime(mime); + return TakeOwnership(encoder); +} + +inline VideoEncoder VideoEncoder::TakeOwnership(OH_AVCodec* encoder) +{ + NATIVE_THROW_IF_FAILED(encoder != nullptr, -1, "OH_AVCodec", "Invalid argument", VideoEncoder()); + return VideoEncoder(encoder, [](OH_AVCodec* encoder) { + RTC_DLOG(LS_VERBOSE) << "Destroy video encoder: " << encoder; + if (encoder) { + OH_VideoEncoder_Destroy(encoder); + } + }); +} + +inline VideoEncoder::VideoEncoder() = default; + +inline VideoEncoder::VideoEncoder(OH_AVCodec* codec) : AVCodec(codec, NullDeleter) {} + +inline VideoDecoder VideoDecoder::CreateByName(const char* name) +{ + OH_AVCodec* decoder = OH_VideoDecoder_CreateByName(name); + return TakeOwnership(decoder); +} + +inline VideoDecoder VideoDecoder::CreateByMime(const char* mime) +{ + OH_AVCodec* decoder = OH_VideoDecoder_CreateByMime(mime); + return TakeOwnership(decoder); +} + +inline VideoDecoder VideoDecoder::TakeOwnership(OH_AVCodec* decoder) +{ + NATIVE_THROW_IF_FAILED(decoder != nullptr, -1, "OH_AVCodec", "Invalid argument", VideoDecoder()); + return VideoDecoder(decoder, [](OH_AVCodec* decoder) { + RTC_DLOG(LS_VERBOSE) << "Destroy video decoder: " << decoder; + if (decoder) { + OH_VideoDecoder_Destroy(decoder); + } + }); +} + +inline VideoDecoder::VideoDecoder() = default; + +inline VideoDecoder::VideoDecoder(OH_AVCodec* codec) : AVCodec(codec, NullDeleter) {} + +} // namespace ohos + +#endif // WEBRTC_HELPER_AVCODEC_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/camera.cpp b/sdk/ohos/src/ohos_webrtc/helper/camera.cpp new file mode 100644 index 0000000000000000000000000000000000000000..678b91b7c7da31176084db4a0aa03afad2ecfa23 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/camera.cpp @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "camera.h" + +namespace ohos { + +std::map> CameraPreviewOutput::observersMap_; +std::mutex CameraPreviewOutput::mutex_; + +void CameraPreviewOutput::AddObserver(Observer* observer) +{ + NATIVE_THROW_IF_FAILED(observer != nullptr, -1, "OH_Camera", "Null argument"); + + std::lock_guard lock(mutex_); + + auto& observers = observersMap_[Raw()]; + if (observers.size() == 0) { + PreviewOutput_Callbacks callback; + callback.onFrameStart = OnPreviewOutputFrameStart; + callback.onFrameEnd = OnPreviewOutputFrameEnd; + callback.onError = OnPreviewOutputError; + RegisterCallback(&callback); + } + + observers.push_back(observer); +} + +void CameraPreviewOutput::RemoveObserver(Observer* observer) +{ + std::lock_guard lock(mutex_); + + auto& observers = observersMap_[Raw()]; + if (observer) { + observers.clear(); + } else { + observers.remove(observer); + } + + if (observers.size() == 0) { + PreviewOutput_Callbacks callback; + callback.onFrameStart = OnPreviewOutputFrameStart; + callback.onFrameEnd = OnPreviewOutputFrameEnd; + callback.onError = OnPreviewOutputError; + UnregisterCallback(&callback); + } +} + +void CameraPreviewOutput::OnPreviewOutputFrameStart(Camera_PreviewOutput* previewOutput) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[previewOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnPreviewOutputFrameStart(); + } + } +} + +void CameraPreviewOutput::OnPreviewOutputFrameEnd(Camera_PreviewOutput* previewOutput, int32_t frameCount) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[previewOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnPreviewOutputFrameEnd(frameCount); + } + } +} + +void CameraPreviewOutput::OnPreviewOutputError(Camera_PreviewOutput* previewOutput, Camera_ErrorCode errorCode) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[previewOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnPreviewOutputError(errorCode); + } + } +} + +std::map> CameraVideoOutput::observersMap_; +std::mutex CameraVideoOutput::mutex_; + +void CameraVideoOutput::AddObserver(Observer* observer) +{ + NATIVE_THROW_IF_FAILED(observer != nullptr, -1, "OH_Camera", "Null argument"); + + std::lock_guard lock(mutex_); + + auto& observers = observersMap_[Raw()]; + if (observers.size() == 0) { + VideoOutput_Callbacks callback; + callback.onFrameStart = OnVideoOutputFrameStart; + callback.onFrameEnd = OnVideoOutputFrameEnd; + callback.onError = OnVideoOutputError; + RegisterCallback(&callback); + } + + observers.push_back(observer); +} + +void CameraVideoOutput::RemoveObserver(Observer* observer) +{ + std::lock_guard lock(mutex_); + + auto& observers = observersMap_[Raw()]; + if (observer) { + observers.clear(); + } else { + observers.remove(observer); + } + + if (observers.size() == 0) { + VideoOutput_Callbacks callback; + callback.onFrameStart = OnVideoOutputFrameStart; + callback.onFrameEnd = OnVideoOutputFrameEnd; + callback.onError = OnVideoOutputError; + UnregisterCallback(&callback); + } +} + +void CameraVideoOutput::OnVideoOutputFrameStart(Camera_VideoOutput* videoOutput) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[videoOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnVideoOutputFrameStart(); + } + } +} + +void CameraVideoOutput::OnVideoOutputFrameEnd(Camera_VideoOutput* videoOutput, int32_t frameCount) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[videoOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnVideoOutputFrameEnd(frameCount); + } + } +} + +void CameraVideoOutput::OnVideoOutputError(Camera_VideoOutput* videoOutput, Camera_ErrorCode errorCode) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observersMap_[videoOutput]; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnVideoOutputError(errorCode); + } + } +} + +void CameraManager::AddObserver(Observer* observer) +{ + std::lock_guard lock(mutex_); + observers_.push_back(observer); +} + +void CameraManager::RemoveObserver(Observer* observer) +{ + std::lock_guard lock(mutex_); + observers_.remove(observer); +} + +void CameraManager::OnCameraManagerStatusCallback1(Camera_Manager* cameraManager, Camera_StatusInfo* status) +{ + GetInstance().OnCameraManagerStatusCallback(status); +} + +void CameraManager::OnCameraManagerStatusCallback(Camera_StatusInfo* status) +{ + std::list observers; + { + std::lock_guard lock(mutex_); + observers = observers_; + } + + for (Observer* obs : observers) { + if (obs) { + obs->OnDeviceStatus(); + } + } +} + +} // namespace ohos diff --git a/sdk/ohos/src/ohos_webrtc/helper/camera.h b/sdk/ohos/src/ohos_webrtc/helper/camera.h new file mode 100644 index 0000000000000000000000000000000000000000..0bb8fc5b79c7eab6a97565e25fdba817b787dec8 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/camera.h @@ -0,0 +1,688 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_CAMERA_H +#define WEBRTC_HELPER_CAMERA_H + +#include "pointer_wrapper.h" +#include "error.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace ohos { + +class CameraDevices : public PointerWrapper { +public: + static CameraDevices TakeOwnership(Camera_Device* devices, std::size_t size); + + // empty + CameraDevices(); + CameraDevices(Camera_Device* devices, std::size_t size); + + Camera_Device* operator[](std::size_t index) const; + + std::size_t Size() const; + +protected: + CameraDevices(Camera_Device* devices, std::size_t size, DeleterType del); + +private: + std::size_t size_{}; + + using PointerWrapper::PointerWrapper; +}; + +class CameraSceneModes : public PointerWrapper { +public: + static CameraSceneModes TakeOwnership(Camera_SceneMode* modes, std::size_t size); + + CameraSceneModes(); + CameraSceneModes(Camera_SceneMode* modes, std::size_t size); + + Camera_SceneMode operator[](std::size_t index) const; + + std::size_t Size() const; + +protected: + CameraSceneModes(Camera_SceneMode* modes, std::size_t size, DeleterType del); + +private: + std::size_t size_{}; + + using PointerWrapper::PointerWrapper; +}; + +class CameraOutputCapability : public PointerWrapper { +public: + /** + * Take ownership of a specified Camera_OutputCapability pointer. + * @param ptr The pointer. Cannot be NULL. + * @return An CameraOutputCapability. + */ + static CameraOutputCapability TakeOwnership(Camera_OutputCapability* outputCapability); + + /** + * Create a new empty CameraOutputCapability. + */ + CameraOutputCapability(); + + /** + * Create an CameraOutputCapability by *NOT* taking ownership of an existing Camera_OutputCapability pointer. + * @param ptr The Camera_OutputCapability pointer. + * @warning The caller is still responsible for freeing the memory. + */ + explicit CameraOutputCapability(Camera_OutputCapability* outputCapability); + + uint32_t PreviewProfileSize() const; + uint32_t PhotoProfileSize() const; + uint32_t VideoProfileSize() const; + + Camera_Profile* GetPreviewProfile(uint32_t) const; + Camera_Profile* GetPhotoProfile(uint32_t index) const; + Camera_VideoProfile* GetVideoProfile(uint32_t index) const; + +private: + using PointerWrapper::PointerWrapper; +}; + +class CameraInput : public PointerWrapper { +public: + /** + * Take ownership of a specified Camera_Input pointer. + * @param ptr The pointer. Cannot be NULL. + * @return An CameraInput. + */ + static CameraInput TakeOwnership(Camera_Input* input); + + /** + * Create a new empty CameraInput. + */ + CameraInput(); + + /** + * Create an CameraInput by *NOT* taking ownership of an existing Camera_Input pointer. + * @param ptr The Camera_Input pointer. + * @warning The caller is still responsible for freeing the memory. + */ + explicit CameraInput(Camera_Input* input); + + bool Open(); + + bool Close(); + +private: + using PointerWrapper::PointerWrapper; +}; + +class CameraPreviewOutput : public PointerWrapper { +public: + class Observer { + public: + virtual ~Observer() {} + virtual void OnPreviewOutputFrameStart() = 0; + virtual void OnPreviewOutputFrameEnd(int32_t frameCount) = 0; + virtual void OnPreviewOutputError(Camera_ErrorCode errorCode) = 0; + }; + + /** + * Take ownership of a specified Camera_PreviewOutput pointer. + * @param ptr The pointer. Cannot be NULL. + * @return An CameraPreviewOutput. + */ + static CameraPreviewOutput TakeOwnership(Camera_PreviewOutput* output); + + /** + * Create a new empty CameraPreviewOutput. + */ + CameraPreviewOutput(); + + /** + * Create an CameraPreviewOutput by *NOT* taking ownership of an existing Camera_PreviewOutput pointer. + * @param ptr The Camera_PreviewOutput pointer. + * @warning The caller is still responsible for freeing the memory. + */ + explicit CameraPreviewOutput(Camera_PreviewOutput* output); + + void Start(); + void Stop(); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + +protected: + void RegisterCallback(PreviewOutput_Callbacks* callback); + void UnregisterCallback(PreviewOutput_Callbacks* callback); + +protected: + static void OnPreviewOutputFrameStart(Camera_PreviewOutput* previewOutput); + static void OnPreviewOutputFrameEnd(Camera_PreviewOutput* previewOutput, int32_t frameCount); + static void OnPreviewOutputError(Camera_PreviewOutput* previewOutput, Camera_ErrorCode errorCode); + +private: + static std::map> observersMap_; + static std::mutex mutex_; + + using PointerWrapper::PointerWrapper; +}; + +class CameraVideoOutput : public PointerWrapper { +public: + class Observer { + public: + virtual ~Observer() {} + virtual void OnVideoOutputFrameStart() = 0; + virtual void OnVideoOutputFrameEnd(int32_t frameCount) = 0; + virtual void OnVideoOutputError(Camera_ErrorCode errorCode) = 0; + }; + + /** + * Take ownership of a specified Camera_VideoOutput pointer. + * @param ptr The pointer. Cannot be NULL. + * @return An CameraVideoOutput. + */ + static CameraVideoOutput TakeOwnership(Camera_VideoOutput* videoOutput); + + /** + * Create a new empty CameraVideoOutput. + */ + CameraVideoOutput(); + + /** + * Create an CameraVideoOutput by *NOT* taking ownership of an existing Camera_VideoOutput pointer. + * @param ptr The Camera_VideoOutput pointer. + * @warning The caller is still responsible for freeing the memory. + */ + explicit CameraVideoOutput(Camera_VideoOutput* videoOutput); + + void Start(); + void Stop(); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + +protected: + void RegisterCallback(VideoOutput_Callbacks* callback); + void UnregisterCallback(VideoOutput_Callbacks* callback); + +protected: + static void OnVideoOutputFrameStart(Camera_VideoOutput* videoOutput); + static void OnVideoOutputFrameEnd(Camera_VideoOutput* videoOutput, int32_t frameCount); + static void OnVideoOutputError(Camera_VideoOutput* videoOutput, Camera_ErrorCode errorCode); + +private: + static std::map> observersMap_; + static std::mutex mutex_; + + using PointerWrapper::PointerWrapper; +}; + +class CameraCaptureSession : public PointerWrapper { +public: + /** + * Take ownership of a specified Camera_CaptureSession pointer. + * @param ptr The pointer. Cannot be NULL. + * @return An CameraCaptureSession. + */ + static CameraCaptureSession TakeOwnership(Camera_CaptureSession* session); + + /** + * Create a new empty CameraCaptureSession. + */ + CameraCaptureSession(); + + /** + * Create an CameraCaptureSession by *NOT* taking ownership of an existing Camera_CaptureSession pointer. + * @param ptr The Camera_CaptureSession pointer. + * @warning The caller is still responsible for freeing the memory. + */ + explicit CameraCaptureSession(Camera_CaptureSession* session); + + bool Start(); + + bool Stop(); + + bool BeginConfig(); + + bool CommitConfig(); + + bool AddInput(const CameraInput& input); + + bool AddPreviewOutput(const CameraPreviewOutput& output); + + bool AddVideoOutput(const CameraVideoOutput& output); + +private: + using PointerWrapper::PointerWrapper; +}; + +class CameraManager { +public: + class Observer { + public: + virtual ~Observer() {} + virtual void OnDeviceStatus() = 0; + }; + + static CameraManager& GetInstance(); + + ~CameraManager(); + + void AddObserver(Observer* observer); + + void RemoveObserver(Observer* observer); + + CameraDevices GetSupportedCameras(); + + CameraOutputCapability GetSupportedCameraOutputCapability(Camera_Device* cameraDevice); + + CameraSceneModes GetSupportedSceneModes(Camera_Device* device); + + CameraInput CreateCameraInput(Camera_Device* cameraDevice); + + CameraPreviewOutput CreatePreviewOutput(Camera_Profile* profile, const std::string& surfaceId); + + CameraVideoOutput CreateVideoOutput(Camera_VideoProfile* profile, const std::string& surfaceId); + + CameraCaptureSession CreateCaptureSession(); + + Camera_Manager* Raw() const; + +protected: + CameraManager(); + + static void OnCameraManagerStatusCallback1(Camera_Manager* cameraManager, Camera_StatusInfo* status); + void OnCameraManagerStatusCallback(Camera_StatusInfo* status); + +private: + Camera_Manager* manager_{}; + + std::list observers_; + std::mutex mutex_; +}; + +// implementations +inline CameraDevices CameraDevices::TakeOwnership(Camera_Device* devices, std::size_t size) +{ + NATIVE_THROW_IF_FAILED((devices != nullptr) && (size > 0), -1, "OH_Camera", "Invalid argument", CameraDevices()); + return CameraDevices(devices, size, [size](Camera_Device* devices) { + OH_CameraManager_DeleteSupportedCameras(CameraManager::GetInstance().Raw(), devices, size); + }); +} + +inline CameraDevices::CameraDevices() = default; + +inline CameraDevices::CameraDevices(Camera_Device* devices, std::size_t size) + : PointerWrapper(devices, NullDeleter), size_(size) +{ +} + +inline CameraDevices::CameraDevices(Camera_Device* devices, std::size_t size, DeleterType del) + : PointerWrapper(devices, del), size_(size) +{ +} + +inline Camera_Device* CameraDevices::operator[](std::size_t index) const +{ + // 此处不检查index的范围,由调用者保证 + return Raw() + index; +} + +inline std::size_t CameraDevices::Size() const +{ + return size_; +} + +inline CameraSceneModes CameraSceneModes::TakeOwnership(Camera_SceneMode* modes, std::size_t size) +{ + NATIVE_THROW_IF_FAILED((modes != nullptr) && (size > 0), -1, "OH_Camera", "Invalid argument", CameraSceneModes()); + return CameraSceneModes(modes, size, [](Camera_SceneMode* modes) { + OH_CameraManager_DeleteSceneModes(CameraManager::GetInstance().Raw(), modes); + }); +} + +inline CameraSceneModes::CameraSceneModes() = default; + +inline CameraSceneModes::CameraSceneModes(Camera_SceneMode* modes, std::size_t size) + : PointerWrapper(modes, NullDeleter), size_(size) +{ +} + +inline CameraSceneModes::CameraSceneModes(Camera_SceneMode* modes, std::size_t size, DeleterType del) + : PointerWrapper(modes, del), size_(size) +{ +} + +inline Camera_SceneMode CameraSceneModes::operator[](std::size_t index) const +{ + // 此处不检查index的范围,由调用者保证 + return Raw()[index]; +} + +inline std::size_t CameraSceneModes::Size() const +{ + return size_; +} + +inline CameraOutputCapability CameraOutputCapability::TakeOwnership(Camera_OutputCapability* outputCapability) +{ + NATIVE_THROW_IF_FAILED(outputCapability != nullptr, -1, "OH_Camera", "Null argument", CameraOutputCapability()); + return CameraOutputCapability(outputCapability, [](Camera_OutputCapability* capability) { + OH_CameraManager_DeleteSupportedCameraOutputCapability(CameraManager::GetInstance().Raw(), capability); + }); +} + +inline CameraOutputCapability::CameraOutputCapability() = default; + +inline CameraOutputCapability::CameraOutputCapability(Camera_OutputCapability* outputCapability) + : PointerWrapper(outputCapability, NullDeleter) +{ +} + +inline uint32_t CameraOutputCapability::PreviewProfileSize() const +{ + return Raw()->previewProfilesSize; +} + +inline uint32_t CameraOutputCapability::PhotoProfileSize() const +{ + return Raw()->photoProfilesSize; +} + +inline uint32_t CameraOutputCapability::VideoProfileSize() const +{ + return Raw()->videoProfilesSize; +} + +inline Camera_Profile* CameraOutputCapability::GetPreviewProfile(uint32_t index) const +{ + return Raw()->previewProfiles[index]; +} + +inline Camera_Profile* CameraOutputCapability::GetPhotoProfile(uint32_t index) const +{ + return Raw()->photoProfiles[index]; +} + +inline Camera_VideoProfile* CameraOutputCapability::GetVideoProfile(uint32_t index) const +{ + return Raw()->videoProfiles[index]; +} + +inline CameraInput CameraInput::TakeOwnership(Camera_Input* input) +{ + NATIVE_THROW_IF_FAILED(input != nullptr, -1, "OH_Camera", "Null argument", CameraInput()); + return CameraInput(input, [](Camera_Input* input) { OH_CameraInput_Release(input); }); +} + +inline CameraInput::CameraInput() = default; + +inline CameraInput::CameraInput(Camera_Input* input) : PointerWrapper(input, NullDeleter) {} + +inline bool CameraInput::Open() +{ + Camera_ErrorCode ret = OH_CameraInput_Open(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to open camera input", false); + return true; +} + +inline bool CameraInput::Close() +{ + Camera_ErrorCode ret = OH_CameraInput_Close(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to close camera input", false); + return true; +} + +inline CameraPreviewOutput CameraPreviewOutput::TakeOwnership(Camera_PreviewOutput* output) +{ + NATIVE_THROW_IF_FAILED(output != nullptr, -1, "OH_Camera", "Null argument", CameraPreviewOutput()); + return CameraPreviewOutput(output, [](Camera_PreviewOutput* output) { OH_PreviewOutput_Release(output); }); +} + +inline CameraPreviewOutput::CameraPreviewOutput() = default; + +inline CameraPreviewOutput::CameraPreviewOutput(Camera_PreviewOutput* output) : PointerWrapper(output, NullDeleter) {} + +inline void CameraPreviewOutput::Start() +{ + Camera_ErrorCode ret = OH_PreviewOutput_Start(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to start camera preview output"); +} + +inline void CameraPreviewOutput::Stop() +{ + Camera_ErrorCode ret = OH_PreviewOutput_Stop(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to stop camera preview output"); +} + +inline void CameraPreviewOutput::RegisterCallback(PreviewOutput_Callbacks* callback) +{ + Camera_ErrorCode ret = OH_PreviewOutput_RegisterCallback(Raw(), callback); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to register callback"); +} + +inline void CameraPreviewOutput::UnregisterCallback(PreviewOutput_Callbacks* callback) +{ + Camera_ErrorCode ret = OH_PreviewOutput_UnregisterCallback(Raw(), callback); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to unregister callback"); +} + +inline CameraVideoOutput CameraVideoOutput::TakeOwnership(Camera_VideoOutput* videoOutput) +{ + NATIVE_THROW_IF_FAILED(videoOutput != nullptr, -1, "OH_Camera", "Null argument", CameraVideoOutput()); + return CameraVideoOutput(videoOutput, [](Camera_VideoOutput* videoOutput) { OH_VideoOutput_Release(videoOutput); }); +} + +inline CameraVideoOutput::CameraVideoOutput() = default; + +inline CameraVideoOutput::CameraVideoOutput(Camera_VideoOutput* videoOutput) : PointerWrapper(videoOutput, NullDeleter) +{ +} + +inline void CameraVideoOutput::Start() +{ + Camera_ErrorCode ret = OH_VideoOutput_Start(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to start camera preview output"); +} + +inline void CameraVideoOutput::Stop() +{ + Camera_ErrorCode ret = OH_VideoOutput_Stop(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to stop camera preview output"); +} + +inline void CameraVideoOutput::RegisterCallback(VideoOutput_Callbacks* callback) +{ + Camera_ErrorCode ret = OH_VideoOutput_RegisterCallback(Raw(), callback); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to register callback"); +} + +inline void CameraVideoOutput::UnregisterCallback(VideoOutput_Callbacks* callback) +{ + Camera_ErrorCode ret = OH_VideoOutput_UnregisterCallback(Raw(), callback); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to unregister callback"); +} + +inline CameraCaptureSession CameraCaptureSession::TakeOwnership(Camera_CaptureSession* session) +{ + NATIVE_THROW_IF_FAILED(session != nullptr, -1, "OH_Camera", "Null argument", CameraCaptureSession()); + return CameraCaptureSession(session, [](Camera_CaptureSession* session) { OH_CaptureSession_Release(session); }); +} + +inline CameraCaptureSession::CameraCaptureSession() = default; + +inline CameraCaptureSession::CameraCaptureSession(Camera_CaptureSession* session) : PointerWrapper(session, NullDeleter) +{ +} + +inline bool CameraCaptureSession::Start() +{ + Camera_ErrorCode ret = OH_CaptureSession_Start(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to start capture session", false); + return true; +} + +inline bool CameraCaptureSession::Stop() +{ + Camera_ErrorCode ret = OH_CaptureSession_Stop(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to stop capture session", false); + return true; +} + +inline bool CameraCaptureSession::BeginConfig() +{ + Camera_ErrorCode ret = OH_CaptureSession_BeginConfig(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to begin capture session config", false); + return true; +} + +inline bool CameraCaptureSession::CommitConfig() +{ + Camera_ErrorCode ret = OH_CaptureSession_CommitConfig(Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to commit capture session config", false); + return true; +} + +inline bool CameraCaptureSession::AddInput(const CameraInput& input) +{ + Camera_ErrorCode ret = OH_CaptureSession_AddInput(Raw(), input.Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to add input to capture session", false); + return true; +} + +inline bool CameraCaptureSession::AddPreviewOutput(const CameraPreviewOutput& output) +{ + Camera_ErrorCode ret = OH_CaptureSession_AddPreviewOutput(Raw(), output.Raw()); + NATIVE_THROW_IF_FAILED( + ret == CAMERA_OK, ret, "OH_Camera", "Failed to add preview output to capture session", false); + return true; +} + +inline bool CameraCaptureSession::AddVideoOutput(const CameraVideoOutput& videoOutput) +{ + Camera_ErrorCode ret = OH_CaptureSession_AddVideoOutput(Raw(), videoOutput.Raw()); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to add video output to capture session", false); + return true; +} + +inline CameraManager& CameraManager::GetInstance() +{ + static CameraManager _manager; + return _manager; +} + +inline CameraManager::CameraManager() +{ + Camera_ErrorCode ret = OH_Camera_GetCameraManager(&manager_); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to get camera manager"); + + CameraManager_Callbacks callbacks; + callbacks.onCameraStatus = OnCameraManagerStatusCallback1; + ret = OH_CameraManager_RegisterCallback(manager_, &callbacks); + NATIVE_THROW_IF_FAILED_VOID(ret == CAMERA_OK, ret, "OH_Camera", "Failed to register camera manager callback"); +} + +inline CameraManager::~CameraManager() +{ + CameraManager_Callbacks callbacks; + callbacks.onCameraStatus = OnCameraManagerStatusCallback1; + Camera_ErrorCode ret = OH_CameraManager_UnregisterCallback(manager_, &callbacks); + if (ret != CAMERA_OK) { + NativeError::Create(ret, "OH_Camera", "Failed to unregister camera manager callback").PrintToLog(); + } + + ret = OH_Camera_DeleteCameraManager(manager_); + if (ret != CAMERA_OK) { + NativeError::Create(ret, "OH_Camera", "Failed to release camara manager").PrintToLog(); + } +} + +inline CameraDevices CameraManager::GetSupportedCameras() +{ + uint32_t devicesSize = 0; + Camera_Device* devices = nullptr; // point to an array of Camera_Device + Camera_ErrorCode ret = OH_CameraManager_GetSupportedCameras(Raw(), &devices, &devicesSize); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to get supported cameras", CameraDevices()); + return CameraDevices::TakeOwnership(devices, devicesSize); +} + +inline CameraOutputCapability CameraManager::GetSupportedCameraOutputCapability(Camera_Device* cameraDevice) +{ + Camera_OutputCapability* outputCapability = nullptr; + Camera_ErrorCode ret = OH_CameraManager_GetSupportedCameraOutputCapability(Raw(), cameraDevice, &outputCapability); + NATIVE_THROW_IF_FAILED( + ret == CAMERA_OK, ret, "OH_Camera", "Failed to get supported camera output capability", + CameraOutputCapability()); + return CameraOutputCapability::TakeOwnership(outputCapability); +} + +inline CameraSceneModes CameraManager::GetSupportedSceneModes(Camera_Device* device) +{ + uint32_t size = 0; + Camera_SceneMode* sceneModes = nullptr; + Camera_ErrorCode ret = OH_CameraManager_GetSupportedSceneModes(device, &sceneModes, &size); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to get supported cameras", CameraSceneModes()); + return CameraSceneModes::TakeOwnership(sceneModes, size); +} + +inline CameraInput CameraManager::CreateCameraInput(Camera_Device* cameraDevice) +{ + Camera_Input* input = nullptr; + Camera_ErrorCode ret = OH_CameraManager_CreateCameraInput(Raw(), cameraDevice, &input); + NATIVE_THROW_IF_FAILED(ret == CAMERA_OK, ret, "OH_Camera", "Failed to create camera input", CameraInput()); + return CameraInput::TakeOwnership(input); +} + +inline CameraPreviewOutput CameraManager::CreatePreviewOutput(Camera_Profile* profile, const std::string& surfaceId) +{ + Camera_PreviewOutput* output = nullptr; + Camera_ErrorCode ret = OH_CameraManager_CreatePreviewOutput(Raw(), profile, surfaceId.c_str(), &output); + NATIVE_THROW_IF_FAILED( + ret == CAMERA_OK, ret, "OH_Camera", "Failed to create preview output", CameraPreviewOutput()); + return CameraPreviewOutput(output); +} + +inline CameraVideoOutput CameraManager::CreateVideoOutput(Camera_VideoProfile* profile, const std::string& surfaceId) +{ + Camera_VideoOutput* output = nullptr; + Camera_ErrorCode ret = OH_CameraManager_CreateVideoOutput(Raw(), profile, surfaceId.c_str(), &output); + NATIVE_THROW_IF_FAILED( + ret == CAMERA_OK, ret, "OH_Camera", "Failed to create preview output", CameraVideoOutput()); + return CameraVideoOutput(output); +} + +inline CameraCaptureSession CameraManager::CreateCaptureSession() +{ + Camera_CaptureSession* session = nullptr; + Camera_ErrorCode ret = OH_CameraManager_CreateCaptureSession(Raw(), &session); + NATIVE_THROW_IF_FAILED( + ret == CAMERA_OK, ret, "OH_Camera", "Failed to create capture session", CameraCaptureSession()); + return CameraCaptureSession(session); +} + +inline Camera_Manager* CameraManager::Raw() const +{ + return manager_; +} + +} // namespace ohos + +#endif // WEBRTC_HELPER_CAMERA_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/error.h b/sdk/ohos/src/ohos_webrtc/helper/error.h new file mode 100644 index 0000000000000000000000000000000000000000..c2557cc7b3aa83dc2ad36fc1e96ced1829d06bff --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/error.h @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_ERROR_H +#define WEBRTC_HELPER_ERROR_H + +#include "rtc_base/logging.h" + +#if !defined(NDK_HELPER_CPP_EXCEPTIONS) && !defined(NDK_HELPER_DISABLE_CPP_EXCEPTIONS) +#if defined(__EXCEPTIONS) +#define NDK_HELPER_CPP_EXCEPTIONS +#else +#error Exception support not detected. \ + Define either NDK_HELPER_CPP_EXCEPTIONS or NDK_HELPER_DISABLE_CPP_EXCEPTIONS. +#endif +#endif + +#include +#ifdef NDK_HELPER_CPP_EXCEPTIONS +#include +#endif // NDK_HELPER_CPP_EXCEPTIONS + +#ifdef NDK_HELPER_CPP_EXCEPTIONS + +#define NATIVE_THROW(e, ...) throw (e) +#define NATIVE_THROW_VOID(e) throw (e) + +#define NATIVE_THROW_IF_FAILED(condition, code, domain, message, ...) \ + if (!(condition)) { \ + throw NativeError::Create(code, domain, message); \ + } + +#define NATIVE_THROW_IF_FAILED_VOID(condition, code, domain, message) \ + if (!(condition)) { \ + throw NativeError::Create(code, domain, message); \ + } + +#else // NDK_HELPER_CPP_EXCEPTIONS + +#define NATIVE_THROW(e, ...) \ + do { \ + NativeError::ThrowAsNativeException(e); \ + return __VA_ARGS__; \ + } while (0) + +#define NATIVE_THROW_VOID(e) \ + do { \ + NativeError::ThrowAsNativeException(e); \ + return; \ + } while (0) + +#define NATIVE_THROW_IF_FAILED(condition, code, domain, message, ...) \ + if (!(condition)) { \ + NativeError::ThrowAsNativeException(NativeError::Create(code, domain, message)); \ + return __VA_ARGS__; \ + } + +#define NATIVE_THROW_IF_FAILED_VOID(condition, code, domain, message) \ + if (!(condition)) { \ + NativeError::ThrowAsNativeException(NativeError::Create(code, domain, message)); \ + return; \ + } + +#endif // __EXCEPTIONS + +namespace ohos { + +void ThrowError(const char* message); + +class NativeError +#ifdef NDK_HELPER_CPP_EXCEPTIONS + : public std::exception +#endif // NDK_HELPER_CPP_EXCEPTIONS +{ +public: + static NativeError Create(const char* domain, const char* message); + static NativeError Create(const std::string& domain, const std::string& message); + static NativeError Create(int32_t code, const char* domain, const char* message); + static NativeError Create(int32_t code, const std::string& domain, const std::string& message); + + static bool HasPendingException(); + static NativeError GetAndClearPendingException(); + static void ThrowAsNativeException(NativeError&& e); + + int32_t Code() const; + const std::string& Domain() const; + + void PrintToLog() const; + +#ifdef NDK_HELPER_CPP_EXCEPTIONS + const char* what() const noexcept override; +#else + const char* what() const noexcept; +#endif // NDK_HELPER_CPP_EXCEPTIONS + +protected: + NativeError(int32_t code, const char* domain, const char* message); + NativeError(int32_t code, const std::string& domain, const std::string& message); + +private: + const int32_t code_{-1}; + const std::string domain_; + const std::string message_; + + static thread_local std::unique_ptr error_; +}; + +inline thread_local std::unique_ptr NativeError::error_; + +inline NativeError NativeError::Create(const char* domain, const char* message) +{ + return NativeError(0, domain, message); +} + +inline NativeError NativeError::Create(const std::string& domain, const std::string& message) +{ + return NativeError(0, domain, message); +} + +inline NativeError NativeError::Create(int32_t code, const char* domain, const char* message) +{ + return NativeError(code, domain, message); +} + +inline NativeError NativeError::Create(int32_t code, const std::string& domain, const std::string& message) +{ + return NativeError(code, domain, message); +} + +inline bool NativeError::HasPendingException() +{ + return static_cast(error_); +} + +inline NativeError NativeError::GetAndClearPendingException() +{ + RTC_CHECK(HasPendingException()); + return *error_.release(); +} + +inline void NativeError::ThrowAsNativeException(NativeError&& e) +{ + error_ = std::make_unique(e); +} + +inline NativeError::NativeError(int32_t code, const char* domain, const char* message) + : code_(code), domain_(domain), message_(message) +{ +} + +inline NativeError::NativeError(int32_t code, const std::string& domain, const std::string& message) + : code_(code), domain_(domain), message_(message) +{ +} + +inline int32_t NativeError::Code() const +{ + return code_; +} + +inline const std::string& NativeError::Domain() const +{ + return domain_; +} + +inline void NativeError::PrintToLog() const +{ + RTC_LOG(LS_ERROR) << "NativeError: " << code_ << "-" << domain_ << ", " << what(); +} + +inline const char* NativeError::what() const noexcept +{ + return message_.c_str(); +} + +} // namespace ohos + +#endif // WEBRTC_HELPER_ERROR_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/native_buffer.h b/sdk/ohos/src/ohos_webrtc/helper/native_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..e6c5159701c58dcc45ad0d6e0bb655744bda9d6f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/native_buffer.h @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_NATIVE_BUFFER_H +#define WEBRTC_HELPER_NATIVE_BUFFER_H + +#include "pointer_wrapper.h" +#include "error.h" + +#include +#include + +namespace ohos { + +class NativeBuffer : public PointerWrapper { +public: + static NativeBuffer Create(const OH_NativeBuffer_Config* config); + + static NativeBuffer Create(int32_t width, int32_t height, int32_t format, int32_t usage, int32_t stride); + + /** + * Adds the reference count of a OH_NativeBuffer, and take its ownership. + * @param buffer Indicates the pointer to a OH_NativeBuffer instance. + * @return An NativeBuffer. + */ + static NativeBuffer Reference(OH_NativeBuffer* buffer); + + static NativeBuffer TakeOwnership(OH_NativeBuffer* buffer); + + static NativeBuffer From(OHNativeWindowBuffer* nativeWindowBuffer); + + NativeBuffer(); + explicit NativeBuffer(OH_NativeBuffer*); + + OH_NativeBuffer_Config GetConfig() const; + + void* Map() const; + void Unmap() const; + + uint32_t GetSeqNum() const; + +private: + using PointerWrapper::PointerWrapper; +}; + +inline NativeBuffer NativeBuffer::Create(const OH_NativeBuffer_Config* config) +{ + OH_NativeBuffer* buffer = OH_NativeBuffer_Alloc(config); + NATIVE_THROW_IF_FAILED(buffer != nullptr, -1, "OH_NativeBuffer", "Failed to alloc native buffer", NativeBuffer()); + return NativeBuffer::TakeOwnership(buffer); +} + +inline NativeBuffer NativeBuffer::Create(int32_t width, int32_t height, int32_t format, int32_t usage, int32_t stride) +{ + OH_NativeBuffer_Config config{width, height, format, usage, stride}; + OH_NativeBuffer* buffer = OH_NativeBuffer_Alloc(&config); + NATIVE_THROW_IF_FAILED(buffer != nullptr, -1, "OH_NativeBuffer", "Failed to alloc native buffer", NativeBuffer()); + return NativeBuffer::TakeOwnership(buffer); +} + +inline NativeBuffer NativeBuffer::Reference(OH_NativeBuffer* buffer) +{ + int32_t ret = OH_NativeBuffer_Reference(buffer); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeBuffer", "Failed to alloc native buffer", NativeBuffer()); + return NativeBuffer::TakeOwnership(buffer); +} + +inline NativeBuffer NativeBuffer::TakeOwnership(OH_NativeBuffer* buffer) +{ + NATIVE_THROW_IF_FAILED(buffer != nullptr, -1, "OH_NativeBuffer", "Invalid argument", NativeBuffer()); + return NativeBuffer(buffer, [](OH_NativeBuffer* buffer) { OH_NativeBuffer_Unreference(buffer); }); +} + +inline NativeBuffer NativeBuffer::From(OHNativeWindowBuffer* nativeWindowBuffer) +{ + NATIVE_THROW_IF_FAILED(nativeWindowBuffer != nullptr, -1, "OH_NativeBuffer", "Invalid argument", NativeBuffer()); + OH_NativeBuffer* buffer = nullptr; + int32_t ret = OH_NativeBuffer_FromNativeWindowBuffer(nativeWindowBuffer, &buffer); + NATIVE_THROW_IF_FAILED( + ret == 0, ret, "OH_NativeBuffer", "Failed to get native buffer from native window buffer", NativeBuffer()); + return NativeBuffer(buffer); +} + +inline NativeBuffer::NativeBuffer() = default; + +inline NativeBuffer::NativeBuffer(OH_NativeBuffer* buffer) : PointerWrapper(buffer, NullDeleter) {} + +inline OH_NativeBuffer_Config NativeBuffer::GetConfig() const +{ + OH_NativeBuffer_Config config; + OH_NativeBuffer_GetConfig(Raw(), &config); + return config; +} + +inline void* NativeBuffer::Map() const +{ + void* addr = nullptr; + int32_t ret = OH_NativeBuffer_Map(Raw(), &addr); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeBuffer", "Failed to alloc native buffer", nullptr); + return addr; +} + +inline void NativeBuffer::Unmap() const +{ + int32_t ret = OH_NativeBuffer_Unmap(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeBuffer", "Failed to alloc native buffer"); +} + +inline uint32_t NativeBuffer::GetSeqNum() const +{ + return OH_NativeBuffer_GetSeqNum(Raw()); +} + +} // namespace ohos + +#endif // WEBRTC_HELPER_NATIVE_BUFFER_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/native_image.h b/sdk/ohos/src/ohos_webrtc/helper/native_image.h new file mode 100644 index 0000000000000000000000000000000000000000..d25886cf02c2a42602b7440a4b226cccf32ead3c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/native_image.h @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_NATIVE_IMAGE_H +#define WEBRTC_HELPER_NATIVE_IMAGE_H + +#include "pointer_wrapper.h" +#include "native_window.h" +#include "error.h" + +#include +#include +#include + +namespace ohos { + +class NativeImage : public PointerWrapper { +public: + static NativeImage Create(uint32_t textureId, uint32_t textureTarget); + static NativeImage TakeOwnership(OH_NativeImage* image); + + NativeImage(); + explicit NativeImage(OH_NativeImage* image); + + NativeWindow AcquireNativeWindow() const; + void AttachContext(uint32_t textureId); + void DetachContext(); + void UpdateSurfaceImage(); + int64_t GetTimestamp(); + std::array GetTransformMatrix(); + uint64_t GetSurfaceId() const; + void SetOnFrameAvailableListener(OH_OnFrameAvailableListener listener); + void UnsetOnFrameAvailableListener(); + std::array GetTransformMatrixV2(); + OHNativeWindowBuffer* AcquireNativeWindowBuffer(int* fenceFd); + void ReleaseNativeWindowBuffer(OHNativeWindowBuffer* nativeWindowBuffer, int fenceFd); + +private: + using PointerWrapper::PointerWrapper; +}; + +inline NativeImage NativeImage::Create(uint32_t textureId, uint32_t textureTarget) +{ + OH_NativeImage* image = OH_NativeImage_Create(textureId, textureTarget); + NATIVE_THROW_IF_FAILED(image != nullptr, -1, "OH_NativeImage", "Failed to create native image", NativeImage()); + return NativeImage(image, [](OH_NativeImage* image) { OH_NativeImage_Destroy(&image); }); +} + +inline NativeImage NativeImage::TakeOwnership(OH_NativeImage* image) +{ + NATIVE_THROW_IF_FAILED(image != nullptr, -1, "OH_NativeImage", "Null native image", NativeImage()); + return NativeImage(image, [](OH_NativeImage* image) { OH_NativeImage_Destroy(&image); }); +} + +inline NativeImage::NativeImage() = default; + +inline NativeImage::NativeImage(OH_NativeImage* image) : PointerWrapper(image, NullDeleter) {} + +inline NativeWindow NativeImage::AcquireNativeWindow() const +{ + OHNativeWindow* window = OH_NativeImage_AcquireNativeWindow(Raw()); + NATIVE_THROW_IF_FAILED(window != nullptr, -1, "OH_NativeImage", "Failed to acquire native window", NativeWindow()); + return NativeWindow(window); +} + +inline void NativeImage::AttachContext(uint32_t textureId) +{ + int32_t ret = OH_NativeImage_AttachContext(Raw(), textureId); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to attach context"); +} + +inline void NativeImage::DetachContext() +{ + int32_t ret = OH_NativeImage_DetachContext(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to detach context"); +} + +inline void NativeImage::UpdateSurfaceImage() +{ + int32_t ret = OH_NativeImage_UpdateSurfaceImage(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to update surface image"); +} + +inline int64_t NativeImage::GetTimestamp() +{ + return OH_NativeImage_GetTimestamp(Raw()); +} + +inline std::array NativeImage::GetTransformMatrix() +{ + std::array matrix; + int32_t ret = OH_NativeImage_GetTransformMatrix(Raw(), matrix.data()); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeImage", "Failed to get transform matrix", matrix); + return matrix; +} + +inline uint64_t NativeImage::GetSurfaceId() const +{ + uint64_t surfaceId = 0; + int32_t ret = OH_NativeImage_GetSurfaceId(Raw(), &surfaceId); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeImage", "Failed to get surface id", surfaceId); + return surfaceId; +} + +inline void NativeImage::SetOnFrameAvailableListener(OH_OnFrameAvailableListener listener) +{ + int32_t ret = OH_NativeImage_SetOnFrameAvailableListener(Raw(), listener); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to set on frame available listener"); +} + +inline void NativeImage::UnsetOnFrameAvailableListener() +{ + int32_t ret = OH_NativeImage_UnsetOnFrameAvailableListener(Raw()); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to unset on frame available listener"); +} + +inline std::array NativeImage::GetTransformMatrixV2() +{ + std::array matrix; + int32_t ret = OH_NativeImage_GetTransformMatrixV2(Raw(), matrix.data()); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeImage", "Failed to get transform matrix (V2)", matrix); + return matrix; +} + +inline OHNativeWindowBuffer* NativeImage::AcquireNativeWindowBuffer(int* fenceFd) +{ + OHNativeWindowBuffer* buffer = nullptr; + int32_t ret = OH_NativeImage_AcquireNativeWindowBuffer(Raw(), &buffer, fenceFd); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeImage", "Failed to acquire native window buffer", buffer); + ret = OH_NativeWindow_NativeObjectReference(buffer); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OH_NativeImage", "Failed to acquire native window buffer", buffer); + return buffer; +} + +inline void NativeImage::ReleaseNativeWindowBuffer(OHNativeWindowBuffer* nativeWindowBuffer, int fenceFd) +{ + int32_t ret = OH_NativeImage_ReleaseNativeWindowBuffer(Raw(), nativeWindowBuffer, fenceFd); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OH_NativeImage", "Failed to release native window buffer"); +} + +} // namespace ohos + +#endif // WEBRTC_HELPER_NATIVE_IMAGE_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/native_window.h b/sdk/ohos/src/ohos_webrtc/helper/native_window.h new file mode 100644 index 0000000000000000000000000000000000000000..5cd13622add82b86657c6f69af9d592564cda74d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/native_window.h @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_NATIVE_WINDOW_H +#define WEBRTC_HELPER_NATIVE_WINDOW_H + +#include "pointer_wrapper.h" +#include "error.h" + +#include + +#include + +namespace ohos { + +class NativeWindowBuffer : public PointerWrapper { +public: + static NativeWindowBuffer CreateFromSurfaceBuffer(void* pSurfaceBuffer); + + static NativeWindowBuffer CreateFromNativeBuffer(OH_NativeBuffer* nativeBuffer); + + static NativeWindowBuffer TakeOwnership(OHNativeWindowBuffer* buffer); + + NativeWindowBuffer(); + explicit NativeWindowBuffer(OHNativeWindowBuffer* buffer); + +private: + using PointerWrapper::PointerWrapper; +}; + +class NativeWindow : public PointerWrapper { +public: + static NativeWindow CreateFromSurfaceId(uint64_t surfaceId); + + static NativeWindow TakeOwnership(OHNativeWindow* window); + + NativeWindow(); + explicit NativeWindow(OHNativeWindow* window); + + uint64_t GetSurfaceId() const; + + NativeWindowBuffer RequestBuffer(bool wait = false); + + void FlushBuffer(OHNativeWindowBuffer* buffer, int fenceFd = -1); + + void AbortBuffer(OHNativeWindowBuffer* buffer); + +private: + using PointerWrapper::PointerWrapper; +}; + +inline NativeWindowBuffer NativeWindowBuffer::TakeOwnership(OHNativeWindowBuffer* buffer) +{ + NATIVE_THROW_IF_FAILED(buffer != nullptr, -1, "OHNativeWindow", "Null argument", NativeWindowBuffer()); + return NativeWindowBuffer( + buffer, [](OHNativeWindowBuffer* buffer) { OH_NativeWindow_DestroyNativeWindowBuffer(buffer); }); +} + +inline NativeWindowBuffer::NativeWindowBuffer() = default; + +inline NativeWindowBuffer::NativeWindowBuffer(OHNativeWindowBuffer* buffer) : PointerWrapper(buffer, NullDeleter) {} + +inline NativeWindow NativeWindow::CreateFromSurfaceId(uint64_t surfaceId) +{ + OHNativeWindow* window = nullptr; + int32_t ret = OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, &window); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OHNativeWindow", "Failed to create native window", NativeWindow()); + return NativeWindow::TakeOwnership(window); +} + +inline NativeWindow NativeWindow::TakeOwnership(OHNativeWindow* window) +{ + NATIVE_THROW_IF_FAILED(window != nullptr, -1, "OHNativeWindow", "Null argument", NativeWindow()); + return NativeWindow(window, [](OHNativeWindow* window) { OH_NativeWindow_DestroyNativeWindow(window); }); +} + +inline NativeWindow::NativeWindow() = default; + +inline NativeWindow::NativeWindow(OHNativeWindow* window) : PointerWrapper(window, NullDeleter) {} + +inline uint64_t NativeWindow::GetSurfaceId() const +{ + uint64_t surfaceId = 0; + int32_t ret = OH_NativeWindow_GetSurfaceId(Raw(), &surfaceId); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OHNativeWindow", "Failed to get surface id", 0); + return surfaceId; +} + +inline NativeWindowBuffer NativeWindow::RequestBuffer(bool wait) +{ + OHNativeWindowBuffer* windowBuffer = nullptr; + int releaseFenceFd = -1; + int32_t ret = OH_NativeWindow_NativeWindowRequestBuffer(Raw(), &windowBuffer, &releaseFenceFd); + NATIVE_THROW_IF_FAILED(ret == 0, ret, "OHNativeWindow", "Failed to request buffer", NativeWindowBuffer()); + + if (releaseFenceFd != -1) { + if (wait) { + int retCode = -1; + uint32_t timeout = 3000; + struct pollfd fds = {0}; + fds.fd = releaseFenceFd; + fds.events = POLLIN; + + do { + retCode = poll(&fds, 1, timeout); + } while (retCode == -1 && (errno == EINTR || errno == EAGAIN)); + } + + close(releaseFenceFd); + } + + return NativeWindowBuffer(windowBuffer); +} + +inline void NativeWindow::FlushBuffer(OHNativeWindowBuffer* buffer, int fenceFd) +{ + Region region{nullptr, 0}; + int32_t ret = OH_NativeWindow_NativeWindowFlushBuffer(Raw(), buffer, fenceFd, region); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OHNativeWindow", "Failed to flush buffer"); +} + +inline void NativeWindow::AbortBuffer(OHNativeWindowBuffer* buffer) +{ + int32_t ret = OH_NativeWindow_NativeWindowAbortBuffer(Raw(), buffer); + NATIVE_THROW_IF_FAILED_VOID(ret == 0, ret, "OHNativeWindow", "Failed to flush buffer"); +} + +} // namespace ohos + +#endif // WEBRTC_HELPER_NATIVE_WINDOW_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/pointer_wrapper.h b/sdk/ohos/src/ohos_webrtc/helper/pointer_wrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..8fe75296ce6f92172a564ede25451499719885b0 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/pointer_wrapper.h @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_POINTER_WRAPPER_H +#define WEBRTC_HELPER_POINTER_WRAPPER_H + +#include +#include + +namespace ohos { + +template +class PointerWrapper { +public: + /** + * The deleter type. + */ + using DeleterType = std::function; + + T& operator*() const + { + return *ptr_; + } + + T* operator->() const + { + return ptr_.get(); + } + + bool IsEmpty() const noexcept + { + return ptr_ == nullptr; + } + + T* Raw() const noexcept + { + return ptr_.get(); + } + + void Reset() noexcept + { + PointerWrapper tmp; + tmp.Swap(*this); + } + + void Swap(PointerWrapper& rhs) noexcept + { + using std::swap; + swap(ptr_, rhs.ptr_); + } + +protected: + constexpr PointerWrapper() noexcept = default; + PointerWrapper(T* p, DeleterType del) noexcept : ptr_(p, del) {} + + /** + * A null deleter. + */ + static void NullDeleter(T*) {} + +protected: + std::shared_ptr ptr_; +}; + +} // namespace ohos + +namespace std { + +template +void swap(ohos::PointerWrapper& lhs, ohos::PointerWrapper& rhs) noexcept +{ + lhs.Swap(rhs); +} + +} // namespace std + +#endif // WEBRTC_HELPER_POINTER_WRAPPER_H diff --git a/sdk/ohos/src/ohos_webrtc/helper/screen_capture.h b/sdk/ohos/src/ohos_webrtc/helper/screen_capture.h new file mode 100644 index 0000000000000000000000000000000000000000..b73fd5a43e4a4c342f9ef39b538e671ce162677b --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/helper/screen_capture.h @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HELPER_SCREEN_CAPTURE_H +#define WEBRTC_HELPER_SCREEN_CAPTURE_H + +#include "pointer_wrapper.h" +#include "error.h" + +#include +#include +#include + +namespace ohos { + +class AVScreenCapture : public PointerWrapper { +public: + static AVScreenCapture Create(); + static AVScreenCapture TakeOwnership(OH_AVScreenCapture* capture); + + AVScreenCapture(); + explicit AVScreenCapture(OH_AVScreenCapture* capture); + +private: + using PointerWrapper::PointerWrapper; +}; + + +inline AVScreenCapture AVScreenCapture::Create() +{ + OH_AVScreenCapture* capture = OH_AVScreenCapture_Create(); + NATIVE_THROW_IF_FAILED(capture != nullptr, -1, "OH_AVScreenCapture", "Failed to create native window", AVScreenCapture()); + return AVScreenCapture(capture, [](OH_AVScreenCapture* capture) { OH_AVScreenCapture_Release(capture); }); +} + +inline AVScreenCapture AVScreenCapture::TakeOwnership(OH_AVScreenCapture* capture) +{ + NATIVE_THROW_IF_FAILED(capture != nullptr, -1, "OH_AVScreenCapture", "Null argument", AVScreenCapture()); + return AVScreenCapture(capture, [](OH_AVScreenCapture* capture) { OH_AVScreenCapture_Release(capture); }); +} + +inline AVScreenCapture::AVScreenCapture() = default; + +inline AVScreenCapture::AVScreenCapture(OH_AVScreenCapture* capture) : PointerWrapper(capture, NullDeleter) {} + +} // namespace ohos + +#endif //WEBRTC_HELPER_SCREEN_CAPTURE_H diff --git a/sdk/ohos/src/ohos_webrtc/ice_candidate.cpp b/sdk/ohos/src/ohos_webrtc/ice_candidate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1bd64e68bafb55c98073f3b8cae4959f2d4ff0a4 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/ice_candidate.cpp @@ -0,0 +1,445 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ice_candidate.h" + +#include "pc/webrtc_sdp.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumIceCandidateTypeHost[] = "host"; +const char kEnumIceCandidateTypeSrflx[] = "srflx"; +const char kEnumIceCandidateTypePrflx[] = "prflx"; +const char kEnumIceCandidateTypeRelay[] = "relay"; + +const char kEnumIceCandidateRtp[] = "rtp"; +const char kEnumIceCandidateRtcp[] = "rtcp"; + +FunctionReference NapiIceCandidate::constructor_; + +void NapiIceCandidate::Init(class Env env, Object exports) +{ + Function func = DefineClass( + env, "RTCIceCandidate", + { + InstanceAccessor<&NapiIceCandidate::GetCandidate>("candidate"), + InstanceAccessor<&NapiIceCandidate::GetSdpMid>("sdpMid"), + InstanceAccessor<&NapiIceCandidate::GetSdpMLineIndex>("sdpMLineIndex"), + InstanceAccessor<&NapiIceCandidate::GetUsernameFragment>("usernameFragment"), + InstanceAccessor<&NapiIceCandidate::GetFoundation>("foundation"), + InstanceAccessor<&NapiIceCandidate::GetComponent>("component"), + InstanceAccessor<&NapiIceCandidate::GetPriority>("priority"), + InstanceAccessor<&NapiIceCandidate::GetAddress>("address"), + InstanceAccessor<&NapiIceCandidate::GetProtocol>("protocol"), + InstanceAccessor<&NapiIceCandidate::GetPort>("port"), + InstanceAccessor<&NapiIceCandidate::GetType>("type"), + InstanceAccessor<&NapiIceCandidate::GetTcpType>("tcpType"), + InstanceAccessor<&NapiIceCandidate::GetRelatedAddress>("relatedAddress"), + InstanceAccessor<&NapiIceCandidate::GetRelatedPort>("relatedPort"), + InstanceMethod<&NapiIceCandidate::ToJson>("toJSON"), + }); + exports.Set("RTCIceCandidate", func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiIceCandidate::NewInstance(const Napi::CallbackInfo& info, IceCandidateInterface* candidate) +{ + // Unused + NAPI_THROW(Error::New(info.Env(), "Unimplemented"), Object()); +} + +NapiIceCandidate::NapiIceCandidate(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1 || !info[0].IsObject()) { + NAPI_THROW_VOID(Error::New(info.Env(), "Wrong number of argument")); + } + + auto from = info[0].As(); + if (!from.Has("sdpMid") && !from.Has("sdpMLineIndex")) { + NAPI_THROW_VOID(Error::New(info.Env(), "TypeError")); + } + + if (from.Has("sdpMid") && !from.Get("sdpMid").IsUndefined()) { + sdpMid_ = from.Get("sdpMid").As().Utf8Value(); + } + + if (from.Has("sdpMLineIndex") && !from.Get("sdpMLineIndex").IsUndefined()) { + sdpMLineIndex_ = from.Get("sdpMLineIndex").As(); + } + + if (from.Has("candidate")) { + auto sdp = from.Get("candidate").As().Utf8Value(); + sdp_ = sdp; + if (!sdp.empty()) { + auto sdpMid = sdpMid_.value_or(""); + if (!SdpDeserializeCandidate(sdpMid, sdp, &candidate_, nullptr)) { + NAPI_THROW_VOID(Error::New(info.Env(), "SdpDescrializeCandidate failed with sdp")); + } + } + } else { + NAPI_THROW_VOID(Error::New(info.Env(), "candidate is null")); + } +} + +Napi::Value NapiIceCandidate::GetCandidate(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return String::New(info.Env(), sdp_); +} + +Napi::Value NapiIceCandidate::GetSdpMid(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (sdpMid_.has_value()) { + return String::New(info.Env(), sdpMid_.value()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetSdpMLineIndex(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (sdpMLineIndex_.has_value()) { + return Number::New(info.Env(), sdpMLineIndex_.value()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetUsernameFragment(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.username().empty()) { + return String::New(info.Env(), candidate_.username()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetFoundation(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.foundation().empty()) { + return String::New(info.Env(), candidate_.foundation()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetComponent(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + // https://www.w3.org/TR/webrtc/#dfn-candidate-attribute + // https://www.rfc-editor.org/rfc/rfc5245#section-15.1 + if (candidate_.component() == 1) { + return String::New(info.Env(), kEnumIceCandidateRtp); + } + if (candidate_.component() == 2) { + return String::New(info.Env(), kEnumIceCandidateRtcp); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiIceCandidate::GetPriority(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return Number::New(info.Env(), candidate_.priority()); +} + +Napi::Value NapiIceCandidate::GetAddress(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.address().hostname().empty()) { + return String::New(info.Env(), candidate_.address().hostname()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetProtocol(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.protocol().empty()) { + return String::New(info.Env(), candidate_.protocol()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetPort(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return Number::New(info.Env(), candidate_.address().port()); +} + +Napi::Value NapiIceCandidate::GetType(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto env = info.Env(); + + if (candidate_.type() == cricket::LOCAL_PORT_TYPE) { + return String::New(env, kEnumIceCandidateTypeHost); + } else if (candidate_.type() == cricket::STUN_PORT_TYPE) { + return String::New(env, kEnumIceCandidateTypeSrflx); + } else if (candidate_.type() == cricket::RELAY_PORT_TYPE) { + return String::New(env, kEnumIceCandidateTypeRelay); + } else if (candidate_.type() == cricket::PRFLX_PORT_TYPE) { + return String::New(env, kEnumIceCandidateTypePrflx); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetTcpType(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.tcptype().empty()) { + return String::New(info.Env(), candidate_.tcptype()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetRelatedAddress(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate_.related_address().hostname().empty()) { + return String::New(info.Env(), candidate_.related_address().hostname()); + } else { + return info.Env().Undefined(); + } +} + +Napi::Value NapiIceCandidate::GetRelatedPort(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return Number::New(info.Env(), candidate_.related_address().port()); +} + +Value NapiIceCandidate::ToJson(const CallbackInfo& info) +{ + auto from = info.This().As(); + auto to = Object::New(info.Env()); + + to.Set("candidate", from.Get("candidate")); + if (from.Has("sdpMLineIndex")) + to.Set("sdpMLineIndex", from.Get("sdpMLineIndex")); + if (from.Has("sdpMid")) + to.Set("sdpMid", from.Get("sdpMid")); + if (from.Has("usernameFragment")) + to.Set("usernameFragment", from.Get("usernameFragment")); + + return to; +} + +cricket::Candidate JsToNativeCandidate(Napi::Env env, const Napi::Object& jsCandidate) +{ + auto sdpMid = jsCandidate.Has("sdpMid") ? jsCandidate.Get("sdpMid").As().Utf8Value() : std::string(); + auto sdp = jsCandidate.Get("candidate").As().Utf8Value(); + + cricket::Candidate candidate; + if (!SdpDeserializeCandidate(sdpMid, sdp, &candidate, nullptr)) { + RTC_LOG(LS_ERROR) << "SdpDescrializeCandidate failed with sdp " << sdp; + } + + return candidate; +} + +Napi::Object NativeToJsCandidate( + Napi::Env env, const std::string& sdpMid, int sdpMLineIndex, const std::string& sdp, + const cricket::Candidate& candidate) +{ + if (sdp.empty()) { + RTC_LOG(LS_ERROR) << "got an empty ICE candidate"; + return Object::New(env); + } + + auto obj = Object::New(env); + obj.Set("sdpMLineIndex", Number::New(env, sdpMLineIndex)); + obj.Set("sdpMid", String::New(env, sdpMid)); + obj.Set("candidate", String::New(env, sdp)); + obj.Set("foundation", String::New(env, candidate.foundation())); + // https://www.w3.org/TR/webrtc/#dfn-candidate-attribute + // https://www.rfc-editor.org/rfc/rfc5245#section-15.1 + if (candidate.component() == 1) { + obj.Set("component", String::New(env, kEnumIceCandidateRtp)); + } + if (candidate.component() == 2) { + obj.Set("component", String::New(env, kEnumIceCandidateRtcp)); + } + obj.Set("priority", Number::New(env, candidate.priority())); + obj.Set("address", String::New(env, candidate.address().hostname())); + obj.Set("protocol", String::New(env, candidate.protocol())); + obj.Set("port", Number::New(env, candidate.address().port())); + obj.Set("tcpType", String::New(env, candidate.tcptype())); + obj.Set("relatedAddress", String::New(env, candidate.related_address().hostname())); + obj.Set("relatedPort", Number::New(env, candidate.related_address().port())); + obj.Set("usernameFragment", String::New(env, candidate.username())); + + if (candidate.type() == cricket::LOCAL_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeHost)); + } else if (candidate.type() == cricket::STUN_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeSrflx)); + } else if (candidate.type() == cricket::RELAY_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeRelay)); + } else if (candidate.type() == cricket::PRFLX_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypePrflx)); + } else { + // invalid type + } + + // extension attribute + obj.Set("adapterType", String::New(env, rtc::AdapterTypeToString(candidate.network_type()))); + obj.Set("serverUrl", String::New(env, candidate.url())); + + // instance method of IceCandidate + obj.Set("toJSON", Function::New(env, [](const CallbackInfo& info) { + auto from = info.This().As(); + auto to = Object::New(info.Env()); + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " toJson"; + + to.Set("candidate", from.Get("candidate")); + if (from.Has("sdpMLineIndex")) + to.Set("sdpMLineIndex", from.Get("sdpMLineIndex")); + if (from.Has("sdpMid")) + to.Set("sdpMid", from.Get("sdpMid")); + if (from.Has("usernameFragment")) + to.Set("usernameFragment", from.Get("usernameFragment")); + + return to; + })); + + return obj; +} + +Napi::Object NativeToJsCandidate(Napi::Env env, const cricket::Candidate& candidate) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto obj = Object::New(env); + + obj.Set("foundation", String::New(env, candidate.foundation())); + // https://www.w3.org/TR/webrtc/#dfn-candidate-attribute + // https://www.rfc-editor.org/rfc/rfc5245#section-15.1 + if (candidate.component() == 1) { + obj.Set("component", String::New(env, kEnumIceCandidateRtp)); + } + if (candidate.component() == 2) { + obj.Set("component", String::New(env, kEnumIceCandidateRtcp)); + } + obj.Set("priority", Number::New(env, candidate.priority())); + obj.Set("address", String::New(env, candidate.address().hostname())); + obj.Set("protocol", String::New(env, candidate.protocol())); + obj.Set("port", Number::New(env, candidate.address().port())); + obj.Set("tcpType", String::New(env, candidate.tcptype())); + obj.Set("relatedAddress", String::New(env, candidate.related_address().hostname())); + obj.Set("relatedPort", Number::New(env, candidate.related_address().port())); + obj.Set("usernameFragment", String::New(env, candidate.username())); + + if (candidate.type() == cricket::LOCAL_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeHost)); + } else if (candidate.type() == cricket::STUN_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeSrflx)); + } else if (candidate.type() == cricket::RELAY_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypeRelay)); + } else if (candidate.type() == cricket::PRFLX_PORT_TYPE) { + obj.Set("type", String::New(env, kEnumIceCandidateTypePrflx)); + } else { + // invalid type + } + + // extension attribute + obj.Set("adapterType", String::New(env, rtc::AdapterTypeToString(candidate.network_type()))); + obj.Set("serverUrl", String::New(env, candidate.url())); + + // instance method of IceCandidate + obj.Set("toJSON", Function::New(env, [](const CallbackInfo& info) { + auto from = info.This().As(); + auto to = Object::New(info.Env()); + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " toJson"; + + to.Set("candidate", from.Get("candidate")); + if (from.Has("sdpMLineIndex")) + to.Set("sdpMLineIndex", from.Get("sdpMLineIndex")); + if (from.Has("sdpMid")) + to.Set("sdpMid", from.Get("sdpMid")); + if (from.Has("usernameFragment")) + to.Set("usernameFragment", from.Get("usernameFragment")); + + return to; + })); + + return obj; +} + +Napi::Object NativeToJsIceCandidate(Napi::Env env, const IceCandidateInterface& candidate) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::string sdp; + if (!candidate.ToString(&sdp)) { + RTC_LOG(LS_ERROR) << "got so far: " << sdp; + } + + auto obj = Object::New(env); + obj.Set("sdpMLineIndex", Number::New(env, candidate.sdp_mline_index())); + obj.Set("sdpMid", String::New(env, candidate.sdp_mid())); + obj.Set("candidate", String::New(env, sdp)); + + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " xxx"; + // instance method of IceCandidate + obj.Set("toJSON", Function::New(env, [](const CallbackInfo& info) { + auto from = info.This().As(); + auto to = Object::New(info.Env()); + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " toJson"; + + to.Set("candidate", from.Get("candidate")); + if (from.Has("sdpMLineIndex")) + to.Set("sdpMLineIndex", from.Get("sdpMLineIndex")); + if (from.Has("sdpMid")) + to.Set("sdpMid", from.Get("sdpMid")); + if (from.Has("usernameFragment")) + to.Set("usernameFragment", from.Get("usernameFragment")); + + return to; + })); + + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " exit"; + + return obj; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/ice_candidate.h b/sdk/ohos/src/ohos_webrtc/ice_candidate.h new file mode 100644 index 0000000000000000000000000000000000000000..c40adc1a4ea18ec4f243f6780f2e184016d44b1f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/ice_candidate.h @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ICE_CANDIDATE_H +#define WEBRTC_ICE_CANDIDATE_H + +#include + +#include "api/data_channel_interface.h" +#include "api/jsep.h" +#include "api/jsep_ice_candidate.h" +#include "api/peer_connection_interface.h" +#include "api/rtp_parameters.h" +#include "rtc_base/ssl_identity.h" + +#include "napi.h" + +namespace webrtc { + +class NapiIceCandidate : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(const Napi::CallbackInfo& info, IceCandidateInterface* candidate); + + explicit NapiIceCandidate(const Napi::CallbackInfo& info); + +protected: + Napi::Value GetCandidate(const Napi::CallbackInfo& info); + Napi::Value GetSdpMid(const Napi::CallbackInfo& info); + Napi::Value GetSdpMLineIndex(const Napi::CallbackInfo& info); + Napi::Value GetUsernameFragment(const Napi::CallbackInfo& info); + Napi::Value GetFoundation(const Napi::CallbackInfo& info); + Napi::Value GetComponent(const Napi::CallbackInfo& info); + Napi::Value GetPriority(const Napi::CallbackInfo& info); + Napi::Value GetAddress(const Napi::CallbackInfo& info); + Napi::Value GetProtocol(const Napi::CallbackInfo& info); + Napi::Value GetPort(const Napi::CallbackInfo& info); + Napi::Value GetType(const Napi::CallbackInfo& info); + Napi::Value GetTcpType(const Napi::CallbackInfo& info); + Napi::Value GetRelatedAddress(const Napi::CallbackInfo& info); + Napi::Value GetRelatedPort(const Napi::CallbackInfo& info); + + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::string sdp_; + std::optional sdpMid_; + std::optional sdpMLineIndex_; + cricket::Candidate candidate_; +}; + +cricket::Candidate JsToNativeCandidate(Napi::Env env, const Napi::Value& jsCandidate); + +Napi::Object NativeToJsCandidate( + Napi::Env env, const std::string& sdpMid, int sdpMLineIndex, const std::string& sdp, + const cricket::Candidate& candidate); + +Napi::Object NativeToJsCandidate(Napi::Env env, const cricket::Candidate& candidate); + +Napi::Object NativeToJsIceCandidate(Napi::Env env, const IceCandidateInterface& candidate); + +} // namespace webrtc + +#endif // WEBRTC_ICE_CANDIDATE_H diff --git a/sdk/ohos/src/ohos_webrtc/ice_transport.cpp b/sdk/ohos/src/ohos_webrtc/ice_transport.cpp new file mode 100644 index 0000000000000000000000000000000000000000..196f2c666a0fff6ce39b6a429e617ccc0fb11076 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/ice_transport.cpp @@ -0,0 +1,394 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ice_transport.h" +#include "peer_connection.h" +#include "peer_connection_factory.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumRTCIceRoleUnKnown[] = "unknown"; +const char kEnumRTCIceRoleControlling[] = "controlling"; +const char kEnumRTCIceRoleControlled[] = "controlled"; + +const char kEnumRTCIceTransportStateChecking[] = "checking"; +const char kEnumRTCIceTransportStateClosed[] = "closed"; +const char kEnumRTCIceTransportStateCompleted[] = "completed"; +const char kEnumRTCIceTransportStateConnected[] = "connected"; +const char kEnumRTCIceTransportStateDisconnected[] = "disconnected"; +const char kEnumRTCIceTransportStateFailed[] = "failed"; +const char kEnumRTCIceTransportStateNew[] = "new"; + +const char kEnumRTCIceComponentRtp[] = "rtp"; +const char kEnumRTCIceComponentRtcp[] = "rtcp"; + +const char kEnumRTCIceGathererStateComplete[] = "complete"; +const char kEnumRTCIceGathererStateGathering[] = "gathering"; +const char kEnumRTCIceGathererStateNew[] = "new"; + +const char kClassName[] = "RTCIceTransport"; + +const char kAttributeNameRole[] = "role"; +const char kAttributeNameComponent[] = "component"; +const char kAttributeNameState[] = "state"; +const char kAttributeNameGatheringState[] = "gatheringState"; +const char kAttributeNameOnStateChange[] = "onstatechange"; +const char kAttributeNameOnGatheringStateChange[] = "ongatheringstatechange"; +const char kAttributeNameOnSelectedCandidatePairChange[] = "onselectedcandidatepairchange"; + +const char kEventNameStateChange[] = "statechange"; +const char kEventNameGatheringStateChange[] = "gatheringstatechange"; +const char kEventNameSelectedCandidatePairChange[] = "selectedcandidatepairchange"; + +const char kMethodNameGetSelectedCandidatePair[] = "getSelectedCandidatePair"; +const char kMethodNameToJson[] = "toJSON"; + +FunctionReference NapiIceTransport::constructor_; + +void NapiIceTransport::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiIceTransport::GetRole>(kAttributeNameRole), + InstanceAccessor<&NapiIceTransport::GetComponent>(kAttributeNameComponent), + InstanceAccessor<&NapiIceTransport::GetState>(kAttributeNameState), + InstanceAccessor<&NapiIceTransport::GetGatheringState>(kAttributeNameGatheringState), + InstanceAccessor<&NapiIceTransport::GetEventHandler, &NapiIceTransport::SetEventHandler>( + kAttributeNameOnStateChange, napi_default, (void*)kEventNameStateChange), + InstanceAccessor<&NapiIceTransport::GetEventHandler, &NapiIceTransport::SetEventHandler>( + kAttributeNameOnGatheringStateChange, napi_default, (void*)kEventNameGatheringStateChange), + InstanceAccessor<&NapiIceTransport::GetEventHandler, &NapiIceTransport::SetEventHandler>( + kAttributeNameOnSelectedCandidatePairChange, napi_default, + (void*)kEventNameSelectedCandidatePairChange), + InstanceMethod<&NapiIceTransport::GetSelectedCandidatePair>(kMethodNameGetSelectedCandidatePair), + InstanceMethod<&NapiIceTransport::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiIceTransport::~NapiIceTransport() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + std::lock_guard lock(mutex_); + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } +} + +Napi::Object NapiIceTransport::NewInstance( + Napi::Env env, rtc::scoped_refptr iceTransport, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!iceTransport || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + auto externalTransport = External::New( + env, iceTransport.release(), [](Napi::Env env, IceTransportInterface* transport) { transport->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalTransport, externalPcWrapper}); +} + +NapiIceTransport::NapiIceTransport(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr iceTransport; + + if (info[0].IsExternal()) { + auto iceTransport = info[0].As>().Data(); + iceTransport_ = rtc::scoped_refptr(iceTransport); + } + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + cricket::IceTransportInternal* internal = nullptr; + + factoryWrapper->GetNetworkThread()->BlockingCall([&internal, this] { + internal = iceTransport_->internal(); + iceTransportState_ = internal->GetIceTransportState(); + iceGatheringState_ = internal->gathering_state(); + + internal->SignalIceTransportStateChanged.connect(this, &NapiIceTransport::OnStateChange); + internal->SignalGatheringState.connect(this, &NapiIceTransport::OnGatheringStateChange); + internal->SignalCandidatePairChanged.connect(this, &NapiIceTransport::OnSelectedCandidatePairChange); + }); + } +} + +Napi::Value NapiIceTransport::GetRole(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + + cricket::IceRole iceRole; + + factoryWrapper->GetNetworkThread()->BlockingCall( + [&iceRole, this] { iceRole = iceTransport_->internal()->GetIceRole(); }); + + switch (iceRole) { + case cricket::ICEROLE_CONTROLLING: + return String::New(info.Env(), kEnumRTCIceRoleControlling); + case cricket::ICEROLE_CONTROLLED: + return String::New(info.Env(), kEnumRTCIceRoleControlled); + case cricket::ICEROLE_UNKNOWN: + return String::New(info.Env(), kEnumRTCIceRoleUnKnown); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid role"), info.Env().Undefined()); +} + +Napi::Value NapiIceTransport::GetComponent(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + + int component = 0; + + factoryWrapper->GetNetworkThread()->BlockingCall( + [&component, this] { component = iceTransport_->internal()->component(); }); + + // https://www.w3.org/TR/webrtc/#dfn-candidate-attribute + // https://www.rfc-editor.org/rfc/rfc5245#section-15.1 + if (component == 1) { + return String::New(info.Env(), kEnumRTCIceComponentRtp); + } + if (component == 2) { + return String::New(info.Env(), kEnumRTCIceComponentRtcp); + } + + NAPI_THROW(Error::New(info.Env(), "Invalid component"), info.Env().Undefined()); +} + +Napi::Value NapiIceTransport::GetState(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + IceTransportState iceTransportState = iceTransportState_; + switch (iceTransportState) { + case IceTransportState::kNew: + return String::New(info.Env(), kEnumRTCIceTransportStateNew); + case IceTransportState::kChecking: + return String::New(info.Env(), kEnumRTCIceTransportStateChecking); + case IceTransportState::kConnected: + return String::New(info.Env(), kEnumRTCIceTransportStateConnected); + case IceTransportState::kCompleted: + return String::New(info.Env(), kEnumRTCIceTransportStateCompleted); + case IceTransportState::kFailed: + return String::New(info.Env(), kEnumRTCIceTransportStateFailed); + case IceTransportState::kDisconnected: + return String::New(info.Env(), kEnumRTCIceTransportStateDisconnected); + case IceTransportState::kClosed: + return String::New(info.Env(), kEnumRTCIceTransportStateClosed); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiIceTransport::GetGatheringState(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + cricket::IceGatheringState iceGatheringState = iceGatheringState_; + switch (iceGatheringState) { + case cricket::kIceGatheringNew: + return String::New(info.Env(), kEnumRTCIceGathererStateNew); + case cricket::kIceGatheringGathering: + return String::New(info.Env(), kEnumRTCIceGathererStateGathering); + case cricket::kIceGatheringComplete: + return String::New(info.Env(), kEnumRTCIceGathererStateComplete); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid gathering state"), info.Env().Undefined()); +} + +Napi::Value NapiIceTransport::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find((const char*)info.Data()); + if (it == eventHandlers_.end()) { + return info.Env().Null(); + } + + return it->second.ref.Value(); +} + +void NapiIceTransport::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + const auto type = (const char*)info.Data(); + + { + std::lock_guard lock(mutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + it->second.tsfn.Release(); + eventHandlers_.erase(it); + } + } + + if (value.IsFunction()) { + auto fn = value.As(); + + Reference* context = new Reference; + *context = Persistent(info.This()); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = ThreadSafeFunction::New( + fn.Env(), fn, type, 0, 1, context, [](Napi::Env env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + std::lock_guard lock(mutex_); + eventHandlers_[type] = std::move(handler); + } else if (value.IsNull()) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " value is null"; + } else { + NAPI_THROW_VOID(Error::New(info.Env(), "value is error")); + } +} + +Napi::Value NapiIceTransport::GetSelectedCandidatePair(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + cricket::CandidatePair candidatePair; + auto obj = Object::New(info.Env()); + + factoryWrapper->GetNetworkThread()->BlockingCall([&candidatePair, this] { + auto candidate = iceTransport_->internal()->GetSelectedCandidatePair(); + if (candidate) { + candidatePair.local = candidate->local_candidate(); + candidatePair.remote = candidate->remote_candidate(); + } + }); + + obj.Set("local", NativeToJsCandidate(info.Env(), candidatePair.local)); + obj.Set("remote", NativeToJsCandidate(info.Env(), candidatePair.remote)); + + return obj; +} + +Napi::Value NapiIceTransport::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiIceTransport")); +#endif + + return json; +} + +void NapiIceTransport::OnStateChange(cricket::IceTransportInternal* iceTransport) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + iceTransportState_ = iceTransport_->internal()->GetIceTransportState(); + + std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameStateChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiIceTransport::OnGatheringStateChange(cricket::IceTransportInternal* iceTransport) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + iceGatheringState_ = iceTransport->gathering_state(); + + std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameGatheringStateChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +void NapiIceTransport::OnSelectedCandidatePairChange(const cricket::CandidatePairChangeEvent& event) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + + auto it = eventHandlers_.find(kEventNameSelectedCandidatePairChange); + if (it == eventHandlers_.end()) { + return; + } + + auto& tsfn = it->second.tsfn; + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +} // namespace webrtc \ No newline at end of file diff --git a/sdk/ohos/src/ohos_webrtc/ice_transport.h b/sdk/ohos/src/ohos_webrtc/ice_transport.h new file mode 100644 index 0000000000000000000000000000000000000000..18340d7ea1239084928500500a738b221b4e78df --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/ice_transport.h @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_ICE_TRANSPORT_H +#define WEBRTC_ICE_TRANSPORT_H + +#include + +#include "napi.h" + +#include "api/ice_transport_interface.h" +#include "p2p/base/ice_transport_internal.h" +#include "ice_candidate.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiIceTransport : public Napi::ObjectWrap, public sigslot::has_slots<> { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance( + Napi::Env env, rtc::scoped_refptr iceTransport, NapiPeerConnectionWrapper* pcWrapper); + + ~NapiIceTransport(); + +protected: + friend class ObjectWrap; + + explicit NapiIceTransport(const Napi::CallbackInfo& info); + +protected: + // JS + Napi::Value GetRole(const Napi::CallbackInfo& info); + Napi::Value GetComponent(const Napi::CallbackInfo& info); + Napi::Value GetState(const Napi::CallbackInfo& info); + Napi::Value GetGatheringState(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value GetLocalCandidates(const Napi::CallbackInfo& info); + Napi::Value GetLocalParameters(const Napi::CallbackInfo& info); + Napi::Value GetRemoteCandidates(const Napi::CallbackInfo& info); + Napi::Value GetRemoteParameters(const Napi::CallbackInfo& info); + Napi::Value GetSelectedCandidatePair(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + void OnStateChange(cricket::IceTransportInternal* iceTransport); + void OnGatheringStateChange(cricket::IceTransportInternal* iceTransport); + void OnSelectedCandidatePairChange(const cricket::CandidatePairChangeEvent& event); + +private: + static Napi::FunctionReference constructor_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex mutex_; + std::map eventHandlers_; + + rtc::scoped_refptr iceTransport_; + NapiPeerConnectionWrapper* pcWrapper_{}; + + std::atomic iceTransportState_{IceTransportState::kNew}; + std::atomic iceGatheringState_{cricket::kIceGatheringNew}; +}; + +} // namespace webrtc + +#endif // WEBRTC_ICE_TRANSPORT_H diff --git a/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.cpp b/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.cpp new file mode 100644 index 0000000000000000000000000000000000000000..010c4b0e13040e0e2bcda7c46121d9f829286581 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hilog_sink.h" + +#include "hilog/log.h" + +HilogSink::HilogSink() = default; +HilogSink::~HilogSink() = default; + +void HilogSink::OnLogMessage(const std::string& msg) +{ + RTC_DCHECK_NOTREACHED(); +} + +void HilogSink::OnLogMessage(const std::string& msg, rtc::LoggingSeverity severity, const char* tag) +{ + OnLogMessage(absl::string_view{msg}, severity, tag); +} + +void HilogSink::OnLogMessage(absl::string_view msg, rtc::LoggingSeverity severity, const char* tag) +{ + LogLevel level = LOG_DEBUG; + switch (severity) { + case rtc::LS_VERBOSE: + level = LOG_DEBUG; + break; + case rtc::LS_INFO: + level = LOG_INFO; + break; + case rtc::LS_WARNING: + level = LOG_WARN; + break; + case rtc::LS_ERROR: + level = LOG_ERROR; + break; + case rtc::LS_NONE: + return; + default: + break; + } + OH_LOG_Print(LOG_APP, level, LOG_DOMAIN, tag, "%{public}s", msg.data()); +} + +void HilogSink::OnLogMessage(const rtc::LogLineRef& line) +{ + OnLogMessage(line.DefaultLogLine(), line.severity(), line.tag().data()); +} diff --git a/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.h b/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..831979487bbe48143f70255e404b9bcf6c0e36e2 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/hilog_sink.h @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_HILOG_SINK_H +#define WEBRTC_HILOG_SINK_H + +#include + +#include "absl/strings/string_view.h" +#include "rtc_base/logging.h" + +class HilogSink : public rtc::LogSink { +public: + HilogSink(); + ~HilogSink() override; + + void OnLogMessage(const std::string& msg) override; + void OnLogMessage(const std::string& msg, rtc::LoggingSeverity severity, const char* tag) override; + void OnLogMessage(absl::string_view msg, rtc::LoggingSeverity severity, const char* tag) override; + void OnLogMessage(const rtc::LogLineRef& line) override; +}; + +#endif // WEBRTC_HILOG_SINK_H diff --git a/sdk/ohos/src/ohos_webrtc/logging/log_sink.cpp b/sdk/ohos/src/ohos_webrtc/logging/log_sink.cpp new file mode 100644 index 0000000000000000000000000000000000000000..58e072578bec890d84d6bd7d66d7d73b12c6dad8 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/log_sink.cpp @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log_sink.h" + +namespace webrtc { + +using namespace std; +using namespace Napi; + +const char kMethodNameLogMessage[] = "logMessage"; + +LogSink::LogSink(napi_env env, Napi::Object loggable) +{ + if (!loggable.Has(kMethodNameLogMessage)) { + NAPI_THROW_VOID(Error::New(env, "Invalid argument")); + } + + tsfn_ = TSFN::New( + env, loggable.Get(kMethodNameLogMessage).As(), kMethodNameLogMessage, 0, 1, + new Context(Napi::Persistent(loggable)), [](Napi::Env, FinalizerDataType*, Context* ctx) { delete ctx; }); +} + +LogSink::~LogSink() +{ + tsfn_.Release(); +}; + +void LogSink::OnLogMessage(const string& msg) +{ + RTC_DCHECK_NOTREACHED(); +} + +void LogSink::OnLogMessage(const string& msg, rtc::LoggingSeverity severity, const char* tag) +{ + tsfn_.BlockingCall(new DataType{severity, msg, tag}); +} + +void LogSink::OnLogMessage(absl::string_view msg, rtc::LoggingSeverity severity, const char* tag) +{ + tsfn_.BlockingCall(new DataType{severity, std::string(msg), tag}); +} + +void LogSink::OnLogMessage(const rtc::LogLineRef& line) +{ + // Inefficient + tsfn_.BlockingCall(new DataType{line.severity(), line.DefaultLogLine(), std::string(line.tag())}); +} + +void LogSink::CallJs(Napi::Env env, Napi::Function callback, Context* context, DataType* data) +{ + if (env == nullptr) { + // Javascript environment is not available to call into + return; + } + + callback.Call( + context->Value(), + {String::New(env, data->msg.data()), Number::New(env, data->severity), String::New(env, data->tag)}); + + delete data; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/logging/log_sink.h b/sdk/ohos/src/ohos_webrtc/logging/log_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..65a10f1680ab505b229091a8a35e98ad8b39946a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/log_sink.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_LOG_SINK_H +#define WEBRTC_LOG_SINK_H + +#include + +#include "napi.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +class LogSink : public rtc::LogSink { +public: + LogSink(napi_env env, Napi::Object logger); + ~LogSink() override; + + void OnLogMessage(const std::string& msg) override; + void OnLogMessage(const std::string& msg, rtc::LoggingSeverity severity, const char* tag) override; + void OnLogMessage(absl::string_view msg, rtc::LoggingSeverity severity, const char* tag) override; + void OnLogMessage(const rtc::LogLineRef& line) override; + +protected: + struct DataType { + rtc::LoggingSeverity severity; + std::string msg; + std::string tag; + }; + + using Context = Napi::ObjectReference; + static void CallJs(Napi::Env env, Napi::Function callback, Context* context, DataType*); + using TSFN = Napi::TypedThreadSafeFunction; + using FinalizerDataType = void; + +private: + TSFN tsfn_; +}; + +} // namespace webrtc + +#endif // WEBRTC_LOG_SINK_H diff --git a/sdk/ohos/src/ohos_webrtc/logging/native_logging.cpp b/sdk/ohos/src/ohos_webrtc/logging/native_logging.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a537fc9abcfb5434971c971a8496534be53fe946 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/native_logging.cpp @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_logging.h" +#include "hilog_sink.h" +#include "log_sink.h" + +#include "hilog/log.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "NativeLogging"; + +const char kMethodNameInjectLoggable[] = "injectLoggable"; +const char kMethodNameDeleteLoggable[] = "deleteLoggable"; +const char kMethodNameEnableLogToDebugOutput[] = "enableLogToDebugOutput"; +const char kMethodNameEnableLogThreads[] = "enableLogThreads"; +const char kMethodNameEnableLogTimeStamps[] = "enableLogTimeStamps"; +const char kMethodNameLog[] = "log"; + +struct StaticObjectContainer { + std::unique_ptr logSink; + std::unique_ptr hilogSink; +}; + +StaticObjectContainer& GetStaticObjects() +{ + static StaticObjectContainer* static_objects = new StaticObjectContainer(); + return *static_objects; +} + +FunctionReference NapiNativeLogging::constructor_; + +void NapiNativeLogging::Init(class Env env, Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + StaticMethod<&NapiNativeLogging::InjectLoggable>(kMethodNameInjectLoggable), + StaticMethod<&NapiNativeLogging::DeleteLoggable>(kMethodNameDeleteLoggable), + StaticMethod<&NapiNativeLogging::EnableLogToDebugOutput>(kMethodNameEnableLogToDebugOutput), + StaticMethod<&NapiNativeLogging::EnableLogThreads>(kMethodNameEnableLogThreads), + StaticMethod<&NapiNativeLogging::EnableLogTimeStamps>(kMethodNameEnableLogTimeStamps), + StaticMethod<&NapiNativeLogging::Log>(kMethodNameLog), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiNativeLogging::NapiNativeLogging(const Napi::CallbackInfo& info) : ObjectWrap(info) {} + +Napi::Value NapiNativeLogging::InjectLoggable(const Napi::CallbackInfo& info) +{ + OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, kClassName, __FUNCTION__); + + auto loggable = info[0].As(); + auto severity = info[1].As().Int32Value(); + + auto newLogSink = std::make_unique(info.Env(), loggable); + + auto& logSink = GetStaticObjects().logSink; + if (logSink) { + // If there is already a LogSink, remove it from LogMessage. + rtc::LogMessage::RemoveLogToStream(logSink.get()); + } + + logSink = std::move(newLogSink); + rtc::LogMessage::AddLogToStream(logSink.get(), static_cast(severity)); + + auto& hilogSink = GetStaticObjects().hilogSink; + if (hilogSink) { + // remove debug logger + rtc::LogMessage::RemoveLogToStream(hilogSink.get()); + hilogSink.reset(); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeLogging::DeleteLoggable(const Napi::CallbackInfo& info) +{ + OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, kClassName, __FUNCTION__); + + auto& log_sink = GetStaticObjects().logSink; + if (log_sink) { + // If there is already a LogSink, remove it from LogMessage. + rtc::LogMessage::RemoveLogToStream(log_sink.get()); + log_sink.reset(); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeLogging::EnableLogToDebugOutput(const Napi::CallbackInfo& info) +{ + OH_LOG_Print(LOG_APP, LOG_DEBUG, LOG_DOMAIN, kClassName, __FUNCTION__); + + auto& hilogSink = GetStaticObjects().hilogSink; + if (hilogSink) { + rtc::LogMessage::RemoveLogToStream(hilogSink.get()); + } + + auto severity = info[0].As().Int32Value(); + if (severity >= rtc::LS_VERBOSE && severity <= rtc::LS_NONE) { + if (!hilogSink) { + hilogSink = std::make_unique(); + } + rtc::LogMessage::AddLogToStream(hilogSink.get(), rtc::LS_VERBOSE); + } + return info.Env().Undefined(); +} + +Napi::Value NapiNativeLogging::EnableLogThreads(const Napi::CallbackInfo& info) +{ + rtc::LogMessage::LogThreads(true); + return info.Env().Undefined(); +} + +Napi::Value NapiNativeLogging::EnableLogTimeStamps(const Napi::CallbackInfo& info) +{ + rtc::LogMessage::LogTimestamps(true); + return info.Env().Undefined(); +} + +Napi::Value NapiNativeLogging::Log(const Napi::CallbackInfo& info) +{ + if (info.Length() != 3) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + auto message = info[0].As().Utf8Value(); + auto severity = info[1].As().Int32Value(); + auto tag = info[2].As().Utf8Value(); + + LogLevel level = LOG_DEBUG; + switch (severity) { + case rtc::LS_VERBOSE: + level = LOG_DEBUG; + break; + case rtc::LS_INFO: + level = LOG_INFO; + break; + case rtc::LS_WARNING: + level = LOG_WARN; + break; + case rtc::LS_ERROR: + level = LOG_ERROR; + break; + case rtc::LS_NONE: + return info.Env().Undefined(); + default: + break; + } + + OH_LOG_Print(LOG_APP, level, LOG_DOMAIN, tag.c_str(), "%{public}s", message.c_str()); + + return info.Env().Undefined(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/logging/native_logging.h b/sdk/ohos/src/ohos_webrtc/logging/native_logging.h new file mode 100644 index 0000000000000000000000000000000000000000..243e666b06a113e13d8817b23f36de5719d07c49 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/logging/native_logging.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_NATIVE_LOGGING_H +#define WEBRTC_NATIVE_LOGGING_H + +#include "napi.h" + +namespace webrtc { + +class NapiNativeLogging : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + explicit NapiNativeLogging(const Napi::CallbackInfo& info); + +protected: + static Napi::Value InjectLoggable(const Napi::CallbackInfo& info); + static Napi::Value DeleteLoggable(const Napi::CallbackInfo& info); + static Napi::Value EnableLogToDebugOutput(const Napi::CallbackInfo& info); + static Napi::Value EnableLogThreads(const Napi::CallbackInfo& info); + static Napi::Value EnableLogTimeStamps(const Napi::CallbackInfo& info); + static Napi::Value Log(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; +}; + +} // namespace webrtc + +#endif // WEBRTC_NATIVE_LOGGING_H diff --git a/sdk/ohos/src/ohos_webrtc/media_devices.cpp b/sdk/ohos/src/ohos_webrtc/media_devices.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5309941f2775d8712c51687b017a3f1f33fc5a4d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_devices.cpp @@ -0,0 +1,185 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_devices.h" +#include "media_track_constraints.h" +#include "async_work/async_worker_enumerate_devices.h" +#include "async_work/async_worker_get_user_media.h" +#include "async_work/async_worker_get_display_media.h" +#include "utils/marcos.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiMediaDevices::constructor_; + +void NapiMediaDevices::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiMediaDevices::EnumerateDevices>(kMethodNameEnumerateDevices), + InstanceMethod<&NapiMediaDevices::GetSupportedConstraints>(kMethodNameGetSupportedConstraints), + InstanceMethod<&NapiMediaDevices::GetUserMedia>(kMethodNameGetUserMedia), + InstanceMethod<&NapiMediaDevices::GetDisplayMedia>(kMethodNameGetDisplayMedia), + InstanceMethod<&NapiMediaDevices::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiMediaDevices::NapiMediaDevices(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} + +Napi::Value NapiMediaDevices::EnumerateDevices(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = AsyncWorkerEnumerateDevices::Create(info.Env(), "enumerateDevices"); + asyncWorker->Queue(); + + return asyncWorker->GetPromise(); +} + +Napi::Value NapiMediaDevices::GetSupportedConstraints(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto result = Object::New(info.Env()); + +#define NAPI_SET_ATTRIBUTE(name) \ + result.Set(name, Boolean::New(info.Env(), NapiMediaConstraints::IsConstraintSupported(name))) + + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameWidth); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameHeight); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameAspectRatio); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameFrameRate); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameFacingMode); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameResizeMode); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameSampleRate); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameSampleSize); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameEchoCancellation); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameAutoGainControl); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameNoiseSuppression); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameLatency); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameChannelCount); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameDeviceId); + NAPI_SET_ATTRIBUTE(NapiMediaConstraints::kAttributeNameGroupId); + +#undef NAPI_SET_ATTRIBUTE + + return result; +} + +Napi::Value NapiMediaDevices::GetUserMedia(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = AsyncWorkerGetUserMedia::Create(info.Env(), "getUserMedia"); + auto deferred = asyncWorker->GetDeferred(); + + if (info.Length() == 0 || !info[0].IsObject()) { + deferred.Reject(TypeError::New(info.Env(), "Invalid argument").Value()); + return asyncWorker->GetPromise(); + } + + MediaTrackConstraints audio; + MediaTrackConstraints video; + + auto jsConstraints = info[0].As(); + if (jsConstraints.Has("audio")) { + NapiMediaConstraints::JsToNative(jsConstraints.Get("audio"), audio); + if (info.Env().IsExceptionPending()) { + deferred.Reject(info.Env().GetAndClearPendingException().Value()); + return asyncWorker->GetPromise(); + } + } + + if (jsConstraints.Has("video")) { + NapiMediaConstraints::JsToNative(jsConstraints.Get("video"), video); + if (info.Env().IsExceptionPending()) { + deferred.Reject(info.Env().GetAndClearPendingException().Value()); + return asyncWorker->GetPromise(); + } + } + + RTC_DLOG(LS_VERBOSE) << "audio constraints: " << audio.ToString(); + + if (audio.IsNull() && video.IsNull()) { + deferred.Reject(TypeError::New(info.Env(), "At least one of audio and video must be requested").Value()); + return asyncWorker->GetPromise(); + } + + asyncWorker->Start(std::move(audio), std::move(video)); + return asyncWorker->GetPromise(); +} + +Napi::Value NapiMediaDevices::GetDisplayMedia(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = AsyncWorkerGetDisplayMedia::Create(info.Env(), "getDisplayMedia"); + auto deferred = asyncWorker->GetDeferred(); + + if (info.Length() == 0 || !info[0].IsObject()) { + deferred.Reject(TypeError::New(info.Env(), "Invalid argument").Value()); + return asyncWorker->GetPromise(); + } + + MediaTrackConstraints audio; + MediaTrackConstraints video; + + auto jsConstraints = info[0].As(); + if (jsConstraints.Has("audio")) { + NapiMediaConstraints::JsToNative(jsConstraints.Get("audio"), audio); + if (info.Env().IsExceptionPending()) { + deferred.Reject(info.Env().GetAndClearPendingException().Value()); + return asyncWorker->GetPromise(); + } + } + + if (jsConstraints.Has("video")) { + NapiMediaConstraints::JsToNative(jsConstraints.Get("video"), video); + if (info.Env().IsExceptionPending()) { + deferred.Reject(info.Env().GetAndClearPendingException().Value()); + return asyncWorker->GetPromise(); + } + } + + RTC_DLOG(LS_VERBOSE) << "audio constraints: " << audio.ToString(); + + if (audio.IsNull() && video.IsNull()) { + deferred.Reject(TypeError::New(info.Env(), "At least one of audio and video must be requested").Value()); + return asyncWorker->GetPromise(); + } + + asyncWorker->Start(std::move(audio), std::move(video)); + return asyncWorker->GetPromise(); +} + +Napi::Value NapiMediaDevices::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiMediaDevices"); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/media_devices.h b/sdk/ohos/src/ohos_webrtc/media_devices.h new file mode 100644 index 0000000000000000000000000000000000000000..c0d2d95bbd372c7005d077fda9a4351d2a08ba1c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_devices.h @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_MEDIA_DEVICES_H +#define WEBRTC_MEDIA_DEVICES_H + +#include "utils/marcos.h" + +#include "napi.h" + +namespace webrtc { + +class NapiMediaDevices : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(MediaDevices); + NAPI_METHOD_NAME_DECLARE(EnumerateDevices, enumerateDevices); + NAPI_METHOD_NAME_DECLARE(GetSupportedConstraints, getSupportedConstraints); + NAPI_METHOD_NAME_DECLARE(GetUserMedia, getUserMedia); + NAPI_METHOD_NAME_DECLARE(GetDisplayMedia, getDisplayMedia); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + +protected: + friend class ObjectWrap; + + explicit NapiMediaDevices(const Napi::CallbackInfo& info); + + Napi::Value EnumerateDevices(const Napi::CallbackInfo& info); + Napi::Value GetSupportedConstraints(const Napi::CallbackInfo& info); + Napi::Value GetUserMedia(const Napi::CallbackInfo& info); + Napi::Value GetDisplayMedia(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MEDIA_DEVICES_H diff --git a/sdk/ohos/src/ohos_webrtc/media_source.cpp b/sdk/ohos/src/ohos_webrtc/media_source.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b91f9e0b429ecf953eee2a73a3037206d03fe359 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_source.cpp @@ -0,0 +1,427 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_source.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiAudioSource::constructor_; + +void NapiAudioSource::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiAudioSource::GetState>(kAttributeNameState), + InstanceMethod<&NapiAudioSource::Release>(kMethodNameRelease), + InstanceMethod<&NapiAudioSource::SetVolume>(kMethodNameSetVolume), + InstanceMethod<&NapiAudioSource::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiAudioSource::NewInstance(Napi::Env env, rtc::scoped_refptr source) +{ + if (!source) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + auto external = External::New( + env, source.release(), [](Napi::Env /*env*/, AudioSourceInterface* source) { source->Release(); }); + + return constructor_.New({external}); +} + +NapiAudioSource::NapiAudioSource(const Napi::CallbackInfo& info) : NapiMediaSource(info) +{ + if (info[0].IsExternal()) { + auto source = info[0].As>().Data(); + source_ = rtc::scoped_refptr(source); + } +} + +NapiAudioSource::~NapiAudioSource() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +Napi::Value NapiAudioSource::GetState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + auto state = source_->state(); + switch (state) { + case MediaSourceInterface::kInitializing: + return String::New(info.Env(), kEnumNameSourceStateInitializing); + case MediaSourceInterface::kLive: + return String::New(info.Env(), kEnumNameSourceStateLive); + case MediaSourceInterface::kEnded: + return String::New(info.Env(), kEnumNameSourceStateEnded); + case MediaSourceInterface::kMuted: + return String::New(info.Env(), kEnumNameSourceStateMuted); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiAudioSource::Release(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + source_ = nullptr; + + return info.Env().Undefined(); +} + +Napi::Value NapiAudioSource::SetVolume(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsNumber()) { + NAPI_THROW(TypeError::New(info.Env(), "The argument is not number"), info.Env().Undefined()); + } + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + double volume = info[0].As().DoubleValue(); + source_->SetVolume(volume); + + return info.Env().Undefined(); +} + +Value NapiAudioSource::ToJson(const CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiAudioSource")); +#endif + + return json; +} + +FunctionReference NapiVideoSource::constructor_; + +void NapiVideoSource::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiVideoSource::GetState>(kAttributeNameState), + InstanceAccessor<&NapiVideoSource::GetEventHandler, &NapiVideoSource::SetEventHandler>( + kAttributeNameOnCapturerStarted, napi_default, (void*)kEventNameCapturerStarted), + InstanceAccessor<&NapiVideoSource::GetEventHandler, &NapiVideoSource::SetEventHandler>( + kAttributeNameOnCapturerStopped, napi_default, (void*)kEventNameCapturerStopped), + InstanceMethod<&NapiVideoSource::Release>(kMethodNameRelease), + InstanceMethod<&NapiVideoSource::StartCapture>(kMethodNameStartCapture), + InstanceMethod<&NapiVideoSource::StopCapture>(kMethodNameStopCapture), + InstanceMethod<&NapiVideoSource::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiVideoSource::NewInstance(Napi::Env env, rtc::scoped_refptr source) +{ + if (!source) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto external = External::New( + env, source.release(), [](Napi::Env /*env*/, OhosVideoTrackSource* source) { source->Release(); }); + + return constructor_.New({external}); +} + +NapiVideoSource::NapiVideoSource(const Napi::CallbackInfo& info) : NapiMediaSource(info) +{ + if (info[0].IsExternal()) { + auto source = info[0].As>().Data(); + source_ = rtc::scoped_refptr(source); + } + + if (source_) { + source_->SetCapturerObserver(this); + } +} + +NapiVideoSource::~NapiVideoSource() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + source_->SetCapturerObserver(nullptr); + source_ = nullptr; + + RemoveAllEventHandlers(); +} + +Napi::Value NapiVideoSource::GetState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + auto source = static_cast(source_.get()); + auto state = source->state(); + switch (state) { + case MediaSourceInterface::kInitializing: + return String::New(info.Env(), kEnumNameSourceStateInitializing); + case MediaSourceInterface::kLive: + return String::New(info.Env(), kEnumNameSourceStateLive); + case MediaSourceInterface::kEnded: + return String::New(info.Env(), kEnumNameSourceStateEnded); + case MediaSourceInterface::kMuted: + return String::New(info.Env(), kEnumNameSourceStateMuted); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiVideoSource::Release(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + source_->AddRef(); + + source_->SetCapturerObserver(nullptr); + source_ = nullptr; + + RemoveAllEventHandlers(); + + return info.Env().Undefined(); +} + +Napi::Value NapiVideoSource::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + Function fn; + if (!GetEventHandler((const char*)info.Data(), fn)) { + return info.Env().Null(); + } + + return fn; +} + +void NapiVideoSource::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW_VOID(Error::New(info.Env(), "Illegal state")); + } + + RemoveEventHandler((const char*)info.Data()); + + if (value.IsFunction()) { + Function cb = value.As(); + if (!SetEventHandler((const char*)info.Data(), std::move(cb), info.This())) { + NAPI_THROW_VOID(Napi::Error::New(info.Env(), "Failed to set event handler")); + } + } +} + +Napi::Value NapiVideoSource::StartCapture(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + source_->Start(); + + return info.Env().Undefined(); +} + +Napi::Value NapiVideoSource::StopCapture(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!source_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + source_->Stop(); + + return info.Env().Undefined(); +} + +Value NapiVideoSource::ToJson(const CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiVideoSource")); +#endif + + return json; +} + +void NapiVideoSource::OnCapturerStarted(bool success) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventNameCapturerStarted, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context, success](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("type", String::New(env, "VideoCapturerStartedEvent")); + jsEvent.Set("success", Boolean::New(env, success)); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } +} + +void NapiVideoSource::OnCapturerStopped() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventNameCapturerStopped, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("type", String::New(env, "Event")); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } +} + +bool NapiVideoSource::GetEventHandler(const std::string& type, Napi::Function& fn) +{ + RTC_DLOG(LS_VERBOSE) << "NapiPeerConnectionWrapper::GetEventHandler type: " << type; + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + RTC_LOG(LS_VERBOSE) << "No event handler for type: " << type; + return false; + } + + fn = it->second.ref.Value(); + + return true; +} + +bool NapiVideoSource::GetEventHandler(const std::string& type, Napi::ThreadSafeFunction& tsfn) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " type=" << type; + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + RTC_LOG(LS_VERBOSE) << "No event handler for type: " << type; + return false; + } + + tsfn = it->second.tsfn; + + return true; +} + +bool NapiVideoSource::SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver) +{ + RTC_DLOG(LS_VERBOSE) << "SetEventHandler type: " << type; + + Reference* context = new Reference; + *context = Persistent(receiver); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = + ThreadSafeFunction::New(fn.Env(), fn, type, 0, 1, context, [](Napi::Env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + + UNUSED std::lock_guard lock(eventMutex_); + eventHandlers_[type] = std::move(handler); + + return true; +} + +bool NapiVideoSource::RemoveEventHandler(const std::string& type) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + it->second.tsfn.Release(); + eventHandlers_.erase(it); + } + + return true; +} + +void NapiVideoSource::RemoveAllEventHandlers() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(eventMutex_); + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } + eventHandlers_.clear(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/media_source.h b/sdk/ohos/src/ohos_webrtc/media_source.h new file mode 100644 index 0000000000000000000000000000000000000000..839312d1bf874d93c13c24b70c61f38d9bb0b841 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_source.h @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_AUDIO_SOURCE_H +#define WEBRTC_AUDIO_SOURCE_H + +#include "napi.h" + +#include "api/media_stream_interface.h" +#include "utils/marcos.h" +#include "video/video_track_source.h" + +namespace webrtc { + +template +class NapiMediaSource : public Napi::ObjectWrap { +public: + NAPI_ATTRIBUTE_NAME_DECLARE(State, state); + NAPI_METHOD_NAME_DECLARE(Release, release); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + NAPI_ENUM_NAME_DECLARE(SourceStateInitializing, initializing); + NAPI_ENUM_NAME_DECLARE(SourceStateLive, live); + NAPI_ENUM_NAME_DECLARE(SourceStateEnded, ended); + NAPI_ENUM_NAME_DECLARE(SourceStateMuted, muted); + + explicit NapiMediaSource(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} +}; + +class NapiAudioSource : public NapiMediaSource { +public: + NAPI_CLASS_NAME_DECLARE(AudioSource); + NAPI_METHOD_NAME_DECLARE(SetVolume, setVolume); + + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr source); + + ~NapiAudioSource() override; + + AudioSourceInterface* Get() const + { + return source_.get(); + } + +protected: + friend class ObjectWrap; + + explicit NapiAudioSource(const Napi::CallbackInfo& info); + + Napi::Value GetState(const Napi::CallbackInfo& info); + Napi::Value Release(const Napi::CallbackInfo& info); + Napi::Value SetVolume(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr source_; +}; + +class NapiVideoSource : public NapiMediaSource, public VideoCapturer::Observer { +public: + NAPI_CLASS_NAME_DECLARE(VideoSource); + NAPI_ATTRIBUTE_NAME_DECLARE(OnCapturerStarted, oncapturerstarted); + NAPI_ATTRIBUTE_NAME_DECLARE(OnCapturerStopped, oncapturerstopped); + NAPI_EVENT_NAME_DECLARE(CapturerStarted, capturerstarted); + NAPI_EVENT_NAME_DECLARE(CapturerStopped, capturerstopped); + NAPI_METHOD_NAME_DECLARE(StartCapture, startCapture); + NAPI_METHOD_NAME_DECLARE(StopCapture, stopCapture); + + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr source); + + ~NapiVideoSource() override; + + VideoTrackSourceInterface* Get() const + { + return source_.get(); + } + +protected: + friend class ObjectWrap; + + explicit NapiVideoSource(const Napi::CallbackInfo& info); + + Napi::Value GetState(const Napi::CallbackInfo& info); + Napi::Value Release(const Napi::CallbackInfo& info); + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + Napi::Value StartCapture(const Napi::CallbackInfo& info); + Napi::Value StopCapture(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +protected: + // VideoCapturer::Observer + void OnCapturerStarted(bool success) override; + void OnCapturerStopped() override; + void + OnFrameCaptured(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) override {} + +private: + bool GetEventHandler(const std::string& type, Napi::Function& fn); + bool GetEventHandler(const std::string& type, Napi::ThreadSafeFunction& tsfn); + bool SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver); + bool RemoveEventHandler(const std::string& type); + void RemoveAllEventHandlers(); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr source_; + rtc::Thread* signalThread_{}; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex eventMutex_; + std::map eventHandlers_; +}; + +} // namespace webrtc + +#endif // WEBRTC_AUDIO_SOURCE_H diff --git a/sdk/ohos/src/ohos_webrtc/media_stream.cpp b/sdk/ohos/src/ohos_webrtc/media_stream.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0a7c8c34fb7cda048e3e38f6c268109b84fd37d7 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_stream.cpp @@ -0,0 +1,388 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_stream.h" +#include "media_stream_track.h" +#include "peer_connection_factory.h" + +#include "api/rtc_error.h" +#include "rtc_base/logging.h" +#include "rtc_base/helpers.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "MediaStream"; + +const char kAttributeNameId[] = "id"; +const char kAttributeNameActive[] = "active"; + +const char kMethodNameAddTrack[] = "addTrack"; +const char kMethodNameRemoveTrack[] = "removeTrack"; +const char kMethodNameGetTrackById[] = "getTrackById"; +const char kMethodNameGetTracks[] = "getTracks"; +const char kMethodNameGetAudioTracks[] = "getAudioTracks"; +const char kMethodNameGetVideoTracks[] = "getVideoTracks"; +const char kMethodNameToJson[] = "toJSON"; + +FunctionReference NapiMediaStream::constructor_; + +void NapiMediaStream::Init(Napi::Env env, Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiMediaStream::GetId>(kAttributeNameId), + InstanceAccessor<&NapiMediaStream::GetActive>(kAttributeNameActive), + InstanceMethod<&NapiMediaStream::AddTrack>(kMethodNameAddTrack), + InstanceMethod<&NapiMediaStream::RemoveTrack>(kMethodNameRemoveTrack), + InstanceMethod<&NapiMediaStream::GetTrackById>(kMethodNameGetTrackById), + InstanceMethod<&NapiMediaStream::GetTracks>(kMethodNameGetTracks), + InstanceMethod<&NapiMediaStream::GetAudioTracks>(kMethodNameGetAudioTracks), + InstanceMethod<&NapiMediaStream::GetVideoTracks>(kMethodNameGetVideoTracks), + InstanceMethod<&NapiMediaStream::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Object NapiMediaStream::NewInstance(Napi::Env env, rtc::scoped_refptr stream) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!stream) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + auto external = + External::New(env, stream.release(), [](Napi::Env, MediaStreamInterface* stream) { + auto status = stream->Release(); + RTC_DLOG(LS_VERBOSE) << "NapiMediaStream release status=" << status; + }); + + return constructor_.New({external}); +} + +NapiMediaStream::NapiMediaStream(const CallbackInfo& info) : ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() > 0 && info[0].IsExternal()) { + auto stream = info[0].As>().Data(); + stream_ = rtc::scoped_refptr(stream); + } + + AudioTrackVector audioTracks; + VideoTrackVector videoTracks; + if (info.Length() > 0 && info[0].IsObject()) { + if (info[0].IsArray()) { + auto jsTracks = info[0].As(); + for (uint32_t i = 0; i < jsTracks.Length(); i++) { + Napi::Value jsTrackValue = jsTracks[i]; + auto napiTrack = NapiMediaStreamTrack::Unwrap(jsTrackValue.As()); + if (!napiTrack || !napiTrack->Get()) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + auto track = napiTrack->Get(); + if (track->kind() == MediaStreamTrackInterface::kAudioKind) { + auto audioTrack = + rtc::scoped_refptr(static_cast(track.get())); + audioTracks.push_back(audioTrack); + } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + auto videoTrack = + rtc::scoped_refptr(static_cast(track.get())); + videoTracks.push_back(videoTrack); + } else { + RTC_LOG(LS_WARNING) << "Unknown type of media stream track: " << track->id(); + } + } + } else { + auto napiStream = NapiMediaStream::Unwrap(info[0].As()); + if (!napiStream || !napiStream->Get()) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + auto stream = napiStream->Get(); + audioTracks = stream->GetAudioTracks(); + videoTracks = stream->GetVideoTracks(); + } + } + + if (!stream_) { + auto pcFactory = PeerConnectionFactoryWrapper::GetDefault(); + if (!pcFactory || !pcFactory->GetFactory()) { + NAPI_THROW_VOID(Error::New(info.Env(), "No default peer connection factory")); + } + + stream_ = pcFactory->GetFactory()->CreateLocalMediaStream(rtc::CreateRandomUuid()); + if (!stream_) { + NAPI_THROW_VOID(Error::New(info.Env(), "Failed to create media stream")); + } + } + + for (auto& track : audioTracks) { + stream_->AddTrack(track); + } + + for (auto& track : videoTracks) { + stream_->AddTrack(track); + } + + observer_.reset(new MediaStreamObserver( + stream_.get(), + [this](AudioTrackInterface* audio_track, MediaStreamInterface* media_stream) { + OnAudioTrackAddedToStream(audio_track, media_stream); + }, + [this](AudioTrackInterface* audio_track, MediaStreamInterface* media_stream) { + OnAudioTrackRemovedFromStream(audio_track, media_stream); + }, + [this](VideoTrackInterface* video_track, MediaStreamInterface* media_stream) { + OnVideoTrackAddedToStream(video_track, media_stream); + }, + [this](VideoTrackInterface* video_track, MediaStreamInterface* media_stream) { + OnVideoTrackRemovedFromStream(video_track, media_stream); + })); +} + +NapiMediaStream::~NapiMediaStream() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +Napi::Value NapiMediaStream::GetId(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return String::New(info.Env(), stream_->id()); +} + +Napi::Value NapiMediaStream::GetActive(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto active = false; + for (const auto& track : stream_->GetAudioTracks()) { + if (track->state() != MediaStreamTrackInterface::kEnded) { + active = true; + } + } + for (const auto& track : stream_->GetVideoTracks()) { + if (track->state() != MediaStreamTrackInterface::kEnded) { + active = true; + } + } + + return Boolean::New(info.Env(), active); +} + +Napi::Value NapiMediaStream::AddTrack(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto jsTrack = info[0].As(); + auto nativeTrack = NapiMediaStreamTrack::Unwrap(jsTrack); + if (!nativeTrack) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto track = nativeTrack->Get(); + if (!track) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + bool success = false; + if (track->kind() == MediaStreamTrackInterface::kAudioKind) { + auto audioTrack = rtc::scoped_refptr(static_cast(track.get())); + success = stream_->AddTrack(audioTrack); + } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + auto videoTrack = rtc::scoped_refptr(static_cast(track.get())); + success = stream_->AddTrack(videoTrack); + } else { + RTC_LOG(LS_WARNING) << "Unknown type of media stream track: " << track->id(); + } + + if (!success) { + RTC_LOG(LS_ERROR) << "Failed to add track to media stream"; + NAPI_THROW(Error::New(info.Env(), "Unknown error"), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiMediaStream::RemoveTrack(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto jsTrack = info[0].As(); + auto nativeTrack = NapiMediaStreamTrack::Unwrap(jsTrack); + if (!nativeTrack) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto track = nativeTrack->Get(); + if (!track) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + bool success = false; + if (track->kind() == MediaStreamTrackInterface::kAudioKind) { + auto audioTrack = rtc::scoped_refptr(static_cast(track.get())); + success = stream_->RemoveTrack(audioTrack); + } else if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + auto videoTrack = rtc::scoped_refptr(static_cast(track.get())); + success = stream_->RemoveTrack(videoTrack); + } else { + RTC_LOG(LS_WARNING) << "Unknown type of media stream track: " << track->id(); + } + + if (!success) { + RTC_LOG(LS_ERROR) << "Failed to remove track from media stream"; + NAPI_THROW(Error::New(info.Env(), "Unknown error"), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiMediaStream::GetTrackById(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto trackId = info[0].As().Utf8Value(); + auto audioTrack = stream_->FindAudioTrack(trackId); + if (audioTrack) { + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(audioTrack)); + } + + auto videoTrack = stream_->FindVideoTrack(trackId); + if (videoTrack) { + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(videoTrack)); + } + + RTC_LOG(LS_INFO) << "No track with id: " << trackId; + + return info.Env().Null(); +} + +Napi::Value NapiMediaStream::GetTracks(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto audioTracks = stream_->GetAudioTracks(); + auto videoTracks = stream_->GetVideoTracks(); + + auto result = Array::New(info.Env(), audioTracks.size() + videoTracks.size()); + for (uint32_t i = 0; i < audioTracks.size(); i++) { + result[i] = NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(audioTracks[i])); + } + for (uint32_t i = 0; i < videoTracks.size(); i++) { + result[audioTracks.size() + i] = + NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(videoTracks[i])); + } + + return result; +} + +Napi::Value NapiMediaStream::GetAudioTracks(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto audioTracks = stream_->GetAudioTracks(); + + auto result = Array::New(info.Env(), audioTracks.size()); + for (uint32_t i = 0; i < audioTracks.size(); i++) { + result[i] = NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(audioTracks[i])); + } + + return result; +} + +Napi::Value NapiMediaStream::GetVideoTracks(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto videoTracks = stream_->GetVideoTracks(); + + auto result = Array::New(info.Env(), videoTracks.size()); + for (uint32_t i = 0; i < videoTracks.size(); i++) { + result[i] = NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(videoTracks[i])); + } + + return result; +} + +Value NapiMediaStream::ToJson(const CallbackInfo& info) +{ + auto json = Object::New(info.Env()); + json.Set(kAttributeNameId, GetId(info)); + json.Set(kAttributeNameActive, GetActive(info)); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiMediaStream")); +#endif + + return json; +} + +void NapiMediaStream::OnAudioTrackAddedToStream(AudioTrackInterface* track, MediaStreamInterface* stream) +{ + (void)stream; + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " track: " << track->id(); +} + +void NapiMediaStream::OnVideoTrackAddedToStream(VideoTrackInterface* track, MediaStreamInterface* stream) +{ + (void)stream; + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " track: " << track->id(); +} + +void NapiMediaStream::OnAudioTrackRemovedFromStream(AudioTrackInterface* track, MediaStreamInterface* stream) +{ + (void)stream; + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " track: " << track->id(); +} + +void NapiMediaStream::OnVideoTrackRemovedFromStream(VideoTrackInterface* track, MediaStreamInterface* stream) +{ + (void)stream; + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " track: " << track->id(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/media_stream.h b/sdk/ohos/src/ohos_webrtc/media_stream.h new file mode 100644 index 0000000000000000000000000000000000000000..ab9807e7f8387737e70adc969d3178cbbcbadddc --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_stream.h @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_MEDIA_STREAM_H +#define WEBRTC_MEDIA_STREAM_H + +#include "napi.h" + +#include "api/media_stream_interface.h" +#include "pc/media_stream_observer.h" + +namespace webrtc { + +class NapiMediaStream : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, rtc::scoped_refptr stream); + + ~NapiMediaStream() override; + + rtc::scoped_refptr Get() const + { + return stream_; + } + +protected: + friend class ObjectWrap; + + explicit NapiMediaStream(const Napi::CallbackInfo& info); + + Napi::Value GetId(const Napi::CallbackInfo& info); + Napi::Value GetActive(const Napi::CallbackInfo& info); + + Napi::Value AddTrack(const Napi::CallbackInfo& info); + Napi::Value RemoveTrack(const Napi::CallbackInfo& info); + Napi::Value GetTrackById(const Napi::CallbackInfo& info); + Napi::Value GetTracks(const Napi::CallbackInfo& info); + Napi::Value GetAudioTracks(const Napi::CallbackInfo& info); + Napi::Value GetVideoTracks(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +protected: + void OnAudioTrackAddedToStream(AudioTrackInterface* track, MediaStreamInterface* stream); + void OnVideoTrackAddedToStream(VideoTrackInterface* track, MediaStreamInterface* stream); + void OnAudioTrackRemovedFromStream(AudioTrackInterface* track, MediaStreamInterface* stream); + void OnVideoTrackRemovedFromStream(VideoTrackInterface* track, MediaStreamInterface* stream); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr stream_; + std::unique_ptr observer_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MEDIA_STREAM_H diff --git a/sdk/ohos/src/ohos_webrtc/media_stream_track.cpp b/sdk/ohos/src/ohos_webrtc/media_stream_track.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b3fa6c99846442c1099c3e43a5c5973437a52e12 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_stream_track.cpp @@ -0,0 +1,364 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_stream_track.h" + +#include "rtc_base/logging.h" +#include "utils/marcos.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "MediaStreamTrack"; + +const char kAttributeNameId[] = "id"; +const char kAttributeNameKind[] = "kind"; +const char kAttributeNameLabel[] = "label"; +const char kAttributeNameEnabled[] = "enabled"; +const char kAttributeNameMuted[] = "muted"; +const char kAttributeNameReadyState[] = "readyState"; +const char kAttributeNameOnMute[] = "onmute"; +const char kAttributeNameOnUnmute[] = "onunmute"; +const char kAttributeNameOnEnded[] = "onended"; + +const char kMethodNameStop[] = "stop"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEnumMediaStreamTrackStateLive[] = "live"; +const char kEnumMediaStreamTrackStateEnded[] = "ended"; + +const char kEventNameMute[] = "mute"; +const char kEventNameUnmute[] = "unmute"; +const char kEventNameEnded[] = "ended"; + +std::unique_ptr +MediaStreamTrackWrapper::Create(rtc::scoped_refptr track) +{ + if (!track) { + return nullptr; + } + + return std::unique_ptr(new MediaStreamTrackWrapper(track)); +} + +MediaStreamTrackWrapper::MediaStreamTrackWrapper(rtc::scoped_refptr track) : track_(track) +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + track_->RegisterObserver(this); +} + +MediaStreamTrackWrapper::~MediaStreamTrackWrapper() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + track_->UnregisterObserver(this); + + RemoveAllVideoSinks(); +} + +void MediaStreamTrackWrapper::AddVideoSink(rtc::VideoSinkInterface* sink) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (track_->kind() != MediaStreamTrackInterface::kVideoKind) { + RTC_LOG(LS_WARNING) << "Not a video track"; + return; + } + + { + UNUSED std::lock_guard lock(sinksMutex_); + auto ret = videoSinks_.insert(sink); + if (!ret.second) { + RTC_LOG(LS_WARNING) << "Failed to insert video sink"; + return; + } + } + + auto videoTrack = static_cast(track_.get()); + videoTrack->AddOrUpdateSink(sink, rtc::VideoSinkWants()); +} + +void MediaStreamTrackWrapper::RemoveVideoSink(rtc::VideoSinkInterface* sink) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (track_->kind() != MediaStreamTrackInterface::kVideoKind) { + RTC_LOG(LS_WARNING) << "Not a video track"; + return; + } + + { + UNUSED std::lock_guard lock(sinksMutex_); + if (videoSinks_.erase(sink) == 0) { + RTC_LOG(LS_WARNING) << "Failed to erase video sink"; + return; + } + } + + auto videoTrack = static_cast(track_.get()); + videoTrack->RemoveSink(sink); +} + +void MediaStreamTrackWrapper::RemoveAllVideoSinks() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (track_->kind() != MediaStreamTrackInterface::kVideoKind) { + return; + } + + std::set*> sinks; + { + UNUSED std::lock_guard lock(sinksMutex_); + videoSinks_.swap(sinks); + } + + auto videoTrack = static_cast(track_.get()); + for (auto& sink : sinks) { + if (sink) { + videoTrack->RemoveSink(sink); + } + } +} + +void MediaStreamTrackWrapper::OnChanged() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__ << "(" << track_->kind() << ") state=" << track_->state() + << ", enabled=" << track_->enabled(); +} + +FunctionReference NapiMediaStreamTrack::constructor_; + +void NapiMediaStreamTrack::Init(Napi::Env env, Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiMediaStreamTrack::GetKind>(kAttributeNameKind), + InstanceAccessor<&NapiMediaStreamTrack::GetId>(kAttributeNameId), + InstanceAccessor<&NapiMediaStreamTrack::GetEnabled, &NapiMediaStreamTrack::SetEnabled>( + kAttributeNameEnabled), + InstanceAccessor<&NapiMediaStreamTrack::GetReadyState>(kAttributeNameReadyState), + InstanceMethod<&NapiMediaStreamTrack::Stop>(kMethodNameStop), + InstanceMethod<&NapiMediaStreamTrack::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiMediaStreamTrack::NewInstance(Napi::Env env, std::unique_ptr wrapper) +{ + if (!wrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object::New(env)); + } + + return constructor_.New({External::New(env, wrapper.release())}); +} + +NapiMediaStreamTrack::NapiMediaStreamTrack(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + if (info.Length() > 0 && info[0].IsExternal()) { + auto wrapper = info[0].As>().Data(); + wrapper_ = std::unique_ptr(wrapper); + } +} + +NapiMediaStreamTrack::~NapiMediaStreamTrack() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +bool NapiMediaStreamTrack::IsAudioTrack() const +{ + if (!wrapper_) { + return false; + } + + auto track = wrapper_->track(); + return (track->kind() == MediaStreamTrackInterface::kAudioKind); +} + +bool NapiMediaStreamTrack::IsVideoTrack() const +{ + if (!wrapper_) { + return false; + } + + auto track = wrapper_->track(); + return (track->kind() == MediaStreamTrackInterface::kVideoKind); +} + +AudioTrackInterface* NapiMediaStreamTrack::GetAudioTrack() const +{ + if (!wrapper_) { + return nullptr; + } + + auto track = wrapper_->track(); + if (track->kind() == MediaStreamTrackInterface::kAudioKind) { + return static_cast(track); + } + + return nullptr; +} + +VideoTrackInterface* NapiMediaStreamTrack::GetVideoTrack() const +{ + if (!wrapper_) { + return nullptr; + } + + auto track = wrapper_->track(); + if (track->kind() == MediaStreamTrackInterface::kVideoKind) { + return static_cast(track); + } + return nullptr; +} + +void NapiMediaStreamTrack::AddSink(rtc::VideoSinkInterface* sink) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + RTC_LOG(LS_WARNING) << "Track is released"; + return; + } + + wrapper_->AddVideoSink(sink); +} + +void NapiMediaStreamTrack::RemoveSink(rtc::VideoSinkInterface* sink) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + RTC_LOG(LS_WARNING) << "Track is released"; + return; + } + + wrapper_->RemoveVideoSink(sink); +} + +Napi::Value NapiMediaStreamTrack::GetId(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + return String::New(info.Env(), wrapper_->track()->id()); +} + +Napi::Value NapiMediaStreamTrack::GetKind(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + return String::New(info.Env(), wrapper_->track()->kind()); +} + +Napi::Value NapiMediaStreamTrack::GetEnabled(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + return Boolean::New(info.Env(), wrapper_->track()->enabled()); +} + +void NapiMediaStreamTrack::SetEnabled(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!value.IsBoolean()) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + if (!wrapper_) { + NAPI_THROW_VOID(Error::New(info.Env(), "Illegal state")); + } + + wrapper_->track()->set_enabled(value.As()); +} + +Napi::Value NapiMediaStreamTrack::GetReadyState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + auto state = wrapper_->track()->state(); + switch (state) { + case MediaStreamTrackInterface::kLive: + return String::New(info.Env(), kEnumMediaStreamTrackStateLive); + case MediaStreamTrackInterface::kEnded: + return String::New(info.Env(), kEnumMediaStreamTrackStateEnded); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiMediaStreamTrack::Stop(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!wrapper_) { + NAPI_THROW(Error::New(info.Env(), "Illegal state"), info.Env().Undefined()); + } + + auto state = wrapper_->track()->state(); + if (state == MediaStreamTrackInterface::kEnded) { + RTC_LOG(LS_VERBOSE) << "The track is already ended"; + return info.Env().Undefined(); + } + + wrapper_->RemoveAllVideoSinks(); + + return info.Env().Undefined(); +} + +Napi::Value NapiMediaStreamTrack::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiMediaStreamTrack"); +#endif + if (wrapper_) { + json.Set(kAttributeNameId, GetId(info)); + json.Set(kAttributeNameKind, GetKind(info)); + json.Set(kAttributeNameEnabled, GetEnabled(info)); + json.Set(kAttributeNameReadyState, GetReadyState(info)); + } + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/media_stream_track.h b/sdk/ohos/src/ohos_webrtc/media_stream_track.h new file mode 100644 index 0000000000000000000000000000000000000000..c2c823f316e12125af1624b428074f5e41ac4a71 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_stream_track.h @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_MEDIA_STREAM_TRACK_H +#define WEBRTC_MEDIA_STREAM_TRACK_H + +#include + +#include "napi.h" + +#include "api/media_stream_interface.h" +#include "api/peer_connection_interface.h" + +namespace webrtc { + +class MediaStreamTrackWrapper : public ObserverInterface { +public: + static std::unique_ptr Create(rtc::scoped_refptr track); + + ~MediaStreamTrackWrapper() override; + + const MediaStreamTrackInterface* track() const + { + return track_.get(); + } + + MediaStreamTrackInterface* track() + { + return track_.get(); + } + + void AddVideoSink(rtc::VideoSinkInterface* sink); + void RemoveVideoSink(rtc::VideoSinkInterface* sink); + void RemoveAllVideoSinks(); + +protected: + explicit MediaStreamTrackWrapper(rtc::scoped_refptr track); + +protected: + void OnChanged() override; + +private: + rtc::scoped_refptr track_; + std::mutex sinksMutex_; + std::set*> videoSinks_; +}; + +class NapiMediaStreamTrack : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, std::unique_ptr wrapper); + + ~NapiMediaStreamTrack() override; + + rtc::scoped_refptr Get() const + { + if (!wrapper_) { + return nullptr; + } + return rtc::scoped_refptr(wrapper_->track()); + } + + bool IsAudioTrack() const; + bool IsVideoTrack() const; + + AudioTrackInterface* GetAudioTrack() const; + VideoTrackInterface* GetVideoTrack() const; + + void AddSink(rtc::VideoSinkInterface* sink); + void RemoveSink(rtc::VideoSinkInterface* sink); + +protected: + friend class ObjectWrap; + + explicit NapiMediaStreamTrack(const Napi::CallbackInfo& info); + + Napi::Value GetId(const Napi::CallbackInfo& info); + Napi::Value GetKind(const Napi::CallbackInfo& info); + Napi::Value GetEnabled(const Napi::CallbackInfo& info); + void SetEnabled(const Napi::CallbackInfo& info, const Napi::Value& value); + Napi::Value GetReadyState(const Napi::CallbackInfo& info); + Napi::Value Stop(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::unique_ptr wrapper_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MEDIA_STREAM_TRACK_H diff --git a/sdk/ohos/src/ohos_webrtc/media_track_constraints.cpp b/sdk/ohos/src/ohos_webrtc/media_track_constraints.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f555fdf06dae999451303727c1a0878733bace1e --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_track_constraints.cpp @@ -0,0 +1,500 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_track_constraints.h" + +#include +#include +#include + +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +// A naked value is treated as an "ideal" value in the basic constraints, +// but as an exact value in "advanced" constraints. +// https://www.w3.org/TR/mediacapture-streams/#constrainable-interface +enum class NakedValueDisposition { kTreatAsIdeal, kTreatAsExact }; + +const size_t kMaxConstraintStringLength = 500; +const size_t kMaxConstraintStringSeqLength = 100; + +const char kConstraintsMin[] = "min"; +const char kConstraintsMax[] = "max"; +const char kConstraintsExact[] = "exact"; +const char kConstraintsIdeal[] = "ideal"; +const char kConstraintsAdvanced[] = "advanced"; + +const std::map SUPPORTED_CONSTRAINTS_MAP = { + {NapiMediaConstraints::kAttributeNameWidth, true}, + {NapiMediaConstraints::kAttributeNameHeight, true}, + {NapiMediaConstraints::kAttributeNameAspectRatio, true}, + {NapiMediaConstraints::kAttributeNameFrameRate, true}, + {NapiMediaConstraints::kAttributeNameFacingMode, true}, + {NapiMediaConstraints::kAttributeNameResizeMode, false}, + {NapiMediaConstraints::kAttributeNameSampleRate, false}, + {NapiMediaConstraints::kAttributeNameSampleSize, false}, + {NapiMediaConstraints::kAttributeNameEchoCancellation, true}, + {NapiMediaConstraints::kAttributeNameAutoGainControl, true}, + {NapiMediaConstraints::kAttributeNameNoiseSuppression, true}, + {NapiMediaConstraints::kAttributeNameLatency, false}, + {NapiMediaConstraints::kAttributeNameChannelCount, false}, + {NapiMediaConstraints::kAttributeNameDeviceId, true}, + {NapiMediaConstraints::kAttributeNameGroupId, true}, + {NapiMediaConstraints::kAttributeNameDisplaySurface, false}, + {NapiMediaConstraints::kAttributeNameBackgroundBlur, false}, + {NapiMediaConstraints::kAttributeNameGoogEchoCancellation, false}, + {NapiMediaConstraints::kAttributeNameGoogAutoGainControl, false}, + {NapiMediaConstraints::kAttributeNameGoogNoiseSuppression, false}, + {NapiMediaConstraints::kAttributeNameGoogHighpassFilter, false}, + {NapiMediaConstraints::kAttributeNameGoogAudioMirroring, false}, +}; + +void CopyLongConstraint(const Napi::Value& jsValue, NakedValueDisposition naked_treatment, LongConstraint& nativeValue) +{ + if (jsValue.IsNumber()) { + switch (naked_treatment) { + case NakedValueDisposition::kTreatAsIdeal: + nativeValue.SetIdeal(jsValue.As().Int32Value()); + break; + case NakedValueDisposition::kTreatAsExact: + nativeValue.SetExact(jsValue.As().Int32Value()); + break; + } + } else if (jsValue.IsObject()) { + auto jsConstrainULongRange = jsValue.As(); + if (jsConstrainULongRange.Has(kConstraintsMin)) { + nativeValue.SetMin(jsConstrainULongRange.Get(kConstraintsMin).As().Int32Value()); + } + if (jsConstrainULongRange.Has(kConstraintsMax)) { + nativeValue.SetMax(jsConstrainULongRange.Get(kConstraintsMax).As().Int32Value()); + } + if (jsConstrainULongRange.Has(kConstraintsIdeal)) { + nativeValue.SetIdeal(jsConstrainULongRange.Get(kConstraintsIdeal).As().Int32Value()); + } + if (jsConstrainULongRange.Has(kConstraintsExact)) { + nativeValue.SetExact(jsConstrainULongRange.Get(kConstraintsExact).As().Int32Value()); + } + } +} + +void CopyDoubleConstraint( + const Napi::Value& jsValue, NakedValueDisposition naked_treatment, DoubleConstraint& nativeValue) +{ + if (jsValue.IsNumber()) { + switch (naked_treatment) { + case NakedValueDisposition::kTreatAsIdeal: + nativeValue.SetIdeal(jsValue.As().DoubleValue()); + break; + case NakedValueDisposition::kTreatAsExact: + nativeValue.SetExact(jsValue.As().DoubleValue()); + break; + } + } else if (jsValue.IsObject()) { + auto jsConstrainDoubleRange = jsValue.As(); + if (jsConstrainDoubleRange.Has(kConstraintsMin)) { + nativeValue.SetMin(jsConstrainDoubleRange.Get(kConstraintsMin).As().DoubleValue()); + } + if (jsConstrainDoubleRange.Has(kConstraintsMax)) { + nativeValue.SetMax(jsConstrainDoubleRange.Get(kConstraintsMax).As().DoubleValue()); + } + if (jsConstrainDoubleRange.Has(kConstraintsIdeal)) { + nativeValue.SetIdeal(jsConstrainDoubleRange.Get(kConstraintsIdeal).As().DoubleValue()); + } + if (jsConstrainDoubleRange.Has(kConstraintsExact)) { + nativeValue.SetExact(jsConstrainDoubleRange.Get(kConstraintsExact).As().DoubleValue()); + } + } +} + +bool ValidateString(const std::string& str, std::string& error_message) +{ + if (str.length() > kMaxConstraintStringLength) { + error_message = "Constraint string too long."; + return false; + } + return true; +} + +bool ValidateStringSeq(const std::vector& strs, std::string& error_message) +{ + if (strs.size() > kMaxConstraintStringSeqLength) { + error_message = "Constraint string sequence too long."; + return false; + } + + for (const std::string& str : strs) { + if (!ValidateString(str, error_message)) { + return false; + } + } + + return true; +} + +bool ValidateStringConstraint(const Value& jsValue, std::string& error_message) +{ + if (jsValue.IsString()) { + return ValidateString(jsValue.As().Utf8Value(), error_message); + } else if (jsValue.IsArray()) { + auto jsArray = jsValue.As(); + if (jsArray.Length() > kMaxConstraintStringSeqLength) { + error_message = "Constraint string sequence too long."; + return false; + } + + for (uint32_t index = 0; index < jsArray.Length(); index++) { + if (!ValidateString(jsValue.As().Utf8Value(), error_message)) { + return false; + } + } + return true; + } else if (jsValue.IsObject()) { + auto jsObject = jsValue.As(); + if (jsObject.Has(kConstraintsIdeal) && + !ValidateStringConstraint(jsObject.Get(kConstraintsIdeal).As(), error_message)) + { + return false; + } + if (jsObject.Has(kConstraintsExact) && + !ValidateStringConstraint(jsObject.Get(kConstraintsExact).As(), error_message)) + { + return false; + } + return true; + } + + return false; +} + +bool ValidateAndCopyStringConstraint( + const Value& jsValue, NakedValueDisposition naked_treatment, StringConstraint& nativeValue, + std::string& error_message) +{ + if (!ValidateStringConstraint(jsValue, error_message)) { + return false; + } + + if (jsValue.IsString()) { + switch (naked_treatment) { + case NakedValueDisposition::kTreatAsIdeal: + nativeValue.SetIdeal(std::vector(1, jsValue.As().Utf8Value())); + break; + case NakedValueDisposition::kTreatAsExact: + nativeValue.SetExact(std::vector(1, jsValue.As().Utf8Value())); + break; + } + } else if (jsValue.IsArray()) { + std::vector nativeArray; + auto jsArray = jsValue.As(); + for (uint32_t index = 0; index < jsArray.Length(); index++) { + nativeArray.push_back(((Napi::Value)jsArray[index]).As().Utf8Value()); + } + + switch (naked_treatment) { + case NakedValueDisposition::kTreatAsIdeal: + nativeValue.SetIdeal(nativeArray); + break; + case NakedValueDisposition::kTreatAsExact: + nativeValue.SetExact(nativeArray); + break; + } + } else if (jsValue.IsObject()) { + auto jsObject = jsValue.As(); + if (jsObject.Has(kConstraintsIdeal)) { + auto jsIdeal = jsObject.Get(kConstraintsIdeal); + if (jsIdeal.IsString()) { + nativeValue.SetIdeal(std::vector(1, jsValue.As().Utf8Value())); + } else if (jsIdeal.IsArray()) { + std::vector nativeArray; + auto jsArray = jsValue.As(); + for (uint32_t index = 0; index < jsArray.Length(); index++) { + nativeArray.push_back(((Napi::Value)jsArray[index]).As().Utf8Value()); + } + nativeValue.SetIdeal(nativeArray); + } + } + if (jsObject.Has(kConstraintsExact)) { + auto jsIdeal = jsObject.Get(kConstraintsExact); + if (jsIdeal.IsString()) { + nativeValue.SetExact(std::vector(1, jsValue.As().Utf8Value())); + } else if (jsIdeal.IsArray()) { + std::vector nativeArray; + auto jsArray = jsValue.As(); + for (uint32_t index = 0; index < jsArray.Length(); index++) { + nativeArray.push_back(((Napi::Value)jsArray[index]).As().Utf8Value()); + } + nativeValue.SetExact(nativeArray); + } + } + } + return true; +} + +void CopyBooleanConstraint( + const Napi::Value& jsValue, NakedValueDisposition naked_treatment, BooleanConstraint& nativeValue) +{ + if (jsValue.IsBoolean()) { + switch (naked_treatment) { + case NakedValueDisposition::kTreatAsIdeal: + nativeValue.SetIdeal(jsValue.As()); + break; + case NakedValueDisposition::kTreatAsExact: + nativeValue.SetExact(jsValue.As()); + break; + } + } else if (jsValue.IsObject()) { + auto jsObject = jsValue.As(); + if (jsObject.Has(kConstraintsIdeal)) { + nativeValue.SetIdeal(jsObject.Get(kConstraintsIdeal).As()); + } + if (jsObject.Has(kConstraintsExact)) { + nativeValue.SetIdeal(jsObject.Get(kConstraintsExact).As()); + } + } +} + +bool ValidateAndCopyConstraintSet( + const Object jsTrackConstraints, NakedValueDisposition naked_treatment, MediaTrackConstraintSet& trackConstraints, + std::string& error_message) +{ + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameWidth)) { + CopyLongConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameWidth), naked_treatment, trackConstraints.width); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameHeight)) { + CopyLongConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameHeight), naked_treatment, + trackConstraints.height); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameAspectRatio)) { + CopyDoubleConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameAspectRatio), naked_treatment, + trackConstraints.aspectRatio); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameFrameRate)) { + CopyDoubleConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameFrameRate), naked_treatment, + trackConstraints.frameRate); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameFacingMode)) { + if (!ValidateAndCopyStringConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameFacingMode), naked_treatment, + trackConstraints.facingMode, error_message)) + { + return false; + } + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameResizeMode)) { + if (!ValidateAndCopyStringConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameResizeMode), naked_treatment, + trackConstraints.resizeMode, error_message)) + { + return false; + } + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameSampleRate)) { + CopyLongConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameSampleRate), naked_treatment, + trackConstraints.sampleRate); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameSampleSize)) { + CopyLongConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameSampleSize), naked_treatment, + trackConstraints.sampleSize); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameEchoCancellation)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameEchoCancellation), naked_treatment, + trackConstraints.echoCancellation); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameAutoGainControl)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameAutoGainControl), naked_treatment, + trackConstraints.autoGainControl); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameNoiseSuppression)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameNoiseSuppression), naked_treatment, + trackConstraints.noiseSuppression); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameLatency)) { + CopyDoubleConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameLatency), naked_treatment, + trackConstraints.latency); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameChannelCount)) { + CopyLongConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameChannelCount), naked_treatment, + trackConstraints.channelCount); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameDeviceId)) { + if (!ValidateAndCopyStringConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameDeviceId), naked_treatment, + trackConstraints.deviceId, error_message)) + { + return false; + } + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGroupId)) { + if (!ValidateAndCopyStringConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGroupId), naked_treatment, + trackConstraints.groupId, error_message)) + { + return false; + } + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameBackgroundBlur)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameBackgroundBlur), naked_treatment, + trackConstraints.backgroundBlur); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameDisplaySurface)) { + if (!ValidateAndCopyStringConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameDisplaySurface), naked_treatment, + trackConstraints.displaySurface, error_message)) + { + return false; + } + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGoogEchoCancellation)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGoogEchoCancellation), naked_treatment, + trackConstraints.googEchoCancellation); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGoogAutoGainControl)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGoogAutoGainControl), naked_treatment, + trackConstraints.googAutoGainControl); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGoogNoiseSuppression)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGoogNoiseSuppression), naked_treatment, + trackConstraints.googNoiseSuppression); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGoogHighpassFilter)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGoogHighpassFilter), naked_treatment, + trackConstraints.googHighpassFilter); + } + + if (jsTrackConstraints.Has(NapiMediaConstraints::kAttributeNameGoogAudioMirroring)) { + CopyBooleanConstraint( + jsTrackConstraints.Get(NapiMediaConstraints::kAttributeNameGoogAudioMirroring), naked_treatment, + trackConstraints.googAudioMirroring); + } + + return true; +} + +MediaTrackConstraints ParseTrackConstraints(const Napi::Object& jsTrackConstraints, std::string& error_message) +{ + MediaTrackConstraintSet basic; + if (!ValidateAndCopyConstraintSet(jsTrackConstraints, NakedValueDisposition::kTreatAsIdeal, basic, error_message)) { + RTC_LOG(LS_ERROR) << "Failed to parse track constraints: " << error_message; + return MediaTrackConstraints(); + } + + std::vector advanced; + if (jsTrackConstraints.Has(kConstraintsAdvanced)) { + MediaTrackConstraintSet advancedElement; + auto jsArray = jsTrackConstraints.Get(kConstraintsAdvanced).As(); + for (uint32_t index = 0; index < jsArray.Length(); index++) { + auto jsElement = (Napi::Value)jsArray[index]; + if (!ValidateAndCopyConstraintSet( + jsElement.As(), NakedValueDisposition::kTreatAsExact, advancedElement, error_message)) + { + RTC_LOG(LS_ERROR) << "Failed to parse track constraints: " << error_message; + return MediaTrackConstraints(); + } + advanced.push_back(advancedElement); + } + } + + MediaTrackConstraints constraints; + constraints.Initialize(basic, advanced); + + return constraints; +} + +std::vector NapiMediaConstraints::GetSupportedConstraints() +{ + std::vector result; + for (const auto& constraints : SUPPORTED_CONSTRAINTS_MAP) { + if (constraints.second) { + result.push_back(constraints.first); + } + } + return result; +} + +bool NapiMediaConstraints::IsConstraintSupported(const std::string& name) +{ + auto it = SUPPORTED_CONSTRAINTS_MAP.find(name); + if (it != SUPPORTED_CONSTRAINTS_MAP.end()) { + return it->second; + } + return true; +} + +void NapiMediaConstraints::JsToNative( + const Napi::Value& jsTrackConstraints, MediaTrackConstraints& nativeTrackConstraints) +{ + if (jsTrackConstraints.IsBoolean()) { + if (jsTrackConstraints.As()) { + MediaTrackConstraints constraints; + constraints.Initialize(); + nativeTrackConstraints = constraints; + } else { + nativeTrackConstraints = MediaTrackConstraints(); + } + return; + } else if (jsTrackConstraints.IsObject()) { + std::string error_message; + auto constraints = ParseTrackConstraints(jsTrackConstraints.As(), error_message); + if (constraints.IsNull()) { + NAPI_THROW_VOID(TypeError::New(jsTrackConstraints.Env(), error_message)); + } + nativeTrackConstraints = constraints; + return; + } + + nativeTrackConstraints = MediaTrackConstraints(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/media_track_constraints.h b/sdk/ohos/src/ohos_webrtc/media_track_constraints.h new file mode 100644 index 0000000000000000000000000000000000000000..62f8696431ad8a230078b03a1f251da10c5a4e93 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/media_track_constraints.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_TRACK_MEDIA_CONSTRAINTS_H +#define WEBRTC_TRACK_MEDIA_CONSTRAINTS_H + +#include "user_media/media_constraints.h" +#include "utils/marcos.h" + +#include +#include + +#include "napi.h" + +#include "api/audio_options.h" + +namespace webrtc { + +class NapiMediaConstraints { +public: + NAPI_ATTRIBUTE_NAME_DECLARE(Width, width); + NAPI_ATTRIBUTE_NAME_DECLARE(Height, height); + NAPI_ATTRIBUTE_NAME_DECLARE(AspectRatio, aspectRatio); + NAPI_ATTRIBUTE_NAME_DECLARE(FrameRate, frameRate); + NAPI_ATTRIBUTE_NAME_DECLARE(FacingMode, facingMode); + NAPI_ATTRIBUTE_NAME_DECLARE(ResizeMode, resizeMode); + NAPI_ATTRIBUTE_NAME_DECLARE(SampleRate, sampleRate); + NAPI_ATTRIBUTE_NAME_DECLARE(SampleSize, sampleSize); + NAPI_ATTRIBUTE_NAME_DECLARE(EchoCancellation, echoCancellation); + NAPI_ATTRIBUTE_NAME_DECLARE(AutoGainControl, autoGainControl); + NAPI_ATTRIBUTE_NAME_DECLARE(NoiseSuppression, noiseSuppression); + NAPI_ATTRIBUTE_NAME_DECLARE(Latency, latency); + NAPI_ATTRIBUTE_NAME_DECLARE(ChannelCount, ChannelCount); + NAPI_ATTRIBUTE_NAME_DECLARE(DeviceId, deviceId); + NAPI_ATTRIBUTE_NAME_DECLARE(GroupId, groupId); + NAPI_ATTRIBUTE_NAME_DECLARE(BackgroundBlur, backgroundBlur); + NAPI_ATTRIBUTE_NAME_DECLARE(DisplaySurface, displaySurface); + NAPI_ATTRIBUTE_NAME_DECLARE(GoogEchoCancellation, googEchoCancellation); + NAPI_ATTRIBUTE_NAME_DECLARE(GoogAutoGainControl, googAutoGainControl); + NAPI_ATTRIBUTE_NAME_DECLARE(GoogNoiseSuppression, googNoiseSuppression); + NAPI_ATTRIBUTE_NAME_DECLARE(GoogHighpassFilter, googHighpassFilter); + NAPI_ATTRIBUTE_NAME_DECLARE(GoogAudioMirroring, googAudioMirroring); + + static std::vector GetSupportedConstraints(); + static bool IsConstraintSupported(const std::string& name); + static void JsToNative(const Napi::Value& jsTrackConstraints, MediaTrackConstraints& nativeTrackConstraints); +}; + +} // namespace webrtc + +#endif // WEBRTC_TRACK_MEDIA_CONSTRAINTS_H diff --git a/sdk/ohos/src/ohos_webrtc/napi_module.cpp b/sdk/ohos/src/ohos_webrtc/napi_module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ec25f593840e9867acf935335922a374b4484e72 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/napi_module.cpp @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hilog/log.h" +#include "napi/native_api.h" + +#include "napi.h" + +#include "rtc_base/ssl_adapter.h" + +#include "certificate.h" +#include "data_channel.h" +#include "ice_candidate.h" +#include "media_source.h" +#include "media_stream.h" +#include "media_stream_track.h" +#include "peer_connection.h" +#include "peer_connection_factory.h" +#include "rtp_receiver.h" +#include "rtp_sender.h" +#include "rtp_transceiver.h" +#include "sctp_transport.h" +#include "session_description.h" +#include "dtls_transport.h" +#include "dtmf_sender.h" +#include "ice_transport.h" +#include "media_devices.h" +#include "video_encoder_factory.h" +#include "video_decoder_factory.h" +#include "audio_processing_factory.h" +#include "audio_device/audio_device_module.h" +#include "render/native_video_renderer.h" +#include "logging/native_logging.h" + +using namespace Napi; +using namespace webrtc; + +struct SSLInitializer { + SSLInitializer() + { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "napi_module", "InitializeSSL"); + RTC_CHECK(rtc::InitializeSSL()) << "Failed to InitializeSSL()"; + } + + ~SSLInitializer() + { + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "napi_module", "CleanupSSL"); + RTC_CHECK(rtc::CleanupSSL()) << "Failed to CleanupSSL()"; + } +}; + +EXTERN_C_START +static napi_value Init(napi_env env, napi_value exports) +{ + OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "napi_module", "Init"); + + Env e(env); + Object exp(env, exports); + + NapiPeerConnectionFactory::Init(e, exp); + NapiPeerConnection::Init(e, exp); + NapiIceCandidate::Init(e, exp); + NapiSessionDescription::Init(e, exp); + NapiRtpSender::Init(e, exp); + NapiRtpReceiver::Init(e, exp); + NapiRtpTransceiver::Init(e, exp); + NapiSctpTransport::Init(e, exp); + NapiCertificate::Init(e, exp); + NapiAudioSource::Init(e, exp); + NapiVideoSource::Init(e, exp); + NapiDataChannel::Init(e, exp); + NapiMediaStream::Init(e, exp); + NapiMediaStreamTrack::Init(e, exp); + NapiNativeLogging::Init(e, exp); + NapiAudioDeviceModule::Init(e, exp); + NapiDtlsTransport::Init(e, exp); + NapiDtmfSender::Init(e, exp); + NapiIceTransport::Init(e, exp); + NapiNativeVideoRenderer::Init(e, exp); + NapiMediaDevices::Init(e, exp); + NapiHardwareVideoEncoderFactory::Init(e, exp); + NapiHardwareVideoDecoderFactory::Init(e, exp); + NapiSoftwareVideoEncoderFactory::Init(e, exp); + NapiSoftwareVideoDecoderFactory::Init(e, exp); + NapiAudioProcessing::Init(e, exp); + NapiAudioProcessingFactory::Init(e, exp); + + auto sslInitializer = new SSLInitializer; + exp.AddFinalizer([](Napi::Env, SSLInitializer* ssl) { delete ssl; }, sslInitializer); + + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "ohos_webrtc", + .nm_priv = ((void*)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) +{ + napi_module_register(&demoModule); +} diff --git a/sdk/ohos/src/ohos_webrtc/peer_connection.cpp b/sdk/ohos/src/ohos_webrtc/peer_connection.cpp new file mode 100644 index 0000000000000000000000000000000000000000..719ee20dd713a44e39ad784c1b8e6602e87d8105 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/peer_connection.cpp @@ -0,0 +1,1708 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "peer_connection.h" +#include "peer_connection_factory.h" + +#include +#include + +#include + +#include "rtc_base/logging.h" + +#include "configuration.h" +#include "data_channel.h" +#include "ice_candidate.h" +#include "media_stream.h" +#include "media_stream_track.h" +#include "rtp_receiver.h" +#include "rtp_sender.h" +#include "rtp_transceiver.h" +#include "sctp_transport.h" +#include "session_description.h" +#include "utils/marcos.h" +#include "async_work/async_worker_certificate.h" +#include "async_work/async_worker_get_stats.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumSignalingStateStable[] = "stable"; +const char kEnumSignalingStateHaveLocalOffer[] = "have-local-offer"; +const char kEnumSignalingStateHaveLocalPranswer[] = "have-local-pranswer"; +const char kEnumSignalingStateHaveRemoteOffer[] = "have-remote-offer"; +const char kEnumSignalingStateHaveRemotePranswer[] = "have-remote-pranswer"; +const char kEnumSignalingStateClosed[] = "closed"; + +const char kEnumIceGatheringStateNew[] = "new"; +const char kEnumIceGatheringStateGathering[] = "gathering"; +const char kEnumIceGatheringStateComplete[] = "complete"; + +const char kEnumIceConnectionStateNew[] = "new"; +const char kEnumIceConnectionStateChecking[] = "checking"; +const char kEnumIceConnectionStateCompleted[] = "completed"; +const char kEnumIceConnectionStateConnected[] = "connected"; +const char kEnumIceConnectionStateDisconnected[] = "disconnected"; +const char kEnumIceConnectionStateFailed[] = "failed"; +const char kEnumIceConnectionStateClosed[] = "closed"; + +const char kEnumPeerConnectionStateNew[] = "new"; +const char kEnumPeerConnectionStateConnecting[] = "connecting"; +const char kEnumPeerConnectionStateConnected[] = "connected"; +const char kEnumPeerConnectionStateDisconnected[] = "disconnected"; +const char kEnumPeerConnectionStateFailed[] = "failed"; +const char kEnumPeerConnectionStateClosed[] = "closed"; + +const char kClassName[] = "RTCPeerConnection"; + +const char kAttributeNameCanTrickleIceCandidates[] = "canTrickleIceCandidates"; +const char kAttributeNameSignalingState[] = "signalingState"; +const char kAttributeNameIceGatheringState[] = "iceGatheringState"; +const char kAttributeNameIceConnectionState[] = "iceConnectionState"; +const char kAttributeNameConnectionState[] = "connectionState"; +const char kAttributeNameLocalDescription[] = "localDescription"; +const char kAttributeNameRemoteDescription[] = "remoteDescription"; +const char kAttributeNameCurrentLocalDescription[] = "currentLocalDescription"; +const char kAttributeNameCurrentRemoteDescription[] = "currentRemoteDescription"; +const char kAttributeNamePendingLocalDescription[] = "pendingLocalDescription"; +const char kAttributeNamePendingRemoteDescription[] = "pendingRemoteDescription"; +const char kAttributeNameSctp[] = "sctp"; +const char kAttributeNameOnConnectionStateChange[] = "onconnectionstatechange"; +const char kAttributeNameOnIceCandidate[] = "onicecandidate"; +const char kAttributeNameOnIceCandidateError[] = "onicecandidateerror"; +const char kAttributeNameOnIceConnectionStateChange[] = "oniceconnectionstatechange"; +const char kAttributeNameOnIceGatheringStateChange[] = "onicegatheringstatechange"; +const char kAttributeNameOnNegotiationNeeded[] = "onnegotiationneeded"; +const char kAttributeNameOnSignalingStateChange[] = "onsignalingstatechange"; +const char kAttributeNameOnTrack[] = "ontrack"; +const char kAttributeNameOnDataChannel[] = "ondatachannel"; + +const char kMethodNameAddTrack[] = "addTrack"; +const char kMethodNameRemoveTrack[] = "removeTrack"; +const char kMethodNameSetLocalDescription[] = "setLocalDescription"; +const char kMethodNameSetRemoteDescription[] = "setRemoteDescription"; +const char kMethodNameCreateOffer[] = "createOffer"; +const char kMethodNameCreateAnswer[] = "createAnswer"; +const char kMethodNameCreateDataChannel[] = "createDataChannel"; +const char kMethodNameAddIceCandidate[] = "addIceCandidate"; +const char kMethodNameGetSenders[] = "getSenders"; +const char kMethodNameGetReceivers[] = "getReceivers"; +const char kMethodNameGetTransceivers[] = "getTransceivers"; +const char kMethodNameGetConfiguration[] = "getConfiguration"; +const char kMethodNameRestartIce[] = "restartIce"; +const char kMethodNameSetConfiguration[] = "setConfiguration"; +const char kMethodNameAddTransceiver[] = "addTransceiver"; +const char kMethodNameClose[] = "close"; +const char kMethodNameGetStats[] = "getStats"; +const char kMethodNameToJson[] = "toJSON"; + +const char kStaticMethodNameGenerateCertificate[] = "generateCertificate"; + +const char kEventConnectionStateChange[] = "connectionstatechange"; +const char kEventIceCandidate[] = "icecandidate"; +const char kEventIceCandidateError[] = "icecandidateerror"; +const char kEventIceConnectionStateChange[] = "iceconnectionstatechange"; +const char kEventIceGatheringStateChange[] = "icegatheringstatechange"; +const char kEventNegotiationNeeded[] = "negotiationneeded"; +const char kEventSignalingStateChange[] = "signalingstatechange"; +const char kEventTrack[] = "track"; +const char kEventDataChannel[] = "datachannel"; + +template +class BaseSdpObserver : public T { +public: + virtual ~BaseSdpObserver() + { + tsfn_.Release(); + } + + Promise GetPromise() + { + return deferred_.Promise(); + } + +protected: + explicit BaseSdpObserver(Promise::Deferred deferred) : deferred_(deferred) {} + + ThreadSafeFunction tsfn_; + Promise::Deferred deferred_; +}; + +class CreateSdpObserver : public BaseSdpObserver { +public: + explicit CreateSdpObserver(Napi::Env env) : CreateSdpObserver(env, Promise::Deferred::New(env)) {} + + CreateSdpObserver(Napi::Env env, Promise::Deferred deferred) + : BaseSdpObserver(deferred) + { + tsfn_ = ThreadSafeFunction::New( + env, + Function::New( + env, + [deferred](const CallbackInfo& info) { + auto success = info[0].As().Value(); + if (success) { + auto desc = info[1].As>().Data(); + + std::string sdp; + desc->ToString(&sdp); + RTC_DLOG(LS_VERBOSE) << "sdp: " << sdp; + + auto result = Napi::Object::New(info.Env()); + result.Set("sdp", Napi::String::New(info.Env(), sdp)); + result.Set("type", Napi::String::New(info.Env(), webrtc::SdpTypeToString(desc->GetType()))); + deferred.Resolve(result); + } else { + auto error = info[1].As>().Data(); + auto message = error->message(); + deferred.Reject( + Error::New(info.Env(), (message && strlen(message) > 0) ? message : "unknown error") + .Value()); + } + }), + "CreateSdpObserver", 0, 1); + } + +protected: + void OnSuccess(SessionDescriptionInterface* desc) override + { + RTC_LOG(LS_INFO) << "CreateSessionDescription success: " << desc; + + tsfn_.BlockingCall([desc](Napi::Env env, Napi::Function jsCallback) { + auto externalDesc = External::New( + env, desc, [](Napi::Env, SessionDescriptionInterface* desc) { delete desc; }); + jsCallback.Call({Boolean::New(env, true), externalDesc}); + }); + } + + void OnFailure(RTCError error) override + { + RTC_LOG(LS_ERROR) << "CreateSessionDescription failed"; + + tsfn_.BlockingCall([error = new RTCError(std::move(error))](Napi::Env env, Napi::Function jsCallback) { + auto externalError = + External::New(env, new RTCError(RTCError::OK()), [](Napi::Env, RTCError* e) { delete e; }); + jsCallback.Call({Boolean::New(env, false), externalError}); + }); + } +}; + +class SetLocalSdpObserver : public BaseSdpObserver { +public: + explicit SetLocalSdpObserver(Napi::Env env) : SetLocalSdpObserver(env, Promise::Deferred::New(env)) {} + + SetLocalSdpObserver(Napi::Env env, Promise::Deferred deferred) + : BaseSdpObserver(deferred) + { + tsfn_ = ThreadSafeFunction::New( + env, + Function::New( + env, + [deferred](const CallbackInfo& info) { + auto error = info[0].As>().Data(); + if (error->ok()) { + deferred.Resolve(info.Env().Undefined()); + } else { + auto message = error->message(); + deferred.Reject( + Error::New(info.Env(), (message && strlen(message) > 0) ? message : "unknown error") + .Value()); + } + }), + "SetLocalSdpObserver", 0, 1); + } + +protected: + void OnSetLocalDescriptionComplete(RTCError error) override + { + RTC_DLOG(LS_INFO) << __FUNCTION__; + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Error: " << error.type() << ", " << error.message(); + } + + tsfn_.BlockingCall([error = new RTCError(std::move(error))](Napi::Env env, Napi::Function jsCallback) { + jsCallback.Call({External::New(env, error, [](Napi::Env, RTCError* e) { delete e; })}); + }); + } +}; + +class SetRemoteSdpObserver : public BaseSdpObserver { +public: + explicit SetRemoteSdpObserver(Napi::Env env) : SetRemoteSdpObserver(env, Promise::Deferred::New(env)) {} + + SetRemoteSdpObserver(Napi::Env env, Promise::Deferred deferred) + : BaseSdpObserver(deferred) + { + tsfn_ = ThreadSafeFunction::New( + env, + Function::New( + env, + [deferred](const CallbackInfo& info) { + auto error = info[0].As>().Data(); + if (error->ok()) { + deferred.Resolve(info.Env().Undefined()); + } else { + auto message = error->message(); + deferred.Reject( + Error::New(info.Env(), (message && strlen(message) > 0) ? message : "unknown error") + .Value()); + } + }), + "SetRemoteSdpObserver", 0, 1); + } + +protected: + void OnSetRemoteDescriptionComplete(RTCError error) override + { + RTC_DLOG(LS_INFO) << __FUNCTION__; + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Error: " << error.type() << ", " << error.message(); + } + + tsfn_.BlockingCall([error = new RTCError(std::move(error))](Napi::Env env, Napi::Function jsCallback) { + jsCallback.Call({External::New(env, error, [](Napi::Env, RTCError* e) { delete e; })}); + }); + } +}; + +/// NapiPeerConnectionWrapper +std::unique_ptr NapiPeerConnectionWrapper::Create( + std::shared_ptr factoryWrapper, + const PeerConnectionInterface::RTCConfiguration& config) +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + if (!factoryWrapper) { + RTC_DLOG(LS_INFO) << "Get default factory"; + factoryWrapper = PeerConnectionFactoryWrapper::GetDefault(); + } + + if (!factoryWrapper || !factoryWrapper->GetFactory()) { + RTC_LOG(LS_ERROR) << "No factory"; + return nullptr; + } + + auto factory = factoryWrapper->GetFactory(); + auto observer = std::unique_ptr(new NapiPeerConnectionWrapper); + + PeerConnectionDependencies dependencies(observer.get()); + auto result = factory->CreatePeerConnectionOrError(config, std::move(dependencies)); + if (!result.ok()) { + RTC_LOG(LS_ERROR) << "CreatePeerConnection failed: " << result.error().message(); + return nullptr; + } + + observer->SetPeerConnectionFactoryWrapper(std::move(factoryWrapper)); + observer->SetPeerConnection(result.value()); + + return observer; +} + +NapiPeerConnectionWrapper::NapiPeerConnectionWrapper() : eventThread_(rtc::Thread::Create()) +{ + eventThread_->SetName("pc_event_thread", nullptr); + if (!eventThread_->Start()) { + RTC_LOG(LS_ERROR) << "Failed to start event thread"; + } +} + +NapiPeerConnectionWrapper::~NapiPeerConnectionWrapper() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RemoveAllEventHandlers(); + + eventThread_->Stop(); +} + +bool NapiPeerConnectionWrapper::GetEventHandler(const std::string& type, Napi::Function& fn) +{ + RTC_DLOG(LS_VERBOSE) << "NapiPeerConnectionWrapper::GetEventHandler type: " << type; + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + RTC_LOG(LS_VERBOSE) << "No event handler for type: " << type; + return false; + } + + fn = it->second.ref.Value(); + + return true; +} + +bool NapiPeerConnectionWrapper::SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver) +{ + RTC_DLOG(LS_VERBOSE) << "SetEventHandler type: " << type; + + Reference* context = new Reference; + *context = Persistent(receiver); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = + ThreadSafeFunction::New(fn.Env(), fn, type, 0, 1, context, [](Napi::Env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + + UNUSED std::lock_guard lock(eventMutex_); + eventHandlers_[type] = std::move(handler); + + return true; +} + +bool NapiPeerConnectionWrapper::RemoveEventHandler(const std::string& type) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it != eventHandlers_.end()) { + it->second.tsfn.Release(); + eventHandlers_.erase(it); + } + + return true; +} + +void NapiPeerConnectionWrapper::RemoveAllEventHandlers() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED std::lock_guard lock(eventMutex_); + for (auto& handler : eventHandlers_) { + handler.second.tsfn.Release(); + } + eventHandlers_.clear(); +} + +bool NapiPeerConnectionWrapper::GetEventHandler(const std::string& type, Napi::ThreadSafeFunction& tsfn) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " type=" << type; + RTC_DCHECK(eventThread_->IsCurrent()); + + UNUSED std::lock_guard lock(eventMutex_); + auto it = eventHandlers_.find(type); + if (it == eventHandlers_.end()) { + RTC_LOG(LS_VERBOSE) << "No event handler for type: " << type; + return false; + } + + tsfn = it->second.tsfn; + + return true; +} + +void NapiPeerConnectionWrapper::OnIceCandidate(const IceCandidateInterface* candidate) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!candidate) { + RTC_LOG(LS_ERROR) << "The candidate is nullptr"; + return; + } + + std::string sdp; + if (!candidate->ToString(&sdp)) { + RTC_LOG(LS_ERROR) << "Failed to convert candidate to string, got so far: " << sdp; + return; + } + + auto sdpMid = candidate->sdp_mid(); + auto sdpMLineIndex = candidate->sdp_mline_index(); + auto can = candidate->candidate(); + + // We do not have ownership of the candidate, so deep copy here + eventThread_->PostTask([this, sdp, sdpMid, sdpMLineIndex, can] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventIceCandidate, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = + tsfn.BlockingCall([context, sdp, sdpMid, sdpMLineIndex, can](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("candidate", NativeToJsCandidate(env, sdpMid, sdpMLineIndex, sdp, can)); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnIceCandidateError( + const std::string& address, int port, const std::string& url, int error_code, const std::string& error_text) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + eventThread_->PostTask([this, address, port, url, error_code, error_text] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventIceCandidateError, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall( + [context, address, port, url, error_code, error_text](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("address", String::New(env, address)); + jsEvent.Set("port", Number::New(env, port)); + jsEvent.Set("url", String::New(env, url)); + jsEvent.Set("errorCode", Number::New(env, error_code)); + jsEvent.Set("errorText", String::New(env, error_text)); + + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnIceCandidatesRemoved(const std::vector& /*candidates*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NapiPeerConnectionWrapper::OnSignalingChange(PeerConnectionInterface::SignalingState new_state) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " new_state=" << new_state; + + eventThread_->PostTask([this] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventSignalingStateChange, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " new_state=" << new_state; + // use OnStandardizedIceConnectionChange +} + +void NapiPeerConnectionWrapper::OnStandardizedIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " new_state=" << new_state; + + eventThread_->PostTask([this] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventIceConnectionStateChange, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnConnectionChange(PeerConnectionInterface::PeerConnectionState new_state) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " new_state=" << new_state; + + eventThread_->PostTask([this] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventConnectionStateChange, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnIceConnectionReceivingChange(bool /*receiving*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NapiPeerConnectionWrapper::OnIceGatheringChange(PeerConnectionInterface::IceGatheringState new_state) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " new_state=" << new_state; + + eventThread_->PostTask([this] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventIceGatheringStateChange, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnIceSelectedCandidatePairChanged(const cricket::CandidatePairChangeEvent& /*event*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NapiPeerConnectionWrapper::OnAddStream(rtc::scoped_refptr /*stream*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NapiPeerConnectionWrapper::OnRemoveStream(rtc::scoped_refptr /*stream*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NapiPeerConnectionWrapper::OnDataChannel(rtc::scoped_refptr channel) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!channel) { + RTC_LOG(LS_ERROR) << "The channel is nullptr"; + return; + } + + eventThread_->PostTask([this, channel] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventDataChannel, tsfn)) { + return; + } + + auto observer = NapiDataChannel::Observer::Create(channel.get()); + channel->RegisterObserver(observer.get()); + + Reference* context = tsfn.GetContext(); + napi_status status = + tsfn.BlockingCall([context, channel, obs = observer.release()](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsEvent.Set("channel", NapiDataChannel::NewInstance(env, channel, obs)); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnRenegotiationNeeded() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + eventThread_->PostTask([this] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventNegotiationNeeded, tsfn)) { + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnNegotiationNeededEvent(uint32_t event_id) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " event_id=" << event_id; +} + +void NapiPeerConnectionWrapper::OnAddTrack( + rtc::scoped_refptr /*receiver*/, + const std::vector>& /*streams*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + // use OnTrack +} + +void NapiPeerConnectionWrapper::OnTrack(rtc::scoped_refptr transceiver) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + eventThread_->PostTask([this, transceiver] { + ThreadSafeFunction tsfn; + if (!GetEventHandler(kEventTrack, tsfn)) { + return; + } + + auto receiver = transceiver->receiver(); + if (!receiver) { + RTC_LOG(LS_ERROR) << "No receiver in the transceiver"; + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.BlockingCall( + [this, context, transceiver, pc = GetPeerConnection()](Napi::Env env, Napi::Function jsCallback) { + auto receiver = transceiver->receiver(); + auto track = MediaStreamTrackWrapper::Create(receiver->track()); + + auto streams = receiver->streams(); + auto jsStreams = Array::New(env, streams.size()); + for (uint32_t i = 0; i < streams.size(); i++) { + jsStreams[i] = NapiMediaStream::NewInstance(env, streams[i]); + } + + auto jsEvent = Object::New(env); + jsEvent.Set("streams", jsStreams); + jsEvent.Set("track", NapiMediaStreamTrack::NewInstance(env, std::move(track))); + jsEvent.Set("receiver", NapiRtpReceiver::NewInstance(env, receiver, this)); + jsEvent.Set("transceiver", NapiRtpTransceiver::NewInstance(env, transceiver, this)); + + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << "tsfn call error: " << status; + } + }); +} + +void NapiPeerConnectionWrapper::OnRemoveTrack(rtc::scoped_refptr /*receiver*/) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +/// NapiPeerConnection +FunctionReference NapiPeerConnection::constructor_; + +void NapiPeerConnection::Init(class Env env, Object exports) +{ + RTC_LOG(LS_INFO) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiPeerConnection::GetCanTrickleIceCandidates>(kAttributeNameCanTrickleIceCandidates), + InstanceAccessor<&NapiPeerConnection::GetSignalingState>(kAttributeNameSignalingState), + InstanceAccessor<&NapiPeerConnection::GetIceGatheringState>(kAttributeNameIceGatheringState), + InstanceAccessor<&NapiPeerConnection::GetIceConnectionState>(kAttributeNameIceConnectionState), + InstanceAccessor<&NapiPeerConnection::GetConnectionState>(kAttributeNameConnectionState), + InstanceAccessor<&NapiPeerConnection::GetLocalDescription>(kAttributeNameLocalDescription), + InstanceAccessor<&NapiPeerConnection::GetRemoteDescription>(kAttributeNameRemoteDescription), + InstanceAccessor<&NapiPeerConnection::GetCurrentLocalDescription>(kAttributeNameCurrentLocalDescription), + InstanceAccessor<&NapiPeerConnection::GetCurrentRemoteDescription>(kAttributeNameCurrentRemoteDescription), + InstanceAccessor<&NapiPeerConnection::GetPendingLocalDescription>(kAttributeNamePendingLocalDescription), + InstanceAccessor<&NapiPeerConnection::GetPendingRemoteDescription>(kAttributeNamePendingRemoteDescription), + InstanceAccessor<&NapiPeerConnection::GetSctp>(kAttributeNameSctp), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnIceCandidate, napi_default, (void*)kEventIceCandidate), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnIceCandidateError, napi_default, (void*)kEventIceCandidateError), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnIceConnectionStateChange, napi_default, (void*)kEventIceConnectionStateChange), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnIceGatheringStateChange, napi_default, (void*)kEventIceGatheringStateChange), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnConnectionStateChange, napi_default, (void*)kEventConnectionStateChange), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnSignalingStateChange, napi_default, (void*)kEventSignalingStateChange), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnNegotiationNeeded, napi_default, (void*)kEventNegotiationNeeded), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnTrack, napi_default, (void*)kEventTrack), + InstanceAccessor<&NapiPeerConnection::GetEventHandler, &NapiPeerConnection::SetEventHandler>( + kAttributeNameOnDataChannel, napi_default, (void*)kEventDataChannel), + InstanceMethod<&NapiPeerConnection::AddTrack>(kMethodNameAddTrack), + InstanceMethod<&NapiPeerConnection::RemoveTrack>(kMethodNameRemoveTrack), + InstanceMethod<&NapiPeerConnection::SetLocalDescription>(kMethodNameSetLocalDescription), + InstanceMethod<&NapiPeerConnection::SetRemoteDescription>(kMethodNameSetRemoteDescription), + InstanceMethod<&NapiPeerConnection::CreateOffer>(kMethodNameCreateOffer), + InstanceMethod<&NapiPeerConnection::CreateAnswer>(kMethodNameCreateAnswer), + InstanceMethod<&NapiPeerConnection::CreateDataChannel>(kMethodNameCreateDataChannel), + InstanceMethod<&NapiPeerConnection::AddIceCandidate>(kMethodNameAddIceCandidate), + InstanceMethod<&NapiPeerConnection::GetSenders>(kMethodNameGetSenders), + InstanceMethod<&NapiPeerConnection::GetReceivers>(kMethodNameGetReceivers), + InstanceMethod<&NapiPeerConnection::GetTransceivers>(kMethodNameGetTransceivers), + InstanceMethod<&NapiPeerConnection::GetConfiguration>(kMethodNameGetConfiguration), + InstanceMethod<&NapiPeerConnection::RestartIce>(kMethodNameRestartIce), + InstanceMethod<&NapiPeerConnection::SetConfiguration>(kMethodNameSetConfiguration), + InstanceMethod<&NapiPeerConnection::AddTransceiver>(kMethodNameAddTransceiver), + InstanceMethod<&NapiPeerConnection::Close>(kMethodNameClose), + InstanceMethod<&NapiPeerConnection::GetStats>(kMethodNameGetStats), + InstanceMethod<&NapiPeerConnection::ToJson>(kMethodNameToJson), + StaticMethod<&NapiPeerConnection::GenerateCertificate>(kStaticMethodNameGenerateCertificate), + }); + + if (func.IsEmpty() || func.IsUndefined()) { + OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "NapiPeerConnection", "func is empty or undefined"); + } + constructor_ = Persistent(func); + OH_LOG_Print( + LOG_APP, LOG_INFO, LOG_DOMAIN, "NapiPeerConnection", "constructor_=%{public}p", (napi_ref)constructor_); + + exports.Set(kClassName, func); +} + +Napi::Value NapiPeerConnection::NewInstance(Napi::Env env, std::unique_ptr observer) +{ + RTC_LOG(LS_INFO) << __FUNCTION__; + + if (!observer || !observer->GetPeerConnection()) { + RTC_LOG(LS_ERROR) << "Invalid argument"; + return env.Undefined(); + } + + auto externalObserver = External::New(env, observer.release()); + + return constructor_.New({externalObserver}); +} + +NapiPeerConnection::NapiPeerConnection(const CallbackInfo& info) : ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() > 0 && info[0].IsExternal()) { + auto observer = info[0].As>().Data(); + wrapper_ = std::unique_ptr(observer); + pc_ = wrapper_->GetPeerConnection(); + } +} + +NapiPeerConnection::~NapiPeerConnection() +{ + RTC_LOG(LS_INFO) << __FUNCTION__; +} + +Napi::Value NapiPeerConnection::GetCanTrickleIceCandidates(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (pc_->can_trickle_ice_candidates()) { + return Boolean::New(info.Env(), pc_->can_trickle_ice_candidates().value()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnection::GetSignalingState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (pc_->signaling_state()) { + case PeerConnectionInterface::kStable: + return String::New(info.Env(), kEnumSignalingStateStable); + case PeerConnectionInterface::kHaveLocalOffer: + return String::New(info.Env(), kEnumSignalingStateHaveLocalOffer); + case PeerConnectionInterface::kHaveLocalPrAnswer: + return String::New(info.Env(), kEnumSignalingStateHaveLocalPranswer); + case PeerConnectionInterface::kHaveRemoteOffer: + return String::New(info.Env(), kEnumSignalingStateHaveRemoteOffer); + case PeerConnectionInterface::kHaveRemotePrAnswer: + return String::New(info.Env(), kEnumSignalingStateHaveRemotePranswer); + case PeerConnectionInterface::kClosed: + return String::New(info.Env(), kEnumSignalingStateClosed); + default: + RTC_LOG(LS_WARNING) << "Invalid value of signalingState"; + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); +} + +Napi::Value NapiPeerConnection::GetIceGatheringState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (pc_->ice_gathering_state()) { + case PeerConnectionInterface::kIceGatheringNew: + return String::New(info.Env(), kEnumIceGatheringStateNew); + case PeerConnectionInterface::kIceGatheringGathering: + return String::New(info.Env(), kEnumIceGatheringStateGathering); + case PeerConnectionInterface::kIceGatheringComplete: + return String::New(info.Env(), kEnumIceGatheringStateComplete); + default: + RTC_LOG(LS_WARNING) << "Invalid value of iceGatheringState"; + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); +} + +Napi::Value NapiPeerConnection::GetIceConnectionState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (pc_->ice_connection_state()) { + case PeerConnectionInterface::kIceConnectionNew: + return String::New(info.Env(), kEnumIceConnectionStateNew); + case PeerConnectionInterface::kIceConnectionChecking: + return String::New(info.Env(), kEnumIceConnectionStateChecking); + case PeerConnectionInterface::kIceConnectionConnected: + return String::New(info.Env(), kEnumIceConnectionStateConnected); + case PeerConnectionInterface::kIceConnectionCompleted: + return String::New(info.Env(), kEnumIceConnectionStateCompleted); + case PeerConnectionInterface::kIceConnectionFailed: + return String::New(info.Env(), kEnumIceConnectionStateFailed); + case PeerConnectionInterface::kIceConnectionDisconnected: + return String::New(info.Env(), kEnumIceConnectionStateDisconnected); + case PeerConnectionInterface::kIceConnectionClosed: + return String::New(info.Env(), kEnumIceConnectionStateClosed); + default: + RTC_LOG(LS_WARNING) << "Invalid value of iceConnectionState"; + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); +} + +Napi::Value NapiPeerConnection::GetConnectionState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (pc_->peer_connection_state()) { + case PeerConnectionInterface::PeerConnectionState::kNew: + return String::New(info.Env(), kEnumPeerConnectionStateNew); + case PeerConnectionInterface::PeerConnectionState::kConnecting: + return String::New(info.Env(), kEnumPeerConnectionStateConnecting); + case PeerConnectionInterface::PeerConnectionState::kConnected: + return String::New(info.Env(), kEnumPeerConnectionStateConnected); + case PeerConnectionInterface::PeerConnectionState::kDisconnected: + return String::New(info.Env(), kEnumPeerConnectionStateDisconnected); + case PeerConnectionInterface::PeerConnectionState::kFailed: + return String::New(info.Env(), kEnumPeerConnectionStateFailed); + case PeerConnectionInterface::PeerConnectionState::kClosed: + return String::New(info.Env(), kEnumPeerConnectionStateClosed); + default: + RTC_LOG(LS_WARNING) << "Invalid value of connectionState"; + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); +} + +Napi::Value NapiPeerConnection::GetLocalDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->local_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetRemoteDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->remote_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetCurrentLocalDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->current_local_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetCurrentRemoteDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->current_remote_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetPendingLocalDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->pending_local_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetPendingRemoteDescription(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // It's only safe to operate on SessionDescriptionInterface on the signaling thread + std::string sdp; + std::string type; + pc_->signaling_thread()->BlockingCall([pc = pc_, &sdp, &type] { + const SessionDescriptionInterface* desc = pc->pending_remote_description(); + if (desc) { + if (desc->ToString(&sdp)) { + type = desc->type(); + } + } + }); + + if (sdp.empty()) { + return info.Env().Undefined(); + } + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid value"), info.Env().Undefined()); + } + + return NapiSessionDescription::NewInstance(info.Env(), sdp, sdpType.value()); +} + +Napi::Value NapiPeerConnection::GetSctp(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!sctpTransportRef_.IsEmpty()) { + return sctpTransportRef_.Value(); + } + + auto transport = pc_->GetSctpTransport(); + if (!transport) { + return info.Env().Undefined(); + } + + auto sctpTransport = NapiSctpTransport::NewInstance(info.Env(), transport, wrapper_.get()); + sctpTransportRef_ = Persistent(sctpTransport); + + return sctpTransport; +} + +Napi::Value NapiPeerConnection::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function fn; + if (!wrapper_->GetEventHandler((const char*)info.Data(), fn)) { + return info.Env().Null(); + } + + return fn; +} + +void NapiPeerConnection::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + wrapper_->RemoveEventHandler((const char*)info.Data()); + + if (value.IsFunction()) { + Function cb = value.As(); + if (!wrapper_->SetEventHandler((const char*)info.Data(), std::move(cb), info.This())) { + NAPI_THROW_VOID(Napi::Error::New(info.Env(), "Failed to set event handler")); + } + } +} + +Napi::Value NapiPeerConnection::AddTrack(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "First argument is not object"), info.Env().Undefined()); + } + + auto jsTrack = info[0].As(); + if (jsTrack.Has("type")) { + auto type = jsTrack.Get("type").As().Utf8Value(); + RTC_DLOG(LS_VERBOSE) << "type=" << type; + } + + std::vector streamIds; + if (info.Length() >= 2) { + for (uint32_t i = 1; i < info.Length(); i++) { + if (!info[i].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "The argument is not object"), info.Env().Undefined()); + } + + auto jsStream = info[i].As(); + auto napiStream = NapiMediaStream::Unwrap(jsStream); + if (!napiStream) { + NAPI_THROW(Error::New(info.Env(), "The argument is not MediaStream"), info.Env().Undefined()); + } + + auto stream = napiStream->Get(); + if (stream) { + streamIds.push_back(stream->id()); + } + } + } else { + streamIds.push_back("stream_id"); + } + + auto track = NapiMediaStreamTrack::Unwrap(jsTrack); + auto result_or_error = pc_->AddTrack(track->Get(), streamIds); + if (!result_or_error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to add audio track to PeerConnection: " << result_or_error.error().message(); + NAPI_THROW(Error::New(info.Env(), result_or_error.error().message()), info.Env().Undefined()); + } + return NapiRtpSender::NewInstance(info.Env(), result_or_error.value(), wrapper_.get()); +} + +Napi::Value NapiPeerConnection::RemoveTrack(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto jsSender = info[0].As(); + auto sender = NapiRtpSender::Unwrap(jsSender); + + auto error = pc_->RemoveTrackOrError(sender->Get()); + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to remove track: " << error.message(); + std::string message; + switch (error.type()) { + case RTCErrorType::INVALID_PARAMETER: + message = "Invalid argument"; + break; + case RTCErrorType::INVALID_STATE: + message = "Invalid state"; + break; + default: + message = "unknown error"; + break; + } + NAPI_THROW(Error::New(info.Env(), message), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnection::SetLocalDescription(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + std::unique_ptr desc; + + if (info.Length() > 0) { + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "first argument must be a object"), info.Env().Undefined()); + } + + Object jsSdp = info[0].As(); + std::string sdp = ""; + if (jsSdp.Has("sdp")) { + sdp = jsSdp.Get("sdp").As(); + } + std::string type = jsSdp.Get("type").As(); + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "invalid argument"), info.Env().Undefined()); + } + + SdpParseError error; + desc = CreateSessionDescription(*sdpType, sdp, &error); + if (!desc) { + RTC_DLOG(LS_WARNING) << "Can't parse received session description message. SdpParseError was: " + << error.description; + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + } + + auto observer = rtc::make_ref_counted(info.Env()); + if (desc) { + pc_->SetLocalDescription(std::move(desc), observer); + } else { + pc_->SetLocalDescription(observer); + } + + return observer->GetPromise(); +} + +Napi::Value NapiPeerConnection::SetRemoteDescription(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of argument"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not object"), info.Env().Undefined()); + } + + Object jsSdp = info[0].As(); + std::string sdp = ""; + if (jsSdp.Has("sdp")) { + sdp = jsSdp.Get("sdp").As(); + } + std::string type = jsSdp.Get("type").As(); + + auto sdpType = SdpTypeFromString(type); + if (!sdpType) { + NAPI_THROW(Error::New(info.Env(), "Invalid sdp type"), info.Env().Undefined()); + } + + SdpParseError error; + auto desc = CreateSessionDescription(*sdpType, sdp, &error); + if (!desc) { + RTC_DLOG(LS_WARNING) << "Can't parse received session description message. SdpParseError was: " + << error.description; + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + auto observer = rtc::make_ref_counted(info.Env()); + pc_->SetRemoteDescription(std::move(desc), observer); + + return observer->GetPromise(); +} + +Napi::Value NapiPeerConnection::CreateOffer(const CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + PeerConnectionInterface::RTCOfferAnswerOptions options; + if (info.Length() > 0 && info[0].IsObject()) { + auto jsOptions = info[0].As(); + if (jsOptions.Has("iceRestart")) { + options.ice_restart = jsOptions.Get("iceRestart").As(); + } + } + + auto observer = rtc::make_ref_counted(info.Env()); + pc_->CreateOffer(observer.get(), options); + + return observer->GetPromise(); +} + +Napi::Value NapiPeerConnection::CreateAnswer(const CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + // ignore the argument, RTCAnswerOptions is empty + PeerConnectionInterface::RTCOfferAnswerOptions options; + + auto observer = rtc::make_ref_counted(info.Env()); + pc_->CreateAnswer(observer.get(), options); + + return observer->GetPromise(); +} + +Napi::Value NapiPeerConnection::CreateDataChannel(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not string"), info.Env().Undefined()); + } + + auto label = info[0].As().Utf8Value(); + + if (info.Length() < 2) { + auto result = pc_->CreateDataChannelOrError(label, nullptr); + if (!result.ok()) { + auto& error = result.error(); + RTC_LOG(LS_ERROR) << "create data channel error: " << error.type() << ", " << error.message(); + NAPI_THROW( + Error::New(info.Env(), error.message() ? error.message() : "unknown error"), info.Env().Undefined()); + } + + return NapiDataChannel::NewInstance(info.Env(), result.value()); + } + + if (!info[1].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "Second argument is not object"), info.Env().Undefined()); + } + + DataChannelInit options; + JsToNativeDataChannelInit(info[1].As(), options); + + auto result = pc_->CreateDataChannelOrError(label, &options); + if (!result.ok()) { + auto& error = result.error(); + RTC_LOG(LS_ERROR) << "create data channel error: " << error.type() << ", " << error.message(); + NAPI_THROW(Error::New(info.Env(), error.message() ? error.message() : "unknown error"), info.Env().Undefined()); + } + + return NapiDataChannel::NewInstance(info.Env(), result.value()); +} + +Napi::Value NapiPeerConnection::AddIceCandidate(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + std::string sdp; + std::string sdpMid; + int sdpMLineIndex = 0; + + if (info.Length() > 0) { + auto jsCandidate = info[0].As(); + sdp = jsCandidate.Get("candidate").As().Utf8Value(); + + if (jsCandidate.Has("sdpMid")) { + sdpMid = jsCandidate.Get("sdpMid").As().Utf8Value(); + } + + if (jsCandidate.Has("sdpMLineIndex")) { + sdpMLineIndex = jsCandidate.Get("sdpMLineIndex").As().Uint32Value(); + } + + // ignore usernameFragment + } + + auto deferred = Promise::Deferred::New(info.Env()); + + SdpParseError error; + auto candidate = CreateIceCandidate(sdpMid, sdpMLineIndex, sdp, &error); + if (!candidate) { + RTC_LOG(LS_ERROR) << "Can't parse received candidate message. SdpParseError was: " << error.line << ", " + << error.description; + deferred.Reject(Error::New(info.Env(), "Invalid argument").Value()); + return deferred.Promise(); + } + + auto tsfn = ThreadSafeFunction::New( + info.Env(), + Function::New( + info.Env(), + [deferred](const CallbackInfo& info) { + auto error = info[0].As>().Data(); + if (error->ok()) { + deferred.Resolve(info.Env().Undefined()); + } else { + auto type = error->type(); + auto message = error->message(); + RTC_LOG(LS_ERROR) << "AddIceCandidate failed: " << type << ", " << message; + deferred.Reject( + Error::New(info.Env(), (message && strlen(message) > 0) ? message : "unknown error").Value()); + } + }), + "AddIceCandidate", 0, 1); + + pc_->AddIceCandidate(std::unique_ptr(candidate), [tsfn](RTCError error) { + RTC_DLOG(LS_INFO) << "AddIceCandidate complete: " << error.ok(); + tsfn.BlockingCall([error = new RTCError(std::move(error))](Napi::Env env, Napi::Function jsCallback) { + jsCallback.Call({External::New(env, error, [](Napi::Env, RTCError* e) { delete e; })}); + }); + tsfn.Release(); + }); + + return deferred.Promise(); +} + +Napi::Value NapiPeerConnection::GetSenders(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto senders = pc_->GetSenders(); + auto jsSenders = Array::New(info.Env(), senders.size()); + for (uint32_t i = 0; i < senders.size(); i++) { + jsSenders.Set(i, NapiRtpSender::NewInstance(info.Env(), senders[i], wrapper_.get())); + } + + return jsSenders; +} + +Napi::Value NapiPeerConnection::GetReceivers(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto receivers = pc_->GetReceivers(); + auto jsReceivers = Array::New(info.Env(), receivers.size()); + for (uint32_t i = 0; i < receivers.size(); i++) { + jsReceivers.Set(i, NapiRtpReceiver::NewInstance(info.Env(), receivers[i], wrapper_.get())); + } + + return jsReceivers; +} + +Napi::Value NapiPeerConnection::GetTransceivers(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto transceivers = pc_->GetTransceivers(); + auto jsTransceivers = Array::New(info.Env(), transceivers.size()); + for (uint32_t i = 0; i < transceivers.size(); i++) { + jsTransceivers.Set(i, NapiRtpTransceiver::NewInstance(info.Env(), transceivers[i], wrapper_.get())); + } + + return jsTransceivers; +} + +Napi::Value NapiPeerConnection::GetConfiguration(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto configuration = pc_->GetConfiguration(); + auto jsConfiguration = Object::New(info.Env()); + + if (!NativeToJsConfiguration(configuration, jsConfiguration)) { + RTC_LOG(LS_ERROR) << "NativeToJsConfiguration failed"; + } + + return jsConfiguration; +} + +Napi::Value NapiPeerConnection::SetConfiguration(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + PeerConnectionInterface::RTCConfiguration config; + if (info.Length() > 0) { + auto jsConfiguration = info[0].As(); + if (!JsToNativeConfiguration(jsConfiguration, config)) { + RTC_LOG(LS_ERROR) << "JsToNativeConfiguration failed"; + } + } + + auto error = pc_->SetConfiguration(config); + if (!error.ok()) { + std::string message; + switch (error.type()) { + case RTCErrorType::INVALID_STATE: + message = "Invalid state"; + break; + case RTCErrorType::INVALID_MODIFICATION: + message = "Invalid modification"; + break; + case RTCErrorType::INVALID_RANGE: + message = "Invalid range"; + break; + case RTCErrorType::SYNTAX_ERROR: + message = "Syntax error"; + break; + case RTCErrorType::INVALID_PARAMETER: + message = "Invalid argument"; + break; + case RTCErrorType::INTERNAL_ERROR: + message = "Internal error"; + break; + default: + message = "Unknown error"; + break; + } + NAPI_THROW(Error::New(info.Env(), message), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnection::RestartIce(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + pc_->RestartIce(); + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnection::AddTransceiver(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject() && !info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + if (info[0].IsObject()) { + auto jsTrack = info[0].As(); + void* unwrapped; + NAPI_THROW_IF_FAILED(info.Env(), napi_unwrap(info.Env(), jsTrack, &unwrapped), info.Env().Undefined()); + auto track = static_cast(unwrapped); + + if (info.Length() > 1) { + auto jsInit = info[1].As(); + webrtc::RtpTransceiverInit init; + PopulateTransceiverInit(jsInit, init); + auto result_or_error = pc_->AddTransceiver(track->Get(), init); + if (!result_or_error.ok()) { + NAPI_THROW(Error::New(info.Env(), result_or_error.error().message()), info.Env().Undefined()); + } + return NapiRtpTransceiver::NewInstance(info.Env(), result_or_error.value(), wrapper_.get()); + } else { + auto result_or_error = pc_->AddTransceiver(track->Get()); + if (!result_or_error.ok()) { + NAPI_THROW(Error::New(info.Env(), result_or_error.error().message()), info.Env().Undefined()); + } + return NapiRtpTransceiver::NewInstance(info.Env(), result_or_error.value(), wrapper_.get()); + } + } else { + std::string jsMediatype = info[0].As().Utf8Value(); + cricket::MediaType mediaType; + if (jsMediatype == "audio") { + mediaType = cricket::MEDIA_TYPE_AUDIO; + } else if (jsMediatype == "video") { + mediaType = cricket::MEDIA_TYPE_VIDEO; + } else { + NAPI_THROW(Error::New(info.Env(), "Media type is not audio or video"), info.Env().Undefined()); + } + + if (info.Length() > 1) { + auto jsInit = info[1].As(); + webrtc::RtpTransceiverInit init; + PopulateTransceiverInit(jsInit, init); + auto result_or_error = pc_->AddTransceiver(mediaType, init); + if (!result_or_error.ok()) { + NAPI_THROW(Error::New(info.Env(), result_or_error.error().message()), info.Env().Undefined()); + } + return NapiRtpTransceiver::NewInstance(info.Env(), result_or_error.value(), wrapper_.get()); + } else { + auto result_or_error = pc_->AddTransceiver(mediaType); + if (!result_or_error.ok()) { + NAPI_THROW(Error::New(info.Env(), result_or_error.error().message()), info.Env().Undefined()); + } + return NapiRtpTransceiver::NewInstance(info.Env(), result_or_error.value(), wrapper_.get()); + } + } + NAPI_THROW(Error::New(info.Env(), "Invalid argument type for the first parameter"), info.Env().Undefined()); +} + +Napi::Value NapiPeerConnection::Close(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + // Close操作可能耗时较长,放到信令线程执行以避免阻塞主线程 + pc_->signaling_thread()->PostTask([pc = pc_] { + RTC_DLOG(LS_INFO) << "Do Close"; + pc->Close(); + }); + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnection::GetStats(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0 || info[0].IsNull() || info[0].IsUndefined()) { + auto asyncWorker = AsyncWorkerGetStats::Create(info.Env(), "GetStats"); + pc_->GetStats(asyncWorker->GetCallback().get()); + return asyncWorker->GetPromise(); + } + + if (!info[0].IsObject()) { + auto deferred = Promise::Deferred::New(info.Env()); + deferred.Reject(Error::New(info.Env(), "Invalid argument").Value()); + return deferred.Promise(); + } + + auto napiTrack = NapiMediaStreamTrack::Unwrap(info[0].As()); + if (!napiTrack) { + auto deferred = Promise::Deferred::New(info.Env()); + deferred.Reject(Error::New(info.Env(), "Invalid argument").Value()); + return deferred.Promise(); + } + + auto track = napiTrack->Get(); + auto senders = pc_->GetSenders(); + auto receivers = pc_->GetReceivers(); + + auto numOfSenders = std::count_if(senders.cbegin(), senders.cend(), [track](const auto& sender) { + auto t = sender->track(); + return t && t->id() == track->id(); + }); + auto numOfReceivers = std::count_if(receivers.cbegin(), receivers.cend(), [track](const auto& receiver) { + return receiver->track()->id() == track->id(); + }); + + if ((numOfSenders + numOfReceivers) != 1) { + // reject with InvalidAccessError + auto deferred = Promise::Deferred::New(info.Env()); + deferred.Reject(Error::New(info.Env(), "Invalid access").Value()); + return deferred.Promise(); + } + + auto asyncWorker = AsyncWorkerGetStats::Create(info.Env(), "GetStats"); + if (numOfSenders == 1) { + auto sender = std::find_if(senders.cbegin(), senders.cend(), [track](const auto& sender) { + auto t = sender->track(); + return t && t->id() == track->id(); + }); + pc_->GetStats(*sender, asyncWorker->GetCallback()); + } else { + auto receiver = std::find_if(receivers.cbegin(), receivers.cend(), [track](const auto& receiver) { + return receiver->track()->id() == track->id(); + }); + pc_->GetStats(*receiver, asyncWorker->GetCallback()); + } + + return asyncWorker->GetPromise(); +} + +Napi::Value NapiPeerConnection::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiPeerConnection")); +#endif + + return json; +} + +Napi::Value NapiPeerConnection::GenerateCertificate(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = new AsyncWorkerCertificate(info.Env(), "GenerateCertificateWorker"); + auto deferred = asyncWorker->GetDeferred(); + + if (info.Length() < 1 || info[0].IsObject()) { + deferred.Reject(Error::New(info.Env(), "Invalid argument").Value()); + return asyncWorker->GetPromise(); + } + + rtc::KeyParams key_params; + auto keyParamsName = info[0].As().Utf8Value(); + + if (keyParamsName == "RSA") { + key_params = rtc::KeyParams::RSA(); + } else if (keyParamsName == "ECDSA") { + key_params = rtc::KeyParams::ECDSA(); + } else { + RTC_DLOG(LS_ERROR) << "Unsupported key algorithm"; + } + + asyncWorker->start(key_params, absl::nullopt); + return asyncWorker->GetPromise(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/peer_connection.h b/sdk/ohos/src/ohos_webrtc/peer_connection.h new file mode 100644 index 0000000000000000000000000000000000000000..e94f2929597838a0748f39bcbec8f44d01f6d7a1 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/peer_connection.h @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_PEER_CONNECTION_H +#define WEBRTC_PEER_CONNECTION_H + +#include +#include +#include +#include +#include + +#include "napi.h" + +#include "api/peer_connection_interface.h" + +namespace webrtc { + +class PeerConnectionFactoryWrapper; + +class NapiPeerConnectionWrapper : public PeerConnectionObserver { +public: + static std::unique_ptr Create( + std::shared_ptr factoryWrapper, + const PeerConnectionInterface::RTCConfiguration& config); + + ~NapiPeerConnectionWrapper() override; + + const std::shared_ptr GetPeerConnectionFactoryWrapper() const + { + return factory_; + } + + const rtc::scoped_refptr& GetPeerConnection() const + { + return pc_; + } + + bool GetEventHandler(const std::string& type, Napi::Function& fn); + bool SetEventHandler(const std::string& type, Napi::Function fn, Napi::Value receiver); + bool RemoveEventHandler(const std::string& type); + void RemoveAllEventHandlers(); + +protected: + friend class NapiPeerConnection; + + NapiPeerConnectionWrapper(); + + void SetPeerConnectionFactoryWrapper(std::shared_ptr factoryWrapper) + { + factory_ = std::move(factoryWrapper); + } + + void SetPeerConnection(rtc::scoped_refptr pc) + { + pc_ = pc; + } + + bool GetEventHandler(const std::string& type, Napi::ThreadSafeFunction& tsfn); + +protected: + void OnIceCandidate(const IceCandidateInterface* candidate) override; + void OnIceCandidateError( + const std::string& address, int port, const std::string& url, int error_code, + const std::string& error_text) override; + + void OnIceCandidatesRemoved(const std::vector& candidates) override; + void OnSignalingChange(PeerConnectionInterface::SignalingState new_state) override; + void OnIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) override; + void OnStandardizedIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) override; + void OnConnectionChange(PeerConnectionInterface::PeerConnectionState new_state) override; + void OnIceConnectionReceivingChange(bool receiving) override; + void OnIceGatheringChange(PeerConnectionInterface::IceGatheringState new_state) override; + void OnIceSelectedCandidatePairChanged(const cricket::CandidatePairChangeEvent& event) override; + void OnAddStream(rtc::scoped_refptr stream) override; + void OnRemoveStream(rtc::scoped_refptr stream) override; + void OnDataChannel(rtc::scoped_refptr channel) override; + void OnRenegotiationNeeded() override; + void OnNegotiationNeededEvent(uint32_t event_id) override; + void OnAddTrack( + rtc::scoped_refptr receiver, + const std::vector>& streams) override; + void OnTrack(rtc::scoped_refptr transceiver) override; + void OnRemoveTrack(rtc::scoped_refptr receiver) override; + +private: + std::shared_ptr factory_; + rtc::scoped_refptr pc_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + // set by 'onxxx' like event handler attribute, eg. onicecandidate. + std::map eventHandlers_; + + std::mutex eventMutex_; + + // Call event handler in this thread (instead of signaling thread) to avoid potential deadlock. + // There is a bug in OpenHarmony (4.1 and before), see + // https://gitee.com/openharmony/arkui_napi/commit/485e75b6f623bcacf9ab4cb4c82bf0f077fe1f6e + std::unique_ptr eventThread_; +}; + +class NapiPeerConnection : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Value NewInstance(Napi::Env env, std::unique_ptr wrapper); + + explicit NapiPeerConnection(const Napi::CallbackInfo& info); + ~NapiPeerConnection(); + +protected: + Napi::Value GetCanTrickleIceCandidates(const Napi::CallbackInfo& info); + Napi::Value GetSignalingState(const Napi::CallbackInfo& info); + Napi::Value GetIceGatheringState(const Napi::CallbackInfo& info); + Napi::Value GetIceConnectionState(const Napi::CallbackInfo& info); + Napi::Value GetConnectionState(const Napi::CallbackInfo& info); + Napi::Value GetLocalDescription(const Napi::CallbackInfo& info); + Napi::Value GetRemoteDescription(const Napi::CallbackInfo& info); + Napi::Value GetCurrentLocalDescription(const Napi::CallbackInfo& info); + Napi::Value GetCurrentRemoteDescription(const Napi::CallbackInfo& info); + Napi::Value GetPendingLocalDescription(const Napi::CallbackInfo& info); + Napi::Value GetPendingRemoteDescription(const Napi::CallbackInfo& info); + Napi::Value GetSctp(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value AddTrack(const Napi::CallbackInfo& info); + Napi::Value RemoveTrack(const Napi::CallbackInfo& info); + Napi::Value SetLocalDescription(const Napi::CallbackInfo& info); + Napi::Value SetRemoteDescription(const Napi::CallbackInfo& info); + Napi::Value CreateOffer(const Napi::CallbackInfo& info); + Napi::Value CreateAnswer(const Napi::CallbackInfo& info); + Napi::Value CreateDataChannel(const Napi::CallbackInfo& info); + Napi::Value AddIceCandidate(const Napi::CallbackInfo& info); + Napi::Value GetSenders(const Napi::CallbackInfo& info); + Napi::Value GetReceivers(const Napi::CallbackInfo& info); + Napi::Value GetTransceivers(const Napi::CallbackInfo& info); + Napi::Value GetConfiguration(const Napi::CallbackInfo& info); + Napi::Value SetConfiguration(const Napi::CallbackInfo& info); + Napi::Value RestartIce(const Napi::CallbackInfo& info); + Napi::Value AddTransceiver(const Napi::CallbackInfo& info); + Napi::Value Close(const Napi::CallbackInfo& info); + Napi::Value GetStats(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + static Napi::Value GenerateCertificate(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::unique_ptr wrapper_; + rtc::scoped_refptr pc_; + + Napi::ObjectReference sctpTransportRef_; +}; + +} // namespace webrtc + +#endif // WEBRTC_PEER_CONNECTION_H diff --git a/sdk/ohos/src/ohos_webrtc/peer_connection_factory.cpp b/sdk/ohos/src/ohos_webrtc/peer_connection_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cc79e1f38a8403938c8a0f1325f710958d0c3888 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/peer_connection_factory.cpp @@ -0,0 +1,458 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "peer_connection_factory.h" + +#include "api/audio_codecs/builtin_audio_decoder_factory.h" +#include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/create_peerconnection_factory.h" +#include "api/scoped_refptr.h" +#include "rtc_base/physical_socket_server.h" + +#include "configuration.h" +#include "media_track_constraints.h" +#include "media_source.h" +#include "media_stream_track.h" +#include "peer_connection.h" +#include "audio_device/audio_device_module.h" +#include "camera/camera_enumerator.h" +#include "camera/camera_capturer.h" +#include "desktop_capture/desktop_capturer.h" +#include "video/video_track_source.h" +#include "user_media/media_constraints_util.h" +#include "video_encoder_factory.h" +#include "video_decoder_factory.h" +#include "audio_processing_factory.h" +#include "utils/marcos.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "PeerConnectionFactory"; + +const char kMethodNameSetDefault[] = "setDefault"; +const char kMethodNameCreatePeerConnection[] = "createPeerConnection"; +const char kMethodNameCreateAudioSource[] = "createAudioSource"; +const char kMethodNameCreateAudioTrack[] = "createAudioTrack"; +const char kMethodNameCreateVideoSource[] = "createVideoSource"; +const char kMethodNameCreateVideoTrack[] = "createVideoTrack"; +const char kMethodNameStartAecDump[] = "startAecDump"; +const char kMethodNameStopAecDump[] = "stopAecDump"; +const char kMethodNameToJson[] = "toJSON"; + +class NapiPeerConnectionFactoryOptions { +public: + NAPI_ATTRIBUTE_NAME_DECLARE(Adm, adm); + NAPI_ATTRIBUTE_NAME_DECLARE(VideoEncoderFactory, videoEncoderFactory); + NAPI_ATTRIBUTE_NAME_DECLARE(VideoDecoderFactory, videoDecoderFactory); + NAPI_ATTRIBUTE_NAME_DECLARE(AudioProcessing, audioProcessing); +}; + +std::mutex PeerConnectionFactoryWrapper::mutex_; +std::shared_ptr PeerConnectionFactoryWrapper::defaultFactory_; + +std::shared_ptr PeerConnectionFactoryWrapper::GetDefault() +{ + UNUSED std::lock_guard lock(mutex_); + if (!defaultFactory_) { + defaultFactory_ = std::shared_ptr(new PeerConnectionFactoryWrapper()); + if (!defaultFactory_->Init(nullptr, nullptr, nullptr, nullptr)) { + defaultFactory_.reset(); + } + } + + return defaultFactory_; +} + +void PeerConnectionFactoryWrapper::SetDefault(std::shared_ptr wrapper) +{ + UNUSED std::lock_guard lock(mutex_); + defaultFactory_ = wrapper; +} + +std::shared_ptr PeerConnectionFactoryWrapper::Create( + rtc::scoped_refptr adm, std::unique_ptr videoEncoderFactory, + std::unique_ptr videoDecoderFactory, rtc::scoped_refptr audioProcessing) +{ + auto wrapper = std::shared_ptr(new PeerConnectionFactoryWrapper()); + if (!wrapper->Init(adm, std::move(videoEncoderFactory), std::move(videoDecoderFactory), audioProcessing)) { + return nullptr; + } + return wrapper; +} + +bool PeerConnectionFactoryWrapper::Init( + rtc::scoped_refptr adm, std::unique_ptr videoEncoderFactory, + std::unique_ptr videoDecoderFactory, rtc::scoped_refptr audioProcessing) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + rtc::ThreadManager::Instance()->WrapCurrentThread(); + + auto socketServer = std::make_unique(); + auto networkThread = std::make_unique(socketServer.get()); + networkThread->SetName("network_thread", nullptr); + if (!networkThread->Start()) { + RTC_LOG(LS_ERROR) << "Failed to start network thread"; + return false; + } + + auto workerThread = rtc::Thread::Create(); + workerThread->SetName("worker_thread", nullptr); + if (!workerThread->Start()) { + RTC_LOG(LS_ERROR) << "Failed to start worker thread"; + return false; + } + + auto signalingThread = rtc::Thread::Create(); + signalingThread->SetName("signaling_thread", NULL); + if (!signalingThread->Start()) { + RTC_LOG(LS_ERROR) << "Failed to start signaling thread"; + return false; + } + + pcFactory_ = CreatePeerConnectionFactory( + networkThread.get(), workerThread.get(), signalingThread.get(), adm, CreateBuiltinAudioEncoderFactory(), + CreateBuiltinAudioDecoderFactory(), + videoEncoderFactory ? std::move(videoEncoderFactory) : CreateDefaultVideoEncoderFactory(), + videoDecoderFactory ? std::move(videoDecoderFactory) : CreateDefaultVideoDecoderFactory(), + nullptr /* audio_mixer */, audioProcessing); + + if (!pcFactory_) { + RTC_LOG(LS_ERROR) << "Failed to create PeerConnectionFactory"; + return false; + } + + socketServer_ = std::move(socketServer); + networkThread_ = std::move(networkThread); + workerThread_ = std::move(workerThread); + signalingThread_ = std::move(signalingThread); + + return true; +} + +FunctionReference NapiPeerConnectionFactory::constructor_; + +NapiPeerConnectionFactory::NapiPeerConnectionFactory(const CallbackInfo& info) + : ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr adm; + std::unique_ptr videoEncoderFactory; + std::unique_ptr videoDecoderFactory; + rtc::scoped_refptr audioProcessing; + if (info.Length() > 0 && info[0].IsObject()) { + auto jsOptions = info[0].As(); + if (jsOptions.Has(NapiPeerConnectionFactoryOptions::kAttributeNameAdm)) { + auto jsAdm = jsOptions.Get(NapiPeerConnectionFactoryOptions::kAttributeNameAdm).As(); + auto napiAdm = NapiAudioDeviceModule::Unwrap(jsAdm); + adm = napiAdm->Get(); + } + if (jsOptions.Has(NapiPeerConnectionFactoryOptions::kAttributeNameVideoEncoderFactory)) { + auto jsVideoEncoderFactory = + jsOptions.Get(NapiPeerConnectionFactoryOptions::kAttributeNameVideoEncoderFactory).As(); + videoEncoderFactory = CreateVideoEncoderFactory(jsVideoEncoderFactory); + } + if (jsOptions.Has(NapiPeerConnectionFactoryOptions::kAttributeNameVideoDecoderFactory)) { + auto jsVideoDecoderFactory = + jsOptions.Get(NapiPeerConnectionFactoryOptions::kAttributeNameVideoDecoderFactory).As(); + videoDecoderFactory = CreateVideoDecoderFactory(jsVideoDecoderFactory); + } + if (jsOptions.Has(NapiPeerConnectionFactoryOptions::kAttributeNameAudioProcessing)) { + auto jsAudioProcessing = + jsOptions.Get(NapiPeerConnectionFactoryOptions::kAttributeNameAudioProcessing).As(); + auto napiAudioProcessing = NapiAudioProcessing::Unwrap(jsAudioProcessing); + audioProcessing = napiAudioProcessing->Get(); + } + } + + wrapper_ = PeerConnectionFactoryWrapper::Create( + adm, std::move(videoEncoderFactory), std::move(videoDecoderFactory), audioProcessing); +} + +void NapiPeerConnectionFactory::Init(Napi::Env env, Object exports) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + StaticMethod<&NapiPeerConnectionFactory::SetDefault>(kMethodNameSetDefault), + InstanceMethod<&NapiPeerConnectionFactory::CreatePeerConnection>(kMethodNameCreatePeerConnection), + InstanceMethod<&NapiPeerConnectionFactory::CreateAudioSource>(kMethodNameCreateAudioSource), + InstanceMethod<&NapiPeerConnectionFactory::CreateAudioTrack>(kMethodNameCreateAudioTrack), + InstanceMethod<&NapiPeerConnectionFactory::CreateVideoSource>(kMethodNameCreateVideoSource), + InstanceMethod<&NapiPeerConnectionFactory::CreateVideoTrack>(kMethodNameCreateVideoTrack), + InstanceMethod<&NapiPeerConnectionFactory::StartAecDump>(kMethodNameStartAecDump), + InstanceMethod<&NapiPeerConnectionFactory::StopAecDump>(kMethodNameStopAecDump), + InstanceMethod<&NapiPeerConnectionFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Value NapiPeerConnectionFactory::SetDefault(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not Object"), info.Env().Undefined()); + } + + auto nativeFactory = NapiPeerConnectionFactory::Unwrap(info[0].As()); + if (!nativeFactory) { + NAPI_THROW(TypeError::New(info.Env(), "Invalid argument"), info.Env().Undefined()); + } + + PeerConnectionFactoryWrapper::SetDefault(nativeFactory->GetWrapper()); + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnectionFactory::CreatePeerConnection(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not Object"), info.Env().Undefined()); + } + + PeerConnectionInterface::RTCConfiguration config; + config.sdp_semantics = SdpSemantics::kUnifiedPlan; + JsToNativeConfiguration(info[0].As(), config); + + auto observer = NapiPeerConnectionWrapper::Create(wrapper_, config); + return NapiPeerConnection::NewInstance(info.Env(), std::move(observer)); +} + +Napi::Value NapiPeerConnectionFactory::CreateAudioSource(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + cricket::AudioOptions options; + if (info.Length() > 0) { + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "The first argument must be an object"), info.Env().Undefined()); + } + + MediaTrackConstraints audioConstraints; + NapiMediaConstraints::JsToNative(info[0], audioConstraints); + if (info.Env().IsExceptionPending()) { + NAPI_THROW(info.Env().GetAndClearPendingException(), info.Env().Undefined()); + } + CopyConstraintsIntoAudioOptions(audioConstraints, options); + } + + if (options.echo_cancellation) { + RTC_DLOG(LS_VERBOSE) << "echo_cancellation = " << options.echo_cancellation.value(); + } + auto source = GetFactory()->CreateAudioSource(options); + if (!source) { + NAPI_THROW(Error::New(info.Env(), "Failed to create audio source"), info.Env().Undefined()); + } + + return NapiAudioSource::NewInstance(info.Env(), source); +} + +Napi::Value NapiPeerConnectionFactory::CreateAudioTrack(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 2) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not String"), info.Env().Undefined()); + } + + if (!info[1].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "Second argument is not Object"), info.Env().Undefined()); + } + + auto id = info[0].As().Utf8Value(); + RTC_DLOG(LS_VERBOSE) << "id=" << id; + + auto source = NapiAudioSource::Unwrap(info[1].As()); + if (!source) { + NAPI_THROW(TypeError::New(info.Env(), "Second argument is not AudioSource"), info.Env().Undefined()); + } + + auto track = GetFactory()->CreateAudioTrack(id, source->Get()); + if (!track) { + NAPI_THROW(TypeError::New(info.Env(), "Failed to create audio track"), info.Env().Undefined()); + } + + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(track)); +} + +Napi::Value NapiPeerConnectionFactory::CreateVideoSource(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + MediaTrackConstraints video; + bool isScreencast = false; + if (info.Length() > 0) { + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "The first argument must be an object"), info.Env().Undefined()); + } + + NapiMediaConstraints::JsToNative(info[0], video); + if (info.Env().IsExceptionPending()) { + NAPI_THROW(info.Env().GetAndClearPendingException(), info.Env().Undefined()); + } + } else { + video.Initialize(); + } + + if (info.Length() > 1) { + if (!info[1].IsBoolean()) { + NAPI_THROW(TypeError::New(info.Env(), "The first argument must be boolean"), info.Env().Undefined()); + } + + isScreencast = info[1].As().Value(); + } + + std::unique_ptr videoCapturer; + if (isScreencast) { + // default size + int width = 720; + int height = 1080; + GetScreenCaptureConstraints(video, width, height); + + video::VideoProfile profile; + profile.resolution.width = width; + profile.resolution.height = height; + profile.format = video::PixelFormat::RGBA; + videoCapturer = DesktopCapturer::Create(profile); + if (!videoCapturer) { + NAPI_THROW(Error::New(info.Env(), "Failed to create desktop capturer"), info.Env().Undefined()); + } + } else { + CameraCaptureSettings selectedSetting; + std::string failedConstraintName; + if (SelectSettingsForVideo( + CameraEnumerator::GetDevices(), video, 640, 480, 30, selectedSetting, failedConstraintName)) + { + RTC_DLOG(LS_VERBOSE) << "Selected camera device: " << selectedSetting.deviceId + << ", resolution = " << selectedSetting.profile.resolution.width << "x" + << selectedSetting.profile.resolution.height + << ", format = " << selectedSetting.profile.format + << ", framerate = " << selectedSetting.profile.frameRateRange.min << "-" + << selectedSetting.profile.frameRateRange.max; + } else { + RTC_LOG(LS_ERROR) << "Failed to select settings for video, unsatisfied constraint: " + << failedConstraintName; + NAPI_THROW( + Error::New(info.Env(), std::string("Unsatisfied constraint: ") + failedConstraintName), + info.Env().Undefined()); + } + + videoCapturer = CameraCapturer::Create(selectedSetting.deviceId, selectedSetting.profile); + if (!videoCapturer) { + NAPI_THROW(Error::New(info.Env(), "Failed to create camera capturer"), info.Env().Undefined()); + } + } + + auto source = OhosVideoTrackSource::Create( + std::move(videoCapturer), wrapper_->GetSignalingThread(), EglEnv::GetDefault().GetContext()); + if (!source) { + NAPI_THROW(Error::New(info.Env(), "Failed to create video source"), info.Env().Undefined()); + } + + return NapiVideoSource::NewInstance(info.Env(), source); +} + +Napi::Value NapiPeerConnectionFactory::CreateVideoTrack(const CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 2) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not String"), info.Env().Undefined()); + } + + if (!info[1].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "Second argument is not Object"), info.Env().Undefined()); + } + + auto id = info[0].As().Utf8Value(); + RTC_DLOG(LS_VERBOSE) << "id=" << id; + + auto source = NapiVideoSource::Unwrap(info[1].As()); + if (!source) { + NAPI_THROW(TypeError::New(info.Env(), "Second argument is not VideoSource"), info.Env().Undefined()); + } + + auto track = GetFactory()->CreateVideoTrack(rtc::scoped_refptr(source->Get()), id); + if (!track) { + NAPI_THROW(TypeError::New(info.Env(), "Failed to create video track"), info.Env().Undefined()); + } + + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(track)); +} + +Napi::Value NapiPeerConnectionFactory::StartAecDump(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto fd = info[0].As().Int32Value(); + auto max_size_bytes = info[1].As().Int32Value(); + + FILE* file = fdopen(fd, "wb"); + if (!file) { + close(fd); + return Boolean::New(info.Env(), false); + } + + return Boolean::New(info.Env(), GetFactory()->StartAecDump(file, max_size_bytes)); +} + +Napi::Value NapiPeerConnectionFactory::StopAecDump(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + GetFactory()->StopAecDump(); + + return info.Env().Undefined(); +} + +Napi::Value NapiPeerConnectionFactory::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiPeerConnectionFactory")); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/peer_connection_factory.h b/sdk/ohos/src/ohos_webrtc/peer_connection_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..143b17e2e20b14c40d50e77c2205a42b5a73bcf9 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/peer_connection_factory.h @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_PEER_CONNECTION_FACTORY_H +#define WEBRTC_PEER_CONNECTION_FACTORY_H + +#include "audio_device/audio_device_module.h" + +#include +#include + +#include "napi.h" + +#include "api/peer_connection_interface.h" +#include "rtc_base/socket_server.h" + +namespace webrtc { + +class PeerConnectionFactoryWrapper : public std::enable_shared_from_this { +public: + static std::shared_ptr GetDefault(); + + static void SetDefault(std::shared_ptr wrapper); + + static std::shared_ptr Create( + rtc::scoped_refptr adm = nullptr, + std::unique_ptr videoEncoderFactory = nullptr, + std::unique_ptr videoDecoderFactory = nullptr, + rtc::scoped_refptr audioProcessing = nullptr); + + PeerConnectionFactoryInterface* GetFactory() + { + return pcFactory_.get(); + } + + rtc::SocketServer* GetSocketServer() + { + return socketServer_.get(); + } + + rtc::Thread* GetNetworkThread() + { + return networkThread_.get(); + } + + rtc::Thread* GetSignalingThread() + { + return signalingThread_.get(); + } + + rtc::Thread* GetWorkerThread() + { + return workerThread_.get(); + } + +protected: + PeerConnectionFactoryWrapper() = default; + + bool Init( + rtc::scoped_refptr adm, std::unique_ptr videoEncoderFactory, + std::unique_ptr videoDecoderFactory, rtc::scoped_refptr audioProcessing); + +private: + static std::mutex mutex_; + static std::shared_ptr defaultFactory_; + + std::unique_ptr socketServer_; + std::unique_ptr networkThread_; + std::unique_ptr workerThread_; + std::unique_ptr signalingThread_; + rtc::scoped_refptr pcFactory_; +}; + +class NapiPeerConnectionFactory : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + explicit NapiPeerConnectionFactory(const Napi::CallbackInfo& info); + + std::shared_ptr GetWrapper() const + { + return wrapper_; + } + +protected: + static Napi::Value SetDefault(const Napi::CallbackInfo& info); + + Napi::Value CreatePeerConnection(const Napi::CallbackInfo& info); + Napi::Value CreateAudioSource(const Napi::CallbackInfo& info); + Napi::Value CreateAudioTrack(const Napi::CallbackInfo& info); + Napi::Value CreateVideoSource(const Napi::CallbackInfo& info); + Napi::Value CreateVideoTrack(const Napi::CallbackInfo& info); + Napi::Value StartAecDump(const Napi::CallbackInfo& info); + Napi::Value StopAecDump(const Napi::CallbackInfo& info); + + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + PeerConnectionFactoryInterface* GetFactory() const + { + return wrapper_->GetFactory(); + } + + std::shared_ptr wrapper_; +}; + +} // namespace webrtc + +#endif // WEBRTC_PEER_CONNECTION_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/render/egl_config_attributes.h b/sdk/ohos/src/ohos_webrtc/render/egl_config_attributes.h new file mode 100644 index 0000000000000000000000000000000000000000..bc6648a7eef39c6634c8c5bf5f240447328e0da3 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/egl_config_attributes.h @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_EGL_CONFIG_ATTRIBUTES_H +#define WEBRTC_RENDER_EGL_CONFIG_ATTRIBUTES_H + +#include +#include + +#include + +namespace webrtc { + +class EglConfigAttributes { +public: + class Builder { + public: + Builder& SetVersion(int version) + { + version_ = version; + return *this; + } + + Builder& SetHasAlphaChannel(bool hasAlphaChannel) + { + hasAlphaChannel_ = hasAlphaChannel; + return *this; + } + + Builder& SetSupportsPixelBuffer(bool supportsPixelBuffer) + { + supportsPixelBuffer_ = supportsPixelBuffer; + return *this; + } + + std::vector Build() const + { + std::vector attrs; + attrs.push_back(EGL_RED_SIZE); + attrs.push_back(8); + attrs.push_back(EGL_GREEN_SIZE); + attrs.push_back(8); + attrs.push_back(EGL_BLUE_SIZE); + attrs.push_back(8); + if (hasAlphaChannel_) { + attrs.push_back(EGL_ALPHA_SIZE); + attrs.push_back(8); + } + if (version_ == 2 || version_ == 3) { + attrs.push_back(EGL_RENDERABLE_TYPE); + attrs.push_back(version_ == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT); + } + if (supportsPixelBuffer_) { + attrs.push_back(EGL_SURFACE_TYPE); + attrs.push_back(EGL_PBUFFER_BIT); + } + attrs.push_back(EGL_NONE); + + return attrs; + } + + private: + int version_{3}; + bool hasAlphaChannel_{false}; + bool supportsPixelBuffer_{false}; + }; + + static const std::vector DEFAULT; + static const std::vector RGBA; + static const std::vector PIXEL_BUFFER; + static const std::vector RGBA_PIXEL_BUFFER; +}; + +inline const std::vector EglConfigAttributes::DEFAULT = Builder().Build(); + +inline const std::vector EglConfigAttributes::RGBA = Builder().SetHasAlphaChannel(true).Build(); + +inline const std::vector EglConfigAttributes::PIXEL_BUFFER = Builder().SetSupportsPixelBuffer(true).Build(); + +inline const std::vector EglConfigAttributes::RGBA_PIXEL_BUFFER = + Builder().SetHasAlphaChannel(true).SetSupportsPixelBuffer(true).Build(); + +} // namespace webrtc + +#endif // WEBRTC_RENDER_EGL_CONFIG_ATTRIBUTES_H diff --git a/sdk/ohos/src/ohos_webrtc/render/egl_context.cpp b/sdk/ohos/src/ohos_webrtc/render/egl_context.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cbce73c3f2ded70f66b69e2ac3a88b2f16df6893 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/egl_context.cpp @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "egl_context.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiEglContext::constructor_; + +void NapiEglContext::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiEglContext::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Value NapiEglContext::NewInstance(Napi::Env env, std::shared_ptr eglContext) +{ + if (!eglContext) { + NAPI_THROW(Error::New(env, "Invalid arguments"), env.Undefined()); + } + + auto external = External::New( + env, eglContext.get(), [eglContext](Napi::Env /*env*/, EglContext* /*ctx*/) { (void)eglContext; }); + return constructor_.New({external}); +} + +NapiEglContext::NapiEglContext(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW_VOID(Error::New(info.Env(), "Wrong number of arguments")); + } + + auto context = info[0].As>().Data(); + if (context) { + eglContext_ = context->shared_from_this(); + } +} + +NapiEglContext::~NapiEglContext() = default; + +Napi::Value NapiEglContext::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiEglContext")); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/egl_context.h b/sdk/ohos/src/ohos_webrtc/render/egl_context.h new file mode 100644 index 0000000000000000000000000000000000000000..ef5faef055dbff2d2883f63a31dc3a1ce1bcf55a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/egl_context.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_EGL_CONTEXT_H +#define WEBRTC_EGL_CONTEXT_H + +#include "../utils/marcos.h" + +#include + +#include + +#include "napi.h" + +namespace webrtc { + +class EglContext : public std::enable_shared_from_this { +public: + virtual ~EglContext() = default; + virtual EGLContext GetRawContext() = 0; +}; + +class NapiEglContext : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(EglContext); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + static Napi::Value NewInstance(Napi::Env env, std::shared_ptr eglContext); + + std::shared_ptr Get() + { + return eglContext_; + } + +protected: + friend class ObjectWrap; + explicit NapiEglContext(const Napi::CallbackInfo& info); + ~NapiEglContext(); + + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::shared_ptr eglContext_; +}; + +} // namespace webrtc + +#endif // WEBRTC_EGL_CONTEXT_H diff --git a/sdk/ohos/src/ohos_webrtc/render/egl_env.cpp b/sdk/ohos/src/ohos_webrtc/render/egl_env.cpp new file mode 100644 index 0000000000000000000000000000000000000000..983ad39b040b16f512d45e3ab7b13a813d864cb5 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/egl_env.cpp @@ -0,0 +1,424 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "egl_env.h" +#include "egl_config_attributes.h" + +#include + +#include + +namespace webrtc { + +namespace { + +constexpr char CHARACTER_WHITESPACE = ' '; +constexpr const char* CHARACTER_STRING_WHITESPACE = " "; + +#define CASE_EGL_STR(value) \ + case value: \ + return #value + +static const char* GetEglErrorString() +{ + EGLint error = eglGetError(); + switch (error) { + CASE_EGL_STR(EGL_SUCCESS); + CASE_EGL_STR(EGL_NOT_INITIALIZED); + CASE_EGL_STR(EGL_BAD_ACCESS); + CASE_EGL_STR(EGL_BAD_ALLOC); + CASE_EGL_STR(EGL_BAD_ATTRIBUTE); + CASE_EGL_STR(EGL_BAD_CONTEXT); + CASE_EGL_STR(EGL_BAD_CONFIG); + CASE_EGL_STR(EGL_BAD_CURRENT_SURFACE); + CASE_EGL_STR(EGL_BAD_DISPLAY); + CASE_EGL_STR(EGL_BAD_SURFACE); + CASE_EGL_STR(EGL_BAD_MATCH); + CASE_EGL_STR(EGL_BAD_PARAMETER); + CASE_EGL_STR(EGL_BAD_NATIVE_PIXMAP); + CASE_EGL_STR(EGL_BAD_NATIVE_WINDOW); + CASE_EGL_STR(EGL_CONTEXT_LOST); + default: + return "Unknow Error"; + } +} +#undef CASE_EGL_STR + +static bool CheckEglExtension(const char* eglExtensions, const char* eglExtension) +{ + // Check egl extension + size_t extLenth = strlen(eglExtension); + const char* endPos = eglExtensions + strlen(eglExtensions); + + while (eglExtensions < endPos) { + size_t len = 0; + if (*eglExtensions == CHARACTER_WHITESPACE) { + eglExtensions++; + continue; + } + len = strcspn(eglExtensions, CHARACTER_STRING_WHITESPACE); + if (len == extLenth && strncmp(eglExtension, eglExtensions, len) == 0) { + return true; + } + eglExtensions += len; + } + + return false; +} + +} // namespace + +class EglContextImpl : public EglContext { +public: + explicit EglContextImpl(EGLContext context) : eglContext_(context) {} + + EGLContext GetRawContext() override + { + return eglContext_; + } + +private: + EGLContext eglContext_{EGL_NO_CONTEXT}; +}; + +EglEnv& EglEnv::GetDefault() +{ + static std::unique_ptr _eglEnv = Create(EglConfigAttributes::DEFAULT); + return *_eglEnv; +} + +std::unique_ptr EglEnv::Create() +{ + return EglEnv::Create(nullptr); +} + +std::unique_ptr EglEnv::Create(std::shared_ptr sharedContext) +{ + return EglEnv::Create(sharedContext, EglConfigAttributes::DEFAULT); +} + +std::unique_ptr EglEnv::Create(const std::vector& configAttributes) +{ + return EglEnv::Create(nullptr, configAttributes); +} + +std::unique_ptr +EglEnv::Create(std::shared_ptr sharedContext, const std::vector& configAttributes) +{ + auto eglEnv = std::unique_ptr(new EglEnv()); + if (!eglEnv->Init(sharedContext ? sharedContext->GetRawContext() : EGL_NO_CONTEXT, configAttributes)) { + RTC_LOG(LS_ERROR) << "Failed to init egl context"; + return nullptr; + } + return eglEnv; +} + +int32_t EglEnv::GetOpenGLESVersionFromConfig(const std::vector& configAttributes) +{ + for (std::size_t i = 0; i < configAttributes.size() - 1; ++i) { + if (configAttributes[i] == EGL_RENDERABLE_TYPE) { + switch (configAttributes[i + 1]) { + case EGL_OPENGL_ES2_BIT: + return 2; + case EGL_OPENGL_ES3_BIT: + return 3; + default: + return 1; + } + } + } + return 1; +} + +EglEnv::EglEnv() = default; + +EglEnv::~EglEnv() +{ + Release(); +} + +std::shared_ptr EglEnv::GetContext() const +{ + return std::make_shared(eglContext_); +} + +bool EglEnv::CreatePbufferSurface(int width, int height) +{ + if (eglSurface_ != EGL_NO_SURFACE) { + RTC_LOG(LS_ERROR) << "Already has an EGLSurface"; + return false; + } + + EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; + eglSurface_ = eglCreatePbufferSurface(eglDisplay_, eglConfig_, attribs); + if (eglSurface_ == EGL_NO_SURFACE) { + RTC_LOG(LS_ERROR) << "Failed to create pixel buffer surface with size " << width << "x" << height << ": " + << eglGetError(); + return false; + } + + return true; +} + +bool EglEnv::CreateWindowSurface(ohos::NativeWindow window) +{ + if (eglSurface_ != EGL_NO_SURFACE) { + RTC_LOG(LS_ERROR) << "Already has an EGLSurface"; + return false; + } + + eglSurface_ = + eglCreateWindowSurface(eglDisplay_, eglConfig_, reinterpret_cast(window.Raw()), nullptr); + if (eglSurface_ == EGL_NO_SURFACE) { + RTC_LOG(LS_ERROR) << "Failed to create window surface: " << eglGetError(); + return false; + } + + return false; +} + +void EglEnv::ReleaseSurface() +{ + if (eglSurface_ != EGL_NO_SURFACE) { + eglDestroySurface(eglDisplay_, eglSurface_); + eglSurface_ = EGL_NO_SURFACE; + } +} + +bool EglEnv::MakeCurrent() +{ + if (eglGetCurrentContext() == eglContext_) { + RTC_LOG(LS_VERBOSE) << "xxx"; + return true; + } + + if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) { + RTC_LOG(LS_ERROR) << "Failed to make current, errno: " << eglGetError(); + return false; + } + + return true; +} + +bool EglEnv::DetachCurrent() +{ + if (!eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + RTC_LOG(LS_ERROR) << "Failed to detach Current, errno: " << eglGetError(); + return false; + } + + return true; +} + +bool EglEnv::SwapBuffers() +{ + if (eglSurface_ == EGL_NO_SURFACE) { + return false; + } + + EGLBoolean ret = eglSwapBuffers(eglDisplay_, eglSurface_); + if (ret == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to swap buffers, errno: " << eglGetError(); + return false; + } + + return true; +} + +bool EglEnv::SwapBuffers(uint64_t timestampNs) +{ + if (eglSurface_ == EGL_NO_SURFACE) { + RTC_LOG(LS_ERROR) << "No egl surface"; + return false; + } + + if (eglPresentationTimeANDROID_) { + if (eglPresentationTimeANDROID_(eglDisplay_, eglSurface_, timestampNs) == EGL_FALSE) { + RTC_LOG(LS_WARNING) << "Failed to eglPresentationTimeANDROID, errno: " << eglGetError(); + } + } else { + RTC_LOG(LS_WARNING) << "No egl extension of eglPresentationTimeANDROID"; + } + + if (eglSwapBuffers(eglDisplay_, eglSurface_) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to swap buffers, errno: " << eglGetError(); + return false; + } + + return true; +} + +int EglEnv::GetSurfaceWidth() const +{ + int width; + if (eglQuerySurface(eglDisplay_, eglSurface_, EGL_WIDTH, &width) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to query surface width, errno: " << eglGetError(); + return -1; + } + return width; +} + +int EglEnv::GetSurfaceHeight() const +{ + int height; + if (eglQuerySurface(eglDisplay_, eglSurface_, EGL_HEIGHT, &height) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to query surface height, errno: " << eglGetError(); + return -1; + } + return height; +} + +bool EglEnv::Init(EGLContext sharedContext, const std::vector& configAttributes) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // 获取当前的显示设备 + eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (eglDisplay_ == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Failed to create EGLDisplay gl errno: " << eglGetError(); + return false; + } + RTC_DLOG(LS_VERBOSE) << "eglDisplay_: " << eglDisplay_; + + EGLint major, minor; + // 初始化EGLDisplay + if (eglInitialize(eglDisplay_, &major, &minor) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to initialize EGLDisplay"; + return false; + } + RTC_LOG(LS_VERBOSE) << "eglInitialize success, version: " << major << "." << minor; + + SetupExtensions(); + + // 绑定图形绘制的API为OpenGLES + if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) { + RTC_LOG(LS_ERROR) << "Failed to bind OpenGL ES API"; + return false; + } + + unsigned int ret; + EGLint count = 0; + + // 获取一个有效的系统配置信息 + ret = eglChooseConfig(eglDisplay_, configAttributes.data(), &eglConfig_, 1, &count); + if (!(ret && static_cast(count) >= 1)) { + RTC_LOG(LS_ERROR) << "Failed to choose config"; + return false; + } + RTC_LOG(LS_VERBOSE) << "eglChooseConfig success, config: " << eglConfig_; + + const EGLint contextAttrs_[] = { + EGL_CONTEXT_CLIENT_VERSION, GetOpenGLESVersionFromConfig(configAttributes), EGL_NONE}; + + // 创建上下文 + eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, sharedContext, contextAttrs_); + if (eglContext_ == EGL_NO_CONTEXT) { + RTC_LOG(LS_ERROR) << "Failed to create egl context: " << GetEglErrorString(); + return false; + } + RTC_DLOG(LS_VERBOSE) << "eglContext_: " << eglContext_; + + // EGL环境初始化完成 + RTC_LOG(LS_VERBOSE) << "Create EGL context successfully"; + + return true; +} + +void EglEnv::Release() +{ + eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (eglContext_ != EGL_NO_CONTEXT) { + if (!eglDestroyContext(eglDisplay_, eglContext_)) { + RTC_LOG(LS_ERROR) << "Failed to destroy egl context: " << GetEglErrorString(); + } + eglContext_ = EGL_NO_CONTEXT; + } + + eglReleaseThread(); + eglTerminate(eglDisplay_); + eglDisplay_ = EGL_NO_DISPLAY; +} + +void EglEnv::SetupExtensions() +{ + const char* extensions = eglQueryString(eglDisplay_, EGL_EXTENSIONS); + if (extensions == nullptr) { + RTC_LOG(LS_WARNING) << "Egl no extensions"; + return; + } + + RTC_DLOG(LS_VERBOSE) << "Egl extensions: " << extensions; + + if (CheckEglExtension(extensions, "EGL_ANDROID_presentation_time")) { + eglPresentationTimeANDROID_ = + reinterpret_cast(eglGetProcAddress("eglPresentationTimeANDROID")); + if (!eglPresentationTimeANDROID_) { + RTC_LOG(LS_WARNING) << "Failed to get proc address of eglPresentationTimeANDROID"; + } + } else { + RTC_LOG(LS_WARNING) << "No egl extension of eglPresentationTimeANDROID"; + } + + if (!CheckEglExtension(extensions, "GL_OES_EGL_image_external")) { + RTC_DLOG(LS_VERBOSE) << "No egl extension: GL_OES_EGL_image_external"; + } +} + +using namespace Napi; + +FunctionReference NapiEglEnv::constructor_; + +void NapiEglEnv::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiEglEnv::GetContext>(kMethodNameGetContext), + InstanceMethod<&NapiEglEnv::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiEglEnv::NapiEglEnv(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + eglEnv_ = EglEnv::Create(nullptr, EglConfigAttributes::DEFAULT); +} + +NapiEglEnv::~NapiEglEnv() = default; + +Napi::Value NapiEglEnv::GetContext(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return NapiEglContext::NewInstance(info.Env(), eglEnv_->GetContext()); +} + +Napi::Value NapiEglEnv::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiEglEnv")); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/egl_env.h b/sdk/ohos/src/ohos_webrtc/render/egl_env.h new file mode 100644 index 0000000000000000000000000000000000000000..d6beda2103ede3825e36f15f05f246d4bc6f04c0 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/egl_env.h @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_EGL_ENV_H +#define WEBRTC_RENDER_EGL_ENV_H + +#include "egl_context.h" +#include "../helper/native_window.h" + +#include +#include +#include +#include + +#include + +#include "napi.h" +#include "utils/marcos.h" + +namespace webrtc { + +class EglEnv { +public: + // used for shared context + static EglEnv& GetDefault(); + + static std::unique_ptr Create(); + static std::unique_ptr Create(std::shared_ptr sharedContext); + static std::unique_ptr Create(const std::vector& configAttributes); + static std::unique_ptr + Create(std::shared_ptr sharedContext, const std::vector& configAttributes); + + static int32_t GetOpenGLESVersionFromConfig(const std::vector& configAttributes); + + ~EglEnv(); + + std::shared_ptr GetContext() const; + + bool CreatePbufferSurface(int width, int height); + bool CreateWindowSurface(ohos::NativeWindow window); + void ReleaseSurface(); + + bool MakeCurrent(); + bool DetachCurrent(); + + bool SwapBuffers(); + bool SwapBuffers(uint64_t timestampNs); + + int GetSurfaceWidth() const; + int GetSurfaceHeight() const; + +protected: + EglEnv(); + + bool Init(EGLContext sharedContext, const std::vector& configAttributes); + void Release(); + + void SetupExtensions(); + +private: + EGLConfig eglConfig_{}; + EGLDisplay eglDisplay_{EGL_NO_DISPLAY}; + EGLContext eglContext_{EGL_NO_CONTEXT}; + EGLSurface eglSurface_{EGL_NO_SURFACE}; + + PFNEGLPRESENTATIONTIMEANDROIDPROC eglPresentationTimeANDROID_{}; +}; + +class NapiEglEnv : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(EglEnv); + NAPI_METHOD_NAME_DECLARE(Create, create); + NAPI_METHOD_NAME_DECLARE(GetContext, getContext); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + +protected: + friend class ObjectWrap; + explicit NapiEglEnv(const Napi::CallbackInfo& info); + ~NapiEglEnv(); + + static Napi::Value Create(const Napi::CallbackInfo& info); + + Napi::Value GetContext(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::shared_ptr eglEnv_; +}; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_EGL_ENV_H diff --git a/sdk/ohos/src/ohos_webrtc/render/gl_drawer.cpp b/sdk/ohos/src/ohos_webrtc/render/gl_drawer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7512ad49c4352ca6e757ed476d1dac7c8dd064a3 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/gl_drawer.cpp @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gl_drawer.h" + +#include "rtc_base/logging.h" + +#include +#include + +namespace webrtc { + +namespace { + +static constexpr char DEFAULT_VERTEX_SHADER[] = R"( +attribute vec3 position; +attribute vec2 texCoord; + +varying vec2 vTexCoord; + +uniform mat4 transform; + +void main() +{ + gl_Position = transform * vec4(position, 1.0); + vTexCoord = texCoord; +} +)"; + +static constexpr char OES_FRAGMENT_SHADER[] = R"( +#extension GL_OES_EGL_image_external : require +precision mediump float; +varying vec2 vTexCoord; +uniform samplerExternalOES tex; + +void main() +{ + gl_FragColor = texture2D(tex, vTexCoord).rgba; +} +)"; + +static constexpr char RGB_FRAGMENT_SHADER[] = R"( +precision mediump float; +varying vec2 vTexCoord; +uniform sampler2D tex; + +void main() +{ + gl_FragColor = texture2D(tex, vTexCoord).rgba; +} +)"; + +static constexpr char YUV_FRAGMENT_SHADER[] = R"( +precision mediump float; + +varying vec2 vTexCoord; + +uniform sampler2D tex_y; +uniform sampler2D tex_u; +uniform sampler2D tex_v; + +void main() +{ + float y = texture2D(tex_y, vTexCoord).r * 1.16438; + float u = texture2D(tex_u, vTexCoord).r; + float v = texture2D(tex_v, vTexCoord).r; + gl_FragColor = vec4(y + 1.59603 * v - 0.874202, y - 0.391762 * u - 0.812968 * v + 0.531668, y + 2.01723 * u - 1.08563, 1.0); +} +)"; + +static constexpr float FULL_RECTANGLE_BUFFER[] = { + -1.0f, -1.0f, // Bottom left. + 1.0f, -1.0f, // Bottom right. + -1.0f, 1.0f, // Top left. + 1.0f, 1.0f, // Top right. +}; + +// Texture coordinates - (0, 0) is bottom-left and (1, 1) is top-right. +static constexpr float FULL_RECTANGLE_TEXTURE_BUFFER[] = { + 0.0f, 0.0f, // Bottom left. + 1.0f, 0.0f, // Bottom right. + 0.0f, 1.0f, // Top left. + 1.0f, 1.0f, // Top right. +}; + +} // namespace + +GlGenericDrawer::GlGenericDrawer() = default; + +GlGenericDrawer::~GlGenericDrawer() = default; + +void GlGenericDrawer::DrawOes( + int oesTextureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::OES, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, oesTextureId); + + // Draw the texture. + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the texture as a precaution. + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); +} + +void GlGenericDrawer::DrawRgb( + int textureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::RGB, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the texture. + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + + // Draw the texture. + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the texture as a precaution. + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GlGenericDrawer::DrawYuv( + std::vector yuvTextures, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, + int viewportY, int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::YUV, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the textures. + for (int i = 0; i < 3; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, yuvTextures[i]); + } + + // Draw the textures. + RTC_DLOG(LS_VERBOSE) << "view port: " << viewportX << ", " << viewportY << ", " << viewportWidth << ", " + << viewportHeight; + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the textures as a precaution. + for (int i = 0; i < 3; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +void GlGenericDrawer::PrepareShader( + ShaderType shaderType, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportWidth, + int viewportHeight) +{ + if (shaderType != currentShaderType_) { + // Allocate new shader. + currentShader_ = CreateShader(shaderType); + RTC_DCHECK(currentShader_); + currentShaderType_ = shaderType; + + currentShader_->Use(); + + // Set input texture units. + if (shaderType == ShaderType::YUV) { + currentShader_->SetInt("tex_y", 0); + currentShader_->SetInt("tex_u", 1); + currentShader_->SetInt("tex_v", 2); + } else { + currentShader_->SetInt("tex", 0); + } + + positionLocation_ = currentShader_->GetAttribLocation("position"); + textureLocation_ = currentShader_->GetAttribLocation("texCoord"); + texTransformLocation_ = currentShader_->GetUniformLocation("transform"); + } else { + // Same shader type as before, reuse exising shader. + currentShader_->Use(); + } + + // Upload the vertex coordinates. + glEnableVertexAttribArray(positionLocation_); + glVertexAttribPointer(positionLocation_, 2, GL_FLOAT, false, 0, FULL_RECTANGLE_BUFFER); + + // Upload the texture coordinates. + glEnableVertexAttribArray(textureLocation_); + glVertexAttribPointer(textureLocation_, 2, GL_FLOAT, false, 0, FULL_RECTANGLE_TEXTURE_BUFFER); + + // Upload the texture transformation matrix. + glUniformMatrix4fv(texTransformLocation_, 1, false, texMatrix.data()); +} + +std::unique_ptr GlGenericDrawer::CreateShader(ShaderType shaderType) +{ + auto shader = std::make_unique(); + switch (shaderType) { + case ShaderType::OES: + shader->Compile(DEFAULT_VERTEX_SHADER, OES_FRAGMENT_SHADER); + break; + case ShaderType::RGB: + shader->Compile(DEFAULT_VERTEX_SHADER, RGB_FRAGMENT_SHADER); + break; + case ShaderType::YUV: + shader->Compile(DEFAULT_VERTEX_SHADER, YUV_FRAGMENT_SHADER); + break; + default: + RTC_LOG(LS_ERROR) << "Unsupported shader type: " << shaderType; + return nullptr; + } + + return shader; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/gl_drawer.h b/sdk/ohos/src/ohos_webrtc/render/gl_drawer.h new file mode 100644 index 0000000000000000000000000000000000000000..8c2c4b021f6a2fe0c1ef0a6a51a3afc7ee2246e9 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/gl_drawer.h @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_GL_DRAWER_H +#define WEBRTC_RENDER_GL_DRAWER_H + +#include "render_common.h" +#include "gl_shader.h" + +namespace webrtc { + +class GlDrawer { +public: + virtual ~GlDrawer() = default; + + virtual void DrawOes( + int oesTextureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) = 0; + virtual void DrawRgb( + int textureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) = 0; + virtual void DrawYuv( + std::vector yuvTextures, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, + int viewportY, int viewportWidth, int viewportHeight) = 0; +}; + +class GlGenericDrawer : public GlDrawer { +public: + enum class ShaderType { UNKNOWN = -1, OES, RGB, YUV }; + + GlGenericDrawer(); + ~GlGenericDrawer() override; + + void DrawOes( + int oesTextureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) override; + void DrawRgb( + int textureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) override; + void DrawYuv( + std::vector yuvTextures, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, + int viewportY, int viewportWidth, int viewportHeight) override; + +protected: + void PrepareShader( + ShaderType shaderType, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportWidth, + int viewportHeight); + std::unique_ptr CreateShader(ShaderType shaderType); + +private: + ShaderType currentShaderType_{ShaderType::UNKNOWN}; + std::unique_ptr currentShader_; + int positionLocation_; + int textureLocation_; + int texTransformLocation_; +}; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_GL_DRAWER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/gl_shader.cpp b/sdk/ohos/src/ohos_webrtc/render/gl_shader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a2084012ce5d6202b38ad560effe1c10ce54150b --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/gl_shader.cpp @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gl_shader.h" + +#include + +#include "rtc_base/logging.h" + +namespace webrtc { + +GlShader::GlShader() = default; + +GlShader::~GlShader() +{ + if (id_ > 0) { + glDeleteProgram(id_); + id_ = 0; + } +} + +bool GlShader::Compile(const char* vertexShaderString, const char* fragmentShaderString) +{ + unsigned int vertex, fragment; + + // vertex shader + vertex = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex, 1, &vertexShaderString, NULL); + glCompileShader(vertex); + if (!CheckCompileErrors(vertex, "VERTEX")) { + return false; + } + + // fragment Shader + fragment = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment, 1, &fragmentShaderString, NULL); + glCompileShader(fragment); + if (!CheckCompileErrors(fragment, "FRAGMENT")) { + return false; + } + + // shader Program + id_ = glCreateProgram(); + glAttachShader(id_, vertex); + glAttachShader(id_, fragment); + glLinkProgram(id_); + if (!CheckCompileErrors(id_, "PROGRAM")) { + return false; + } + + glDeleteShader(vertex); + glDeleteShader(fragment); + + return true; +} + +void GlShader::Use() +{ + glUseProgram(id_); +} + +int GlShader::GetAttribLocation(const char* name) +{ + return glGetAttribLocation(id_, name); +} + +int GlShader::GetUniformLocation(const char* name) +{ + return glGetUniformLocation(id_, name); +} + +void GlShader::SetBool(const char* name, bool value) const +{ + glUniform1i(glGetUniformLocation(id_, name), static_cast(value)); +} + +void GlShader::SetInt(const char* name, int value) const +{ + glUniform1i(glGetUniformLocation(id_, name), value); +} + +void GlShader::SetFloat(const char* name, float value) const +{ + glUniform1f(glGetUniformLocation(id_, name), value); +} + +void GlShader::SetVector2f(const char* name, float x, float y) +{ + glUniform2f(glGetUniformLocation(id_, name), x, y); +} + +void GlShader::SetVector3f(const char* name, float x, float y, float z) +{ + glUniform3f(glGetUniformLocation(id_, name), x, y, z); +} + +void GlShader::SetVector4f(const char* name, float x, float y, float z, float w) +{ + glUniform4f(glGetUniformLocation(id_, name), x, y, z, w); +} + +void GlShader::SetMatrix4(const char* name, const std::array& matrix) +{ + glUniformMatrix4fv(glGetUniformLocation(id_, name), 1, false, matrix.data()); +} + +bool GlShader::CheckCompileErrors(unsigned int shader, const std::string& type) +{ + int success; + char infoLog[1024]; + if (type != "PROGRAM") { + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(shader, 1024, NULL, infoLog); + RTC_LOG(LS_ERROR) << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << ", " << infoLog; + return false; + } + } else { + glGetProgramiv(shader, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(shader, 1024, NULL, infoLog); + RTC_LOG(LS_ERROR) << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << ", " << infoLog; + return false; + } + } + + return true; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/gl_shader.h b/sdk/ohos/src/ohos_webrtc/render/gl_shader.h new file mode 100644 index 0000000000000000000000000000000000000000..9dd017f1792383f14b1da8811806bef9e64df52d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/gl_shader.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_GL_SHADER_H +#define WEBRTC_RENDER_GL_SHADER_H + +#include + +namespace webrtc { + +class GlShader { +public: + GlShader(); + ~GlShader(); + + bool Compile(const char* vertexShaderString, const char* fragmentShaderString); + + void Use(); + + int GetAttribLocation(const char* name); + int GetUniformLocation(const char* name); + + void SetBool(const char* name, bool value) const; + void SetInt(const char* name, int value) const; + void SetFloat(const char* name, float value) const; + void SetVector2f(const char* name, float x, float y); + void SetVector3f(const char* name, float x, float y, float z); + void SetVector4f(const char* name, float x, float y, float z, float w); + void SetMatrix4(const char* name, const std::array& matrix); + +private: + bool CheckCompileErrors(unsigned int shader, const std::string& type); + + unsigned int id_{0}; +}; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_GL_SHADER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.cpp b/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fac757effe71af5461e8b2afa50f6574c3708966 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.cpp @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_video_renderer.h" +#include "media_source.h" +#include "media_stream.h" +#include "media_stream_track.h" +#include "render/native_window_renderer_gl.h" +#include "render/native_window_renderer_raster.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace ohos; +using namespace adapter; +using namespace Napi; + +FunctionReference NapiNativeVideoRenderer::constructor_; + +void NapiNativeVideoRenderer::Init(Napi::Env env, Napi::Object exports) +{ + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiNativeVideoRenderer::GetSurfaceId>(kAttributeNameSurfaceId), + InstanceAccessor<&NapiNativeVideoRenderer::GetSharedContext>(kAttributeNameSharedContext), + InstanceAccessor<&NapiNativeVideoRenderer::GetVideoTrack>(kAttributeNameVideoTrack), + InstanceMethod<&NapiNativeVideoRenderer::SetVideoTrack>(kMethodNameSetVideoTrack), + InstanceMethod<&NapiNativeVideoRenderer::Init>(kMethodNameInit), + InstanceMethod<&NapiNativeVideoRenderer::Release>(kMethodNameRelease), + InstanceMethod<&NapiNativeVideoRenderer::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiNativeVideoRenderer::NapiNativeVideoRenderer(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +NapiNativeVideoRenderer::~NapiNativeVideoRenderer() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + RemoveSink(); +} + +Napi::Value NapiNativeVideoRenderer::GetSurfaceId(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (surfaceId_) { + return String::New(info.Env(), *surfaceId_); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeVideoRenderer::GetSharedContext(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (sharedContext_) { + return NapiEglContext::NewInstance(info.Env(), sharedContext_); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeVideoRenderer::GetVideoTrack(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return jsTrackRef_.IsEmpty() ? info.Env().Null() : jsTrackRef_.Value(); +} + +Napi::Value NapiNativeVideoRenderer::SetVideoTrack(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + return info.Env().Undefined(); + } + + if (info[0].IsNull()) { + RemoveSink(); + jsTrackRef_.Reset(); + return info.Env().Undefined(); + } + + if (!info[0].IsObject()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), Value()); + } + + auto jsTrack = info[0].As(); + auto napiTrack = NapiMediaStreamTrack::Unwrap(jsTrack); + if (!napiTrack) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), Value()); + } + + if (!napiTrack->IsVideoTrack()) { + NAPI_THROW(Error::New(info.Env(), "Invalid argument"), Value()); + } + + RemoveSink(); + jsTrackRef_ = Weak(jsTrack); + AddSink(); + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeVideoRenderer::Init(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "The first argument is not string"), info.Env().Undefined()); + } + + surfaceId_ = info[0].As().Utf8Value(); + + if (info.Length() > 1 && info[1].IsObject()) { + auto jsSharedContext = info[1].As(); + auto napiSharedContext = NapiEglContext::Unwrap(jsSharedContext); + if (napiSharedContext) { + sharedContext_ = napiSharedContext->Get(); + } + } else { + sharedContext_ = EglEnv::GetDefault().GetContext(); + } + + auto nativeWindow = ohos::NativeWindow::CreateFromSurfaceId(std::stoull(*surfaceId_)); + if (nativeWindow.IsEmpty()) { + NAPI_THROW(Error::New(info.Env(), "Failed to create native window"), info.Env().Undefined()); + } + + renderer_ = NativeWindowRendererGl::Create(nativeWindow, sharedContext_, "native-window-renderer"); + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeVideoRenderer::Release(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RemoveSink(); + + surfaceId_.reset(); + renderer_.reset(); + jsTrackRef_.Reset(); + + return info.Env().Undefined(); +} + +Napi::Value NapiNativeVideoRenderer::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiNativeVideoRenderer")); +#endif + + return json; +} + +void NapiNativeVideoRenderer::AddSink() +{ + if (!renderer_) { + RTC_DLOG(LS_VERBOSE) << "renderer is null"; + return; + } + + if (jsTrackRef_.IsEmpty()) { + RTC_DLOG(LS_VERBOSE) << "track ref is empty"; + return; + } + + auto jsTrack = jsTrackRef_.Value(); + if (jsTrack.IsEmpty()) { + RTC_DLOG(LS_VERBOSE) << "track is empty"; + return; + } + + auto napiTrack = NapiMediaStreamTrack::Unwrap(jsTrack); + napiTrack->AddSink(renderer_.get()); +} + +void NapiNativeVideoRenderer::RemoveSink() +{ + if (!renderer_) { + RTC_DLOG(LS_VERBOSE) << "renderer is null"; + return; + } + + if (jsTrackRef_.IsEmpty()) { + RTC_DLOG(LS_VERBOSE) << "track ref is empty"; + return; + } + + auto jsTrack = jsTrackRef_.Value(); + if (jsTrack.IsEmpty()) { + RTC_DLOG(LS_VERBOSE) << "track is empty"; + return; + } + + auto napiTrack = NapiMediaStreamTrack::Unwrap(jsTrack); + napiTrack->RemoveSink(renderer_.get()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.h b/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.h new file mode 100644 index 0000000000000000000000000000000000000000..c211b3b41fdd6144b9d2fa351acfeb81c5e2723b --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_video_renderer.h @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_NATIVE_VIDEO_RENDERER_H +#define WEBRTC_NATIVE_VIDEO_RENDERER_H + +#include "native_window_renderer.h" +#include "../media_stream_track.h" +#include "../render/egl_env.h" +#include "../utils/marcos.h" + +#include +#include + +#include "napi.h" + +namespace webrtc { + +class NapiNativeVideoRenderer : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(NativeVideoRenderer); + NAPI_ATTRIBUTE_NAME_DECLARE(SurfaceId, surfaceId); + NAPI_ATTRIBUTE_NAME_DECLARE(VideoTrack, videoTrack); + NAPI_ATTRIBUTE_NAME_DECLARE(SharedContext, sharedContext); + NAPI_METHOD_NAME_DECLARE(Init, init); + NAPI_METHOD_NAME_DECLARE(SetVideoTrack, setVideoTrack); + NAPI_METHOD_NAME_DECLARE(Release, release); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + + static void Init(Napi::Env env, Napi::Object exports); + +protected: + friend class ObjectWrap; + explicit NapiNativeVideoRenderer(const Napi::CallbackInfo& info); + ~NapiNativeVideoRenderer() override; + + Napi::Value GetSurfaceId(const Napi::CallbackInfo& info); + Napi::Value GetSharedContext(const Napi::CallbackInfo& info); + Napi::Value GetVideoTrack(const Napi::CallbackInfo& info); + Napi::Value SetVideoTrack(const Napi::CallbackInfo& info); + Napi::Value Init(const Napi::CallbackInfo& info); + Napi::Value Release(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + + void AddSink(); + void RemoveSink(); + +private: + static Napi::FunctionReference constructor_; + + std::optional surfaceId_; + std::shared_ptr sharedContext_; + + // weak reference + Napi::ObjectReference jsTrackRef_; + + std::unique_ptr renderer_; +}; + +} // namespace webrtc + +#endif // WEBRTC_NATIVE_VIDEO_RENDERER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.cpp b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c129006e20786d0bb9a4ff959be0963d8e06014c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.cpp @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_window_renderer.h" + +namespace webrtc { +namespace adapter { + +NativeWindowRenderer::~NativeWindowRenderer() = default; + +uint64_t NativeWindowRenderer::GetSurfaceId() const +{ + return window_.GetSurfaceId(); +} + +NativeWindowRenderer::NativeWindowRenderer(ohos::NativeWindow window) : window_(std::move(window)) {} + +} // namespace adapter +} // namespace webrtc \ No newline at end of file diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.h b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.h new file mode 100644 index 0000000000000000000000000000000000000000..a547b9c1fa80848059b641b1ef0a97279ecc61a3 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_H +#define WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_H + +#include "../helper/native_window.h" + +#include "api/video/video_frame.h" +#include "api/video/video_sink_interface.h" + +namespace webrtc { +namespace adapter { + +class NativeWindowRenderer : public rtc::VideoSinkInterface { +public: + virtual ~NativeWindowRenderer(); + + uint64_t GetSurfaceId() const; + +protected: + explicit NativeWindowRenderer(ohos::NativeWindow window); + +protected: + ohos::NativeWindow window_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.cpp b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b599708305a0cf2d4239e6bbfe5088c4fbadf242 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.cpp @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_window_renderer_gl.h" +#include "egl_config_attributes.h" + +#include +#include +#include + +#include "render/gl_drawer.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace adapter { + +std::unique_ptr +NativeWindowRendererGl::Create(ohos::NativeWindow window, std::shared_ptr sharedContext) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (window.IsEmpty()) { + return nullptr; + } + + return std::unique_ptr( + new NativeWindowRendererGl(std::move(window), sharedContext, "native-window-renderer")); +} + +std::unique_ptr NativeWindowRendererGl::Create( + ohos::NativeWindow window, std::shared_ptr sharedContext, const std::string& threadName) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (window.IsEmpty()) { + return nullptr; + } + + return std::unique_ptr( + new NativeWindowRendererGl(std::move(window), sharedContext, threadName)); +} + +NativeWindowRendererGl::NativeWindowRendererGl( + ohos::NativeWindow window, std::shared_ptr sharedContext, const std::string& threadName) + : NativeWindowRenderer(std::move(window)), + thread_(rtc::Thread::Create()), + textureDrawer_(std::make_unique()), + videoFrameDrawer_(std::make_unique()) +{ + // [height] is before [width] + int32_t ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_BUFFER_GEOMETRY, &height_, &width_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get buffer geometry: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window geometry: " << width_ << "x" << height_; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_USAGE, &usage_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get usage: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window usage: " << usage_; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_FORMAT, &format_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get format: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window format: " << format_; + + int32_t stride = 0; + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_STRIDE, &stride); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get stride: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window stride: " << stride; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_TRANSFORM, &transform_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get transform: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window transform: " << transform_; + + thread_->SetName(threadName, this); + thread_->Start(); + thread_->BlockingCall([this, sharedContext] { + eglEnv_ = EglEnv::Create(sharedContext, EglConfigAttributes::RGBA); + eglEnv_->CreateWindowSurface(window_); + eglEnv_->MakeCurrent(); + }); +} + +NativeWindowRendererGl::~NativeWindowRendererGl() +{ + thread_->Stop(); +} + +void NativeWindowRendererGl::OnFrame(const VideoFrame& frame) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " this=" << this; + RTC_DLOG(LS_VERBOSE) << "render frame, id=" << frame.id() << " size=" << frame.width() << "x" << frame.height() + << ", timestamp=" << frame.timestamp_us(); + + if (!frame.video_frame_buffer()) { + RTC_LOG(LS_ERROR) << "Buffer is null"; + return; + } + + thread_->PostTask([this, frame = frame] { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + drawMatrix_ = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + videoFrameDrawer_->DrawFrame( + frame, *textureDrawer_, drawMatrix_, 0, 0, eglEnv_->GetSurfaceWidth(), eglEnv_->GetSurfaceHeight()); + eglEnv_->SwapBuffers(); + }); +} + +void NativeWindowRendererGl::OnDiscardedFrame() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NativeWindowRendererGl::OnConstraintsChanged(const webrtc::VideoTrackSourceConstraints& constraints) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.h b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.h new file mode 100644 index 0000000000000000000000000000000000000000..60e7693acd5e9d6092ce25783e83552bbe160fe3 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_gl.h @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_GL_H +#define WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_GL_H + +#include "native_window_renderer.h" +#include "egl_env.h" +#include "render/gl_shader.h" +#include "video_frame_drawer.h" +#include "../video/texture_buffer.h" +#include "../helper/native_window.h" + +#include +#include + +#include "rtc_base/thread.h" + +namespace webrtc { +namespace adapter { + +class NativeWindowRendererGl : public NativeWindowRenderer { +public: + static std::unique_ptr + Create(ohos::NativeWindow window, std::shared_ptr sharedContext); + static std::unique_ptr + Create(ohos::NativeWindow window, std::shared_ptr sharedContext, const std::string& threadName); + + ~NativeWindowRendererGl() override; + +protected: + NativeWindowRendererGl(ohos::NativeWindow window, std::shared_ptr sharedContext, const std::string& threadName); + + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + void OnConstraintsChanged(const webrtc::VideoTrackSourceConstraints& constraints) override; + +private: + int32_t width_{}; + int32_t height_{}; + int32_t format_{}; + int32_t transform_{}; + uint64_t usage_{}; + + std::unique_ptr thread_; + + std::unique_ptr eglEnv_; + + Matrix4f drawMatrix_; + std::unique_ptr textureDrawer_; + std::unique_ptr videoFrameDrawer_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_GL_H diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.cpp b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4bd73a9cc67dc4acae41a0798dc83ae851987fed --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.cpp @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "native_window_renderer_raster.h" + +#include "api/video/i420_buffer.h" +#include "rtc_base/logging.h" +#include "libyuv.h" + +namespace webrtc { +namespace adapter { + +std::unique_ptr NativeWindowRendererRaster::Create(ohos::NativeWindow window) +{ + if (window.IsEmpty()) { + return nullptr; + } + + return std::unique_ptr(new NativeWindowRendererRaster(std::move(window))); +} + +NativeWindowRendererRaster::NativeWindowRendererRaster(ohos::NativeWindow window) + : NativeWindowRenderer(std::move(window)), thread_(rtc::Thread::Create()) +{ + // [height] is before [width] + int32_t ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_BUFFER_GEOMETRY, &height_, &width_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get buffer geometry: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window geometry: " << width_ << "x" << height_; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_USAGE, &usage_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get usage: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window usage: " << usage_; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_FORMAT, &format_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get format: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window format: " << format_; + + int32_t stride = 0; + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_STRIDE, &stride); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get stride: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window stride: " << stride; + + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), GET_TRANSFORM, &transform_); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to get transform: " << ret; + } + RTC_DLOG(LS_VERBOSE) << "Window transform: " << transform_; + + // set + ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), SET_USAGE, usage_ | NATIVEBUFFER_USAGE_CPU_WRITE); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set usage: " << ret; + } + + ret = OH_NativeWindow_NativeWindowSetScalingModeV2(window_.Raw(), OH_SCALING_MODE_SCALE_FIT_V2); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set scale mode: " << ret; + } + + thread_->SetName("window-renderer-thread", this); + thread_->Start(); +} + +NativeWindowRendererRaster::~NativeWindowRendererRaster() +{ + thread_->Stop(); +} + +void NativeWindowRendererRaster::OnFrame(const VideoFrame& frame) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " this=" << this; + RTC_DLOG(LS_VERBOSE) << "render frame, id=" << frame.id() << " size=" << frame.width() << "x" << frame.height() + << ", timestamp=" << frame.timestamp_us(); + + if (!frame.video_frame_buffer()) { + RTC_LOG(LS_ERROR) << "Buffer is null"; + return; + } + + thread_->PostTask([this, frame] { + RenderByteBuffer(frame.video_frame_buffer()->ToI420()); + }); +} + +void NativeWindowRendererRaster::OnDiscardedFrame() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NativeWindowRendererRaster::OnConstraintsChanged(const webrtc::VideoTrackSourceConstraints& constraints) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void NativeWindowRendererRaster::RenderByteBuffer(rtc::scoped_refptr buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " enter, this=" << this; + + if (!buffer) { + RTC_LOG(LS_ERROR) << "Buffer is nullptr"; + return; + } + + if (format_ != NATIVEBUFFER_PIXEL_FMT_RGBA_8888) { + RTC_DLOG(LS_VERBOSE) << "Set format"; + int32_t ret = + OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), SET_FORMAT, NATIVEBUFFER_PIXEL_FMT_RGBA_8888); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set format: " << ret; + return; + } else { + format_ = NATIVEBUFFER_PIXEL_FMT_RGBA_8888; + } + } + + int width = buffer->width(); + int height = buffer->height(); + + if (width_ != width || height_ != height) { + RTC_DLOG(LS_VERBOSE) << "Set buffer geometry: " << width << "x" << height; + int32_t ret = OH_NativeWindow_NativeWindowHandleOpt(window_.Raw(), SET_BUFFER_GEOMETRY, width, height); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set buffer geometry: " << ret; + return; + } else { + width_ = width; + height_ = height; + } + } + + auto windowBuffer = window_.RequestBuffer(true); + auto dstBuffer = ohos::NativeBuffer::From(windowBuffer.Raw()); + auto dstConfig = dstBuffer.GetConfig(); + RTC_DLOG(LS_VERBOSE) << "dst buffer config: " << dstConfig.width << ", " << dstConfig.height << ", " + << dstConfig.format << ", " << dstConfig.stride << ", " << dstConfig.usage; + + auto dstAddr = dstBuffer.Map(); + if (!dstAddr) { + RTC_LOG(LS_ERROR) << "Failed to map dstBuffer"; + window_.AbortBuffer(windowBuffer.Raw()); + return; + } + + if (dstConfig.format != NATIVEBUFFER_PIXEL_FMT_RGBA_8888) { + RTC_LOG(LS_ERROR) << "Window buffer format is not rgba"; + window_.AbortBuffer(windowBuffer.Raw()); + return; + } + + int ret = libyuv::I420ToABGR( + buffer->DataY(), buffer->StrideY(), buffer->DataU(), buffer->StrideU(), buffer->DataV(), buffer->StrideV(), + (uint8_t*)dstAddr, width * 4, width, height); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to convert i420 to rgba: " << ret; + window_.AbortBuffer(windowBuffer.Raw()); + return; + } + + window_.FlushBuffer(windowBuffer.Raw()); + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " exit"; +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.h b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.h new file mode 100644 index 0000000000000000000000000000000000000000..944ee128cd8a87cd9cd1f918ba0b5d65d610ba1f --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/native_window_renderer_raster.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_RASTER_H +#define WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_RASTER_H + +#include "native_window_renderer.h" +#include "../helper/native_buffer.h" + +#include + +#include "rtc_base/thread.h" + +namespace webrtc { +namespace adapter { + +class NativeWindowRendererRaster : public NativeWindowRenderer { +public: + static std::unique_ptr Create(ohos::NativeWindow window); + + ~NativeWindowRendererRaster() override; + +protected: + explicit NativeWindowRendererRaster(ohos::NativeWindow window); + + void OnFrame(const VideoFrame& frame) override; + void OnDiscardedFrame() override; + void OnConstraintsChanged(const webrtc::VideoTrackSourceConstraints& constraints) override; + + void RenderByteBuffer(rtc::scoped_refptr buffer); + +private: + int32_t width_{}; + int32_t height_{}; + int32_t format_{}; + int32_t transform_{}; + uint64_t usage_{}; + + std::unique_ptr thread_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_RENDER_NATIVE_WINDOW_RENDERER_RASTER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/render_common.h b/sdk/ohos/src/ohos_webrtc/render/render_common.h new file mode 100644 index 0000000000000000000000000000000000000000..8b48ca5724fd1289d88434831c8972baca975980 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/render_common.h @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_RENDER_COMMON_H +#define WEBRTC_RENDER_RENDER_COMMON_H + +#include + +namespace webrtc { + +using Matrix4f = std::array; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_RENDER_COMMON_H diff --git a/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.cpp b/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5e8a7f7c6528aeb9837670c18de49d65e4bd4301 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.cpp @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gl_drawer.h" +#include "video_frame_drawer.h" + +#include "rtc_base/logging.h" + +#include + +namespace webrtc { + +void VideoFrameDrawer::DrawFrame(const VideoFrame& frame, GlDrawer& drawer, const Matrix4f& texMatrix) +{ + DrawFrame(frame, drawer, texMatrix, 0, 0, frame.width(), frame.height()); +} + +void VideoFrameDrawer::DrawFrame( + const VideoFrame& frame, GlDrawer& drawer, const Matrix4f& texMatrix, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (frame.is_texture()) { + auto buffer = static_cast(frame.video_frame_buffer().get()); + DrawTexture( + rtc::scoped_refptr(buffer), drawer, texMatrix, frame.width(), frame.width(), viewportX, + viewportY, viewportWidth, viewportHeight); + } else { + if (yuvTextures_.size() == 0) { + yuvTextures_.resize(3); + + glGenTextures(3, yuvTextures_.data()); + for (uint32_t i = 0; i < yuvTextures_.size(); i++) { + glBindTexture(GL_TEXTURE_2D, yuvTextures_[i]); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + } + + auto buffer = frame.video_frame_buffer()->ToI420(); + + const uint8_t* planes[] = {buffer->DataY(), buffer->DataU(), buffer->DataV()}; + int planeWidths[] = {buffer->width(), buffer->width() / 2, buffer->width() / 2}; + int planeHeights[] = {buffer->height(), buffer->height() / 2, buffer->height() / 2}; + + for (uint32_t i = 0; i < 3; i++) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, yuvTextures_[i]); + + glTexImage2D( + GL_TEXTURE_2D, 0, GL_LUMINANCE, planeWidths[i], planeHeights[i], 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, + planes[i]); + } + + drawer.DrawYuv( + yuvTextures_, texMatrix, frame.width(), frame.height(), viewportX, viewportY, viewportWidth, + viewportHeight); + } + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +void VideoFrameDrawer::DrawTexture( + rtc::scoped_refptr buffer, GlDrawer& drawer, const Matrix4f& texMatrix, int frameWidth, + int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!buffer) { + RTC_LOG(LS_ERROR) << "Buffer is null"; + return; + } + + auto textureData = buffer->GetTexture(); + if (!textureData) { + RTC_LOG(LS_ERROR) << "Buffer is released"; + return; + } + + textureData->Lock(); + + switch (textureData->GetType()) { + case TextureData::Type::OES: + drawer.DrawOes( + textureData->GetId(), texMatrix, frameWidth, frameHeight, viewportX, viewportY, viewportWidth, + viewportHeight); + break; + case TextureData::Type::RGB: + drawer.DrawRgb( + textureData->GetId(), texMatrix, frameWidth, frameHeight, viewportX, viewportY, viewportWidth, + viewportHeight); + break; + default: + RTC_LOG(LS_ERROR) << "Unknown texture type: " << textureData->GetType(); + break; + } + + textureData->Unlock(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.h b/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.h new file mode 100644 index 0000000000000000000000000000000000000000..97f73cb023f2474efe8878af5482dbdb1f2c40e4 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/video_frame_drawer.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_VIDEO_FRAME_DRAWER_H +#define WEBRTC_RENDER_VIDEO_FRAME_DRAWER_H + +#include "gl_drawer.h" +#include "../video/texture_buffer.h" + +#include "api/scoped_refptr.h" +#include "api/video/video_frame.h" +#include "rtc_base/thread.h" + +namespace webrtc { + +class VideoFrameDrawer { +public: + void DrawFrame(const VideoFrame& frame, GlDrawer& drawer, const Matrix4f& texMatrix); + void DrawFrame( + const VideoFrame& frame, GlDrawer& drawer, const Matrix4f& texMatrix, int viewportX, int viewportY, + int viewportWidth, int viewportHeight); + + void DrawTexture( + rtc::scoped_refptr buffer, GlDrawer& drawer, const Matrix4f& texMatrix, int frameWidth, + int frameHeight, int viewportX, int viewportY, int viewportWidth, int viewportHeight); + +private: + std::vector yuvTextures_; +}; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_VIDEO_FRAME_DRAWER_H diff --git a/sdk/ohos/src/ohos_webrtc/render/yuv_converter.cpp b/sdk/ohos/src/ohos_webrtc/render/yuv_converter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5446679461b3798a1bfa954d3e4d4803cb08cf2d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/yuv_converter.cpp @@ -0,0 +1,590 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "yuv_converter.h" + +#include "api/video/i420_buffer.h" +#include "rtc_base/logging.h" +#include "libyuv.h" + +#include +#include +#include + +namespace webrtc { + +namespace { + +static constexpr char DEFAULT_VERTEX_SHADER[] = R"( +attribute vec3 position; +attribute vec2 texCoord; +varying vec2 vTexCoord; +uniform mat4 transform; + +void main() +{ + gl_Position = transform * vec4(position, 1.0); + vTexCoord = texCoord; +} +)"; + +static constexpr char OES_FRAGMENT_SHADER[] = R"( +#extension GL_OES_EGL_image_external : require +precision mediump float; +varying vec2 vTexCoord; +uniform samplerExternalOES tex; +uniform vec2 xUnit; +// Color conversion coefficients, including constant term +uniform vec4 coefficients; + +void main() { + gl_FragColor.r = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord - 1.5 * xUnit).rgb); + gl_FragColor.g = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord - 0.5 * xUnit).rgb); + gl_FragColor.b = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord + 0.5 * xUnit).rgb); + gl_FragColor.a = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord + 1.5 * xUnit).rgb); +} +)"; + +static constexpr char RGB_FRAGMENT_SHADER[] = R"( +precision mediump float; +varying vec2 vTexCoord; +uniform sampler2D tex; +uniform vec2 xUnit; +// Color conversion coefficients, including constant term +uniform vec4 coefficients; + +void main() { + gl_FragColor.r = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord - 1.5 * xUnit).rgb); + gl_FragColor.g = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord - 0.5 * xUnit).rgb); + gl_FragColor.b = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord + 0.5 * xUnit).rgb); + gl_FragColor.a = coefficients.a + dot(coefficients.rgb, texture2D(tex, vTexCoord + 1.5 * xUnit).rgb); +} +)"; + +static constexpr char YUV_FRAGMENT_SHADER[] = R"( +precision mediump float; +varying vec2 vTexCoord; +uniform sampler2D tex_y; +uniform sampler2D tex_u; +uniform sampler2D tex_v; +uniform vec2 xUnit; +// Color conversion coefficients, including constant term +uniform vec4 coefficients; + +vec4 sample(vec2 p) { + float y = texture2D(tex_y, p).r * 1.16438; + float u = texture2D(tex_u, p).r; + float v = texture2D(tex_v, p).r; + return vec4(y + 1.59603 * v - 0.874202, y - 0.391762 * u - 0.812968 * v + 0.531668, y + 2.01723 * u - 1.08563, 1); +} + +void main() { + gl_FragColor.r = coefficients.a + dot(coefficients.rgb, sample(vTexCoord - 1.5 * xUnit).rgb); + gl_FragColor.g = coefficients.a + dot(coefficients.rgb, sample(vTexCoord - 0.5 * xUnit).rgb); + gl_FragColor.b = coefficients.a + dot(coefficients.rgb, sample(vTexCoord + 0.5 * xUnit).rgb); + gl_FragColor.a = coefficients.a + dot(coefficients.rgb, sample(vTexCoord + 1.5 * xUnit).rgb); +} +)"; + +static constexpr float FULL_RECTANGLE_BUFFER[] = { + -1.0f, -1.0f, // Bottom left + 1.0f, -1.0f, // Bottom right + -1.0f, 1.0f, // Top left + 1.0f, 1.0f, // Top right +}; + +// Texture coordinates - (0, 0) is bottom-left and (1, 1) is top-right +static constexpr float FULL_RECTANGLE_TEXTURE_BUFFER[] = { + 0.0f, 0.0f, // Bottom left + 1.0f, 0.0f, // Bottom right + 0.0f, 1.0f, // Top left + 1.0f, 1.0f, // Top right +}; + +} // namespace + +class GlConverterDrawer : public GlDrawer { +public: + enum class ShaderType { UNKNOWN = -1, OES, RGB, YUV }; + + GlConverterDrawer() = default; + ~GlConverterDrawer() override = default; + + void DrawOes( + int oesTextureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) override; + void DrawRgb( + int textureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) override; + void DrawYuv( + std::vector yuvTextures, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, + int viewportY, int viewportWidth, int viewportHeight) override; + + void SetStepSize(float stepSize); + void SetCoefficients(std::array coefficients); + +protected: + void PrepareShader( + ShaderType shaderType, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportWidth, + int viewportHeight); + std::unique_ptr CreateShader(ShaderType shaderType); + +private: + ShaderType currentShaderType_{ShaderType::UNKNOWN}; + std::unique_ptr currentShader_; + int positionLocation_; + int textureLocation_; + int texTransformLocation_; + int xUnitLocation_; + int coefficientsLocation_; + float stepSize_; + std::array coefficients_; +}; + +void GlConverterDrawer::DrawOes( + int oesTextureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::OES, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, oesTextureId); + + // Draw the texture + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the texture as a precaution + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); +} + +void GlConverterDrawer::DrawRgb( + int textureId, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, int viewportY, + int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::RGB, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + + // Draw the texture + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the texture as a precaution + glBindTexture(GL_TEXTURE_2D, 0); +} + +void GlConverterDrawer::DrawYuv( + std::vector yuvTextures, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportX, + int viewportY, int viewportWidth, int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + PrepareShader(ShaderType::YUV, texMatrix, frameWidth, frameHeight, viewportWidth, viewportHeight); + + // Bind the textures + for (int i = 0; i < 3; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, yuvTextures[i]); + } + + // Draw the textures + RTC_DLOG(LS_VERBOSE) << "view port: " << viewportX << ", " << viewportY << ", " << viewportWidth << ", " + << viewportHeight; + glViewport(viewportX, viewportY, viewportWidth, viewportHeight); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // Unbind the textures as a precaution + for (int i = 0; i < 3; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(GL_TEXTURE_2D, 0); + } +} + +void GlConverterDrawer::SetStepSize(float stepSize) +{ + stepSize_ = stepSize; +} + +void GlConverterDrawer::SetCoefficients(std::array coefficients) +{ + coefficients_ = coefficients; +} + +void GlConverterDrawer::PrepareShader( + ShaderType shaderType, const Matrix4f& texMatrix, int frameWidth, int frameHeight, int viewportWidth, + int viewportHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + (void)frameHeight; + (void)viewportWidth; + (void)viewportHeight; + + if (shaderType != currentShaderType_) { + currentShader_ = CreateShader(shaderType); + RTC_DCHECK(currentShader_); + currentShaderType_ = shaderType; + + currentShader_->Use(); + + if (shaderType == ShaderType::YUV) { + currentShader_->SetInt("tex_y", 0); + currentShader_->SetInt("tex_u", 1); + currentShader_->SetInt("tex_v", 2); + } else { + currentShader_->SetInt("tex", 0); + } + + positionLocation_ = currentShader_->GetAttribLocation("position"); + textureLocation_ = currentShader_->GetAttribLocation("texCoord"); + texTransformLocation_ = currentShader_->GetUniformLocation("transform"); + xUnitLocation_ = currentShader_->GetUniformLocation("xUnit"); + coefficientsLocation_ = currentShader_->GetUniformLocation("coefficients"); + } else { + // Same shader type as before, reuse exising shader + currentShader_->Use(); + } + + // Upload the vertex coordinates + glEnableVertexAttribArray(positionLocation_); + glVertexAttribPointer(positionLocation_, 2, GL_FLOAT, false, 0, FULL_RECTANGLE_BUFFER); + + // Upload the texture coordinates + glEnableVertexAttribArray(textureLocation_); + glVertexAttribPointer(textureLocation_, 2, GL_FLOAT, false, 0, FULL_RECTANGLE_TEXTURE_BUFFER); + + // Upload the texture transformation matrix + glUniformMatrix4fv(texTransformLocation_, 1, false, texMatrix.data()); + + glUniform4fv(coefficientsLocation_, 1, coefficients_.data()); + glUniform2f(xUnitLocation_, stepSize_ * texMatrix[0] / frameWidth, stepSize_ * texMatrix[1] / frameWidth); +} + +std::unique_ptr GlConverterDrawer::CreateShader(ShaderType shaderType) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto shader = std::make_unique(); + switch (shaderType) { + case ShaderType::OES: + shader->Compile(DEFAULT_VERTEX_SHADER, OES_FRAGMENT_SHADER); + break; + case ShaderType::RGB: + shader->Compile(DEFAULT_VERTEX_SHADER, RGB_FRAGMENT_SHADER); + break; + case ShaderType::YUV: + shader->Compile(DEFAULT_VERTEX_SHADER, YUV_FRAGMENT_SHADER); + break; + default: + RTC_LOG(LS_ERROR) << "Unsupported shader type: " << shaderType; + return nullptr; + } + + return shader; +} + +class LocalI420Buffer : public I420BufferInterface { +public: + static rtc::scoped_refptr Wrap( + std::unique_ptr data, int width, int height, const uint8_t* dataY, int strideY, + const uint8_t* dataU, int strideU, const uint8_t* dataV, int strideV); + + int width() const override; + int height() const override; + + const uint8_t* DataY() const override; + const uint8_t* DataU() const override; + const uint8_t* DataV() const override; + + int StrideY() const override; + int StrideU() const override; + int StrideV() const override; + + rtc::scoped_refptr CropAndScale( + int offset_x, int offset_y, int crop_width, int crop_height, int scaled_width, int scaled_height) override; + +protected: + LocalI420Buffer( + std::unique_ptr data, int width, int height, const uint8_t* dataY, int strideY, + const uint8_t* dataU, int strideU, const uint8_t* dataV, int strideV); + + ~LocalI420Buffer() override = default; + +private: + const std::unique_ptr data_; + const int width_; + const int height_; + const uint8_t* dataY_; + const uint8_t* dataU_; + const uint8_t* dataV_; + const int strideY_; + const int strideU_; + const int strideV_; +}; + +rtc::scoped_refptr LocalI420Buffer::Wrap( + std::unique_ptr data, int width, int height, const uint8_t* dataY, int strideY, + const uint8_t* dataU, int strideU, const uint8_t* dataV, int strideV) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (dataY == nullptr || dataU == nullptr || dataV == nullptr) { + RTC_LOG(LS_ERROR) << "Data buffers cannot be null"; + return nullptr; + } + + return rtc::make_ref_counted( + std::move(data), width, height, dataY, strideY, dataU, strideU, dataV, strideV); +} + +LocalI420Buffer::LocalI420Buffer( + std::unique_ptr data, int width, int height, const uint8_t* dataY, int strideY, + const uint8_t* dataU, int strideU, const uint8_t* dataV, int strideV) + : data_(std::move(data)), + width_(width), + height_(height), + dataY_(dataY), + dataU_(dataU), + dataV_(dataV), + strideY_(strideY), + strideU_(strideU), + strideV_(strideV) +{ +} + +int LocalI420Buffer::width() const +{ + return width_; +} + +int LocalI420Buffer::height() const +{ + return height_; +} + +const uint8_t* LocalI420Buffer::DataY() const +{ + return dataY_; +} + +const uint8_t* LocalI420Buffer::DataU() const +{ + return dataU_; +} + +const uint8_t* LocalI420Buffer::DataV() const +{ + return dataV_; +} + +int LocalI420Buffer::StrideY() const +{ + return strideY_; +} + +int LocalI420Buffer::StrideU() const +{ + return strideU_; +} + +int LocalI420Buffer::StrideV() const +{ + return strideV_; +} + +rtc::scoped_refptr LocalI420Buffer::CropAndScale( + int offset_x, int offset_y, int crop_width, int crop_height, int scaled_width, int scaled_height) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + const uint8_t* srcY = DataY() + offset_y * StrideY() + offset_x; + const uint8_t* srcU = DataU() + offset_y / 2 * StrideU() + offset_x / 2; + const uint8_t* srcV = DataV() + offset_y / 2 * StrideV() + offset_x / 2; + + auto newBuffer = I420Buffer::Create(scaled_width, scaled_height); + + if (crop_width == scaled_width && crop_height == scaled_height) { + bool ret = libyuv::I420Copy( + srcY, StrideY(), srcU, StrideU(), srcV, StrideV(), newBuffer->MutableDataY(), newBuffer->StrideY(), + newBuffer->MutableDataU(), newBuffer->StrideU(), newBuffer->MutableDataV(), newBuffer->StrideV(), + scaled_width, scaled_height); + RTC_DCHECK_EQ(ret, 0) << "I420Copy failed"; + } else { + bool ret = libyuv::I420Scale( + srcY, StrideY(), srcU, StrideU(), srcV, StrideV(), crop_width, crop_height, newBuffer->MutableDataY(), + newBuffer->StrideY(), newBuffer->MutableDataU(), newBuffer->StrideU(), newBuffer->MutableDataV(), + newBuffer->StrideV(), scaled_width, scaled_height, libyuv::kFilterBox); + RTC_DCHECK_EQ(ret, 0) << "I420Scale failed"; + } + + return newBuffer; +} + +YuvConverter::YuvConverter() + : drawer_(std::make_unique()), frameDrawer_(std::make_unique()) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; +} + +YuvConverter::~YuvConverter() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + drawer_.reset(); + frameDrawer_.reset(); + glDeleteTextures(1, &textureId_); + textureId_ = 0; + glDeleteFramebuffers(1, &frameBufferId_); + frameBufferId_ = 0; + frameBufferWidth_ = 0; + frameBufferHeight_ = 0; +} + +rtc::scoped_refptr YuvConverter::Convert(rtc::scoped_refptr textureBuffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!textureBuffer) { + RTC_LOG(LS_ERROR) << "Texture buffer is null"; + return nullptr; + } + + int frameWidth = textureBuffer->width(); + int frameHeight = textureBuffer->height(); + int stride = ((frameWidth + 7) / 8) * 8; + int uvHeight = (frameHeight + 1) / 2; + int totalHeight = frameHeight + uvHeight; + // Viewport width is divided by four since we are squeezing in four color bytes in each RGBA pixel + int viewportWidth = stride / 4; + + if (!PrepareFrameBuffer(viewportWidth, totalHeight)) { + return nullptr; + } + + glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId_); + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + RTC_LOG(LS_ERROR) << "Failed to call glBindFramebuffer: " << error; + return nullptr; + } + + Matrix4f renderMatrix = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + + // Draw Y + drawer_->SetStepSize(1.0f); + drawer_->SetCoefficients({0.256788f, 0.504129f, 0.0979059f, 0.0627451f}); + frameDrawer_->DrawTexture( + textureBuffer, *drawer_, renderMatrix, frameWidth, frameHeight, 0, 0, viewportWidth, frameHeight); + + // Draw U + drawer_->SetStepSize(2.0f); + drawer_->SetCoefficients({-0.148223f, -0.290993f, 0.439216f, 0.501961f}); + frameDrawer_->DrawTexture( + textureBuffer, *drawer_, renderMatrix, frameWidth, frameHeight, 0, frameHeight, viewportWidth / 2, uvHeight); + + // Draw V + drawer_->SetStepSize(2.0f); + drawer_->SetCoefficients({0.439216f, -0.367788f, -0.0714274f, 0.501961f}); + frameDrawer_->DrawTexture( + textureBuffer, *drawer_, renderMatrix, frameWidth, frameHeight, viewportWidth / 2, frameHeight, + viewportWidth / 2, uvHeight); + + std::unique_ptr i420Buffer( + static_cast(AlignedMalloc(stride * totalHeight, 8))); + glReadPixels(0, 0, frameBufferWidth_, frameBufferHeight_, GL_RGBA, GL_UNSIGNED_BYTE, i420Buffer.get()); + error = glGetError(); + if (error != GL_NO_ERROR) { + RTC_LOG(LS_ERROR) << "Failed to call glReadPixels: " << error; + return nullptr; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + int yPos = 0; + int uPos = yPos + stride * frameHeight; + int vPos = uPos + stride / 2; + + const uint8_t* dataY = i420Buffer.get() + yPos; + const uint8_t* dataU = i420Buffer.get() + uPos; + const uint8_t* dataV = i420Buffer.get() + vPos; + + return LocalI420Buffer::Wrap( + std::move(i420Buffer), frameWidth, frameHeight, dataY, stride, dataU, stride, dataV, stride); +} + +bool YuvConverter::PrepareFrameBuffer(int width, int height) +{ + if (width <= 0 || height <= 0) { + RTC_LOG(LS_ERROR) << "Invalid size: " << width << "x" << height; + return false; + } + + if (width == frameBufferWidth_ && height == frameBufferHeight_) { + return true; + } + + frameBufferWidth_ = width; + frameBufferHeight_ = height; + + if (textureId_ == 0) { + glGenTextures(1, &textureId_); + glBindTexture(GL_TEXTURE_2D, textureId_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + if (frameBufferId_ == 0) { + glGenFramebuffers(1, &frameBufferId_); + } + + // Allocate texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + GLenum error = glGetError(); + if (error != GL_NO_ERROR) { + RTC_LOG(LS_ERROR) << "Failed to prepare frame buffer: " << error; + return false; + } + + // Attach the texture to the framebuffer as color attachment + glBindFramebuffer(GL_FRAMEBUFFER, frameBufferId_); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId_, 0); + + // Check that the framebuffer is in a good state + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + RTC_LOG(LS_ERROR) << "Framebuffer not complete, status: " << status; + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/render/yuv_converter.h b/sdk/ohos/src/ohos_webrtc/render/yuv_converter.h new file mode 100644 index 0000000000000000000000000000000000000000..67ad500f732235e7efdf44121a512648d9ef1cf2 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/render/yuv_converter.h @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RENDER_YUV_CONVERTER_H +#define WEBRTC_RENDER_YUV_CONVERTER_H + +#include "video_frame_drawer.h" + +#include "api/video/video_frame_buffer.h" + +#include + +namespace webrtc { + +class GlConverterDrawer; + +class YuvConverter { +public: + YuvConverter(); + ~YuvConverter(); + + rtc::scoped_refptr Convert(rtc::scoped_refptr textureBuffer); + +protected: + bool PrepareFrameBuffer(int width, int height); + +private: + int frameBufferWidth_{0}; + int frameBufferHeight_{0}; + unsigned int frameBufferId_{0}; + unsigned int textureId_{0}; + std::unique_ptr drawer_; + std::unique_ptr frameDrawer_; +}; + +} // namespace webrtc + +#endif // WEBRTC_RENDER_YUV_CONVERTER_H diff --git a/sdk/ohos/src/ohos_webrtc/rtp_parameters.cpp b/sdk/ohos/src/ohos_webrtc/rtp_parameters.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15236b17a7066c224643fbf5d7cb9d4856a3f21d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_parameters.cpp @@ -0,0 +1,691 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rtp_parameters.h" + +#include + +#include "rtc_base/logging.h" +#include "rtc_base/string_encode.h" +#include "absl/strings/ascii.h" + +namespace webrtc { + +const char kAttributeNameRid[] = "rid"; + +const char kAttributeNameActive[] = "active"; +const char kAttributeNameMaxBitrate[] = "maxBitrate"; +const char kAttributeNameMaxFramerate[] = "maxFramerate"; +const char kAttributeNameScaleResolutionDownBy[] = "scaleResolutionDownBy"; + +const char kAttributeNameClockRate[] = "clockRate"; +const char kAttributeNameChannels[] = "channels"; +const char kAttributeNameMimeType[] = "mimeType"; +const char kAttributeNameSdpFmtpLine[] = "sdpFmtpLine"; +const char kAttributeNamePayloadType[] = "payloadType"; + +const char kAttributeNameId[] = "id"; +const char kAttributeNameUri[] = "uri"; +const char kAttributeNameEncrypted[] = "encrypted"; + +const char kAttributeNameCname[] = "cname"; +const char kAttributeNameReducedSize[] = "reducedSize"; + +const char kAttributeNameCodecs[] = "codecs"; +const char kAttributeNameHeaderExtensions[] = "headerExtensions"; +const char kAttributeNameRtcp[] = "rtcp"; + +const char kAttributeNameEncodings[] = "encodings"; +const char kAttributeNameTransactionId[] = "transactionId"; + +using namespace Napi; + +void WriteFmtpParameter(absl::string_view parameter_name, absl::string_view parameter_value, std::ostringstream& ss) +{ + if (parameter_name.empty()) { + // RFC 2198 and RFC 4733 don't use key-value pairs. + ss << parameter_value; + } else { + // fmtp parameters: `parameter_name`=`parameter_value` + ss << parameter_name << "=" << parameter_value; + } +} + +bool WriteFmtpParameters(const std::map& parameters, std::ostringstream& ss) +{ + bool empty = true; + const char* delimiter = ""; // No delimiter before first parameter. + for (const auto& entry : parameters) { + const std::string& key = entry.first; + const std::string& value = entry.second; + + ss << delimiter; + // A semicolon before each subsequent parameter. + delimiter = ";"; + WriteFmtpParameter(key, value, ss); + empty = false; + } + + return !empty; +} + +bool ParseFmtpParam(absl::string_view line, std::string* parameter, std::string* value) +{ + if (!rtc::tokenize_first(line, '=', parameter, value)) { + // Support for non-key-value lines like RFC 2198 or RFC 4733. + *parameter = ""; + *value = std::string(line); + return true; + } + // a=fmtp: =; =; ... + return true; +} + +bool ParseFmtpLine(const std::string& line_params, std::map& parameters) +{ + if (line_params.size() == 0) { + return false; + } + + for (absl::string_view param : rtc::split(line_params, ';')) { + if (param.size() == 0) { + continue; + } + + std::string name; + std::string value; + if (!ParseFmtpParam(absl::StripAsciiWhitespace(param), &name, &value)) { + return false; + } + if (parameters.find(name) != parameters.end()) { + RTC_LOG(LS_INFO) << "Overwriting duplicate fmtp parameter with key \"" << name << "\"."; + } + parameters[name] = value; + } + + return true; +} + +bool ParseMimeType(const std::string& mimeType, std::string& kind, std::string& name) +{ + auto results = rtc::split(mimeType, '/'); + if (results.size() != 2) { + return false; + } + + kind = results[0]; + name = results[1]; + + return true; +} + +cricket::MediaType MediaTypeFromString(const std::string& kind) +{ + if (kind == cricket::kMediaTypeAudio) { + return cricket::MEDIA_TYPE_AUDIO; + } else if (kind == cricket::kMediaTypeVideo) { + return cricket::MEDIA_TYPE_VIDEO; + } else if (kind == cricket::kMediaTypeData) { + return cricket::MEDIA_TYPE_DATA; + } + + return cricket::MEDIA_TYPE_UNSUPPORTED; +} + +struct NapiRtpCodingParameters { + static void JsToNative(const Napi::Object& js, RtpEncodingParameters& native) + { + // rid?: string; + if (js.Has(kAttributeNameRid)) { + auto jsRid = js.Get(kAttributeNameRid); + if (jsRid.IsString()) { + native.rid = jsRid.As().Utf8Value(); + } + } + } + + static void NativeToJs(const RtpEncodingParameters& native, Napi::Object& js) + { + js.Set(kAttributeNameRid, String::New(js.Env(), native.rid)); + } +}; + +struct NapiRtpCodecParameters { + // clockRate: number; + // channels?: number; + // mimeType: string; + // sdpFmtpLine: string; + // payloadType: number; + + static void JsToNative(const Napi::Object& js, RtpCodecParameters& native) + { + if (!js.Has(kAttributeNameClockRate)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No clockRate")); + } + if (!js.Has(kAttributeNameMimeType)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No mimeType")); + } + if (!js.Has(kAttributeNameSdpFmtpLine)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No sdpFmtpLine")); + } + if (!js.Has(kAttributeNamePayloadType)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No payloadType")); + } + + auto jsClockRate = js.Get(kAttributeNameClockRate); + if (!jsClockRate.IsNumber()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The clockRate is not number")); + } + + auto jsMimeType = js.Get(kAttributeNameMimeType); + if (!jsMimeType.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The mimeType is not string")); + } + + auto jsSdpFmtpLine = js.Get(kAttributeNameSdpFmtpLine); + if (!jsSdpFmtpLine.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The sdpFmtpLine is not string")); + } + + auto jsPayloadType = js.Get(kAttributeNamePayloadType); + if (!jsPayloadType.IsNumber()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The payloadType is not number")); + } + + native.clock_rate = jsClockRate.As().Int32Value(); + native.payload_type = jsPayloadType.As().Int32Value(); + + std::string kind; + std::string name; + if (!ParseMimeType(jsMimeType.As().Utf8Value(), kind, name)) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "Invalid mimeType")); + } + native.kind = MediaTypeFromString(kind); + native.name = name; + + ParseFmtpLine(jsSdpFmtpLine.As().Utf8Value(), native.parameters); + + if (js.Has(kAttributeNameChannels)) { + auto jsChannels = js.Get(kAttributeNameChannels); + if (jsChannels.IsNumber()) { + native.num_channels = jsChannels.As().Int32Value(); + } + } + } + + static void NativeToJs(const RtpCodecParameters& native, Napi::Object& js) + { + js.Set(kAttributeNameMimeType, String::New(js.Env(), native.mime_type())); + js.Set(kAttributeNamePayloadType, Number::New(js.Env(), native.payload_type)); + if (native.num_channels) { + js.Set(kAttributeNameChannels, Number::New(js.Env(), native.num_channels.value())); + } + + if (native.clock_rate) { + js.Set(kAttributeNameClockRate, Number::New(js.Env(), native.clock_rate.value())); + } else { + // unset + js.Set(kAttributeNameClockRate, Number::New(js.Env(), -1)); + } + + std::ostringstream ss; + WriteFmtpParameters(native.parameters, ss); + js.Set(kAttributeNameSdpFmtpLine, String::New(js.Env(), ss.str())); + } +}; + +struct NapiRtpHeaderExtensionParameters { + // id: number; + // uri: string; + // encrypted?: boolean; + + static void JsToNative(const Napi::Object& js, RtpExtension& native) + { + if (!js.Has(kAttributeNameId)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No id")); + } + if (!js.Has(kAttributeNameUri)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No uri")); + } + + auto jsId = js.Get(kAttributeNameId); + if (!jsId.IsNumber()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The id is not number")); + } + + auto jsUri = js.Get(kAttributeNameUri); + if (!jsUri.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The uri is not string")); + } + + native.id = jsId.As().Int32Value(); + native.uri = jsUri.As().Utf8Value(); + + if (js.Has(kAttributeNameEncrypted)) { + auto jsEncrypted = js.Get(kAttributeNameEncrypted); + if (jsEncrypted.IsNumber()) { + native.encrypt = jsEncrypted.As().Int32Value(); + } + } + } + + static void NativeToJs(const RtpExtension& native, Napi::Object& js) + { + js.Set(kAttributeNameId, Number::New(js.Env(), native.id)); + js.Set(kAttributeNameUri, String::New(js.Env(), native.uri)); + js.Set(kAttributeNameEncrypted, Boolean::New(js.Env(), native.encrypt)); + } +}; + +struct NapiRtcpParameters { + // cname?: string; + // reducedSize?: boolean; + + static void JsToNative(const Napi::Object& js, RtcpParameters& native) + { + if (js.Has(kAttributeNameCname)) { + auto jsEncrypted = js.Get(kAttributeNameCname); + if (jsEncrypted.IsString()) { + native.cname = jsEncrypted.As().Utf8Value(); + } + } + + if (js.Has(kAttributeNameReducedSize)) { + auto jsEncrypted = js.Get(kAttributeNameReducedSize); + if (jsEncrypted.IsBoolean()) { + native.reduced_size = jsEncrypted.As().Value(); + } + } + } + + static void NativeToJs(const RtcpParameters& native, Napi::Object& js) + { + js.Set(kAttributeNameCname, String::New(js.Env(), native.cname)); + js.Set(kAttributeNameReducedSize, Boolean::New(js.Env(), native.reduced_size)); + } +}; + +struct NapiRtpParameters { + // codecs: RTCRtpCodecParameters[]; + // headerExtensions: RTCRtpHeaderExtensionParameters[]; + // rtcp: RTCRtcpParameters; + + static void JsToNative(const Napi::Object& js, RtpParameters& native) + { + if (!js.Has(kAttributeNameCodecs)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No codecs")); + } + if (!js.Has(kAttributeNameHeaderExtensions)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No headerExtensions")); + } + if (!js.Has(kAttributeNameRtcp)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No rtcp")); + } + + auto jsCodecs = js.Get(kAttributeNameCodecs); + if (!jsCodecs.IsArray()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The codecs is not array")); + } + + auto jsHeaderExtensions = js.Get(kAttributeNameHeaderExtensions); + if (!jsHeaderExtensions.IsArray()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The headerExtensions is not string")); + } + + auto jsRtcp = js.Get(kAttributeNameRtcp); + if (!jsRtcp.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The rtcp is not object")); + } + + auto jsCodecsArray = jsCodecs.As(); + for (uint32_t i = 0; i < jsCodecsArray.Length(); i++) { + Value jsCodec = jsCodecsArray[i]; + if (!jsCodec.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The element of codecs is not object")); + } + + RtpCodecParameters parameters; + NapiRtpCodecParameters::JsToNative(jsCodec.As(), parameters); + if (js.Env().IsExceptionPending()) { + NAPI_THROW_VOID(js.Env().GetAndClearPendingException()); + } + native.codecs.push_back(parameters); + } + + auto jsHeaderExtensionsArray = jsHeaderExtensions.As(); + for (uint32_t i = 0; i < jsHeaderExtensionsArray.Length(); i++) { + Value jsHeaderExtension = jsHeaderExtensionsArray[i]; + if (!jsHeaderExtension.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The element of headerExtensions is not object")); + } + + RtpExtension headerExtension; + NapiRtpHeaderExtensionParameters::JsToNative(jsHeaderExtension.As(), headerExtension); + if (js.Env().IsExceptionPending()) { + NAPI_THROW_VOID(js.Env().GetAndClearPendingException()); + } + native.header_extensions.push_back(headerExtension); + } + + NapiRtcpParameters::JsToNative(jsRtcp.As(), native.rtcp); + } + + static void NativeToJs(const RtpParameters& native, Napi::Object& js) + { + auto jsCodecsArray = Array::New(js.Env(), native.codecs.size()); + for (uint32_t i = 0; i < jsCodecsArray.Length(); i++) { + auto jsCodec = Object::New(js.Env()); + NapiRtpCodecParameters::NativeToJs(native.codecs[i], jsCodec); + jsCodecsArray[i] = jsCodec; + } + js.Set(kAttributeNameCodecs, jsCodecsArray); + + auto jsHeaderExtensionsArray = Array::New(js.Env(), native.header_extensions.size()); + for (uint32_t i = 0; i < jsHeaderExtensionsArray.Length(); i++) { + auto jsHeaderExtension = Object::New(js.Env()); + NapiRtpHeaderExtensionParameters::NativeToJs(native.header_extensions[i], jsHeaderExtension); + jsHeaderExtensionsArray[i] = jsHeaderExtension; + } + js.Set(kAttributeNameHeaderExtensions, jsHeaderExtensionsArray); + + auto jsRtcp = Object::New(js.Env()); + NapiRtcpParameters::NativeToJs(native.rtcp, jsRtcp); + js.Set(kAttributeNameRtcp, jsRtcp); + } +}; + +struct NapiRtpHeaderExtensionCapability { + // uri: string; + static void JsToNative(const Napi::Object& js, RtpHeaderExtensionCapability& native) + { + if (!js.Has(kAttributeNameUri)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No uri")); + } + + auto jsUri = js.Get(kAttributeNameUri); + if (!jsUri.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The uri is not string")); + } + + native.uri = jsUri.As().Utf8Value(); + } + + static void NativeToJs(const RtpHeaderExtensionCapability& native, Napi::Object& js) + { + js.Set(kAttributeNameUri, String::New(js.Env(), native.uri)); + } +}; + +// encodings: RTCRtpEncodingParameters[]; +// transactionId: string; +void NapiRtpSendParameters::JsToNative(const Napi::Object& js, RtpParameters& native) +{ + if (!js.Has(kAttributeNameTransactionId)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No transactionId")); + } + + if (!js.Has(kAttributeNameEncodings)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No encodings")); + } + + auto jsTransactionId = js.Get(kAttributeNameTransactionId); + if (!jsTransactionId.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The transactionId is not string")); + } + + auto jsEncodings = js.Get(kAttributeNameEncodings); + if (!jsEncodings.IsArray()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The encodings is not array")); + } + + native.transaction_id = jsTransactionId.As().Utf8Value(); + + auto jsEncodingsArray = jsEncodings.As(); + for (uint32_t i = 0; i < jsEncodingsArray.Length(); i++) { + Value jsEncodingParameters = jsEncodingsArray[i]; + if (!jsEncodingParameters.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The element of encodings is not object")); + } + + RtpEncodingParameters rtpEncodingParameters; + NapiRtpEncodingParameters::JsToNative(jsEncodingParameters.As(), rtpEncodingParameters); + native.encodings.push_back(rtpEncodingParameters); + } + + NapiRtpParameters::JsToNative(js, native); + if (js.Env().IsExceptionPending()) { + NAPI_THROW_VOID(js.Env().GetAndClearPendingException()); + } +} + +void NapiRtpSendParameters::NativeToJs(const RtpParameters& native, Napi::Object& js) +{ + js.Set(kAttributeNameTransactionId, native.transaction_id); + + auto jsEncodingsArray = Array::New(js.Env(), native.encodings.size()); + for (uint32_t i = 0; i < jsEncodingsArray.Length(); i++) { + auto jsEncoding = Object::New(js.Env()); + NapiRtpEncodingParameters::NativeToJs(native.encodings[i], jsEncoding); + jsEncodingsArray[i] = jsEncoding; + } + js.Set(kAttributeNameEncodings, jsEncodingsArray); + + NapiRtpParameters::NativeToJs(native, js); +} + +void NapiRtpReceiveParameters::JsToNative(const Napi::Object& js, RtpParameters& native) +{ + NapiRtpParameters::JsToNative(js, native); + if (js.Env().IsExceptionPending()) { + NAPI_THROW_VOID(js.Env().GetAndClearPendingException()); + } +} + +void NapiRtpReceiveParameters::NativeToJs(const RtpParameters& native, Napi::Object& js) +{ + NapiRtpParameters::NativeToJs(native, js); +} + +// codecs: RTCRtpCodecCapability[]; +// headerExtensions: RTCRtpHeaderExtensionCapability[]; +void NapiRtpCapabilities::JsToNative(const Napi::Object& js, RtpCapabilities& native) +{ + if (!js.Has(kAttributeNameCodecs)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No codecs")); + } + + if (!js.Has(kAttributeNameHeaderExtensions)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No headerExtensions")); + } + + auto jsCodecs = js.Get(kAttributeNameCodecs); + if (!jsCodecs.IsArray()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The codecs is not array")); + } + + auto jsHeaderExtensions = js.Get(kAttributeNameHeaderExtensions); + if (!jsHeaderExtensions.IsArray()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The headerExtensions is not string")); + } + + auto jsCodecsArray = jsCodecs.As(); + for (uint32_t i = 0; i < jsCodecsArray.Length(); i++) { + Value jsCodec = jsCodecsArray[i]; + if (!jsCodec.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The element of codecs is not object")); + } + + RtpCodecCapability parameters; + NapiRtpCodecCapability::JsToNative(jsCodec.As(), parameters); + native.codecs.push_back(parameters); + } + + auto jsHeaderExtensionsArray = jsHeaderExtensions.As(); + for (uint32_t i = 0; i < jsHeaderExtensionsArray.Length(); i++) { + Value jsHeaderExtension = jsHeaderExtensionsArray[i]; + if (!jsHeaderExtension.IsObject()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The element of headerExtensions is not object")); + } + + RtpHeaderExtensionCapability headerExtension; + NapiRtpHeaderExtensionCapability::JsToNative(jsHeaderExtension.As(), headerExtension); + if (js.Env().IsExceptionPending()) { + NAPI_THROW_VOID(js.Env().GetAndClearPendingException()); + } + native.header_extensions.push_back(headerExtension); + } +} + +void NapiRtpCapabilities::NativeToJs(const RtpCapabilities& native, Napi::Object& js) +{ + auto jsCodecsArray = Array::New(js.Env(), native.codecs.size()); + for (uint32_t i = 0; i < jsCodecsArray.Length(); i++) { + auto jsCodec = Object::New(js.Env()); + NapiRtpCodecCapability::NativeToJs(native.codecs[i], jsCodec); + jsCodecsArray[i] = jsCodec; + } + js.Set(kAttributeNameCodecs, jsCodecsArray); + + auto jsHeaderExtensionsArray = Array::New(js.Env(), native.header_extensions.size()); + for (uint32_t i = 0; i < jsHeaderExtensionsArray.Length(); i++) { + auto jsHeaderExtension = Object::New(js.Env()); + NapiRtpHeaderExtensionCapability::NativeToJs(native.header_extensions[i], jsHeaderExtension); + jsHeaderExtensionsArray[i] = jsHeaderExtension; + } + js.Set(kAttributeNameHeaderExtensions, jsHeaderExtensionsArray); +} + +// mimeType: string; +// clockRate: number; +// channels?: number; +// sdpFmtpLine?: string; +void NapiRtpCodecCapability::JsToNative(const Napi::Object& js, RtpCodecCapability& native) +{ + if (!js.Has(kAttributeNameClockRate)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No clockRate")); + } + if (!js.Has(kAttributeNameMimeType)) { + NAPI_THROW_VOID(Error::New(js.Env(), "No mimeType")); + } + + auto jsClockRate = js.Get(kAttributeNameClockRate); + if (!jsClockRate.IsNumber()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The clockRate is not number")); + } + + auto jsMimeType = js.Get(kAttributeNameMimeType); + if (!jsMimeType.IsString()) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "The mimeType is not string")); + } + + native.clock_rate = jsClockRate.As().Int32Value(); + + std::string kind; + std::string name; + if (!ParseMimeType(jsMimeType.As().Utf8Value(), kind, name)) { + NAPI_THROW_VOID(TypeError::New(js.Env(), "Invalid mimeType")); + } + native.kind = MediaTypeFromString(kind); + native.name = name; + + if (js.Has(kAttributeNameSdpFmtpLine)) { + auto jsSdpFmtpLine = js.Get(kAttributeNameSdpFmtpLine); + if (jsSdpFmtpLine.IsString()) { + ParseFmtpLine(jsSdpFmtpLine.As().Utf8Value(), native.parameters); + } + } + + if (js.Has(kAttributeNameChannels)) { + auto jsChannels = js.Get(kAttributeNameChannels); + if (jsChannels.IsNumber()) { + native.num_channels = jsChannels.As().Int32Value(); + } + } +} + +void NapiRtpCodecCapability::NativeToJs(const RtpCodecCapability& native, Napi::Object& js) +{ + js.Set(kAttributeNameMimeType, String::New(js.Env(), native.mime_type())); + + if (native.clock_rate) { + js.Set(kAttributeNameClockRate, Number::New(js.Env(), native.clock_rate.value())); + } else { + // unset + js.Set(kAttributeNameClockRate, Number::New(js.Env(), -1)); + } + + if (native.num_channels) { + js.Set(kAttributeNameChannels, Number::New(js.Env(), native.num_channels.value())); + } + + std::ostringstream ss; + WriteFmtpParameters(native.parameters, ss); + js.Set(kAttributeNameSdpFmtpLine, String::New(js.Env(), ss.str())); +} + +// active?: boolean; +// maxBitrate?: number; +// maxFramerate?: number; +// scaleResolutionDownBy?: number; +void NapiRtpEncodingParameters::JsToNative(const Napi::Object& js, RtpEncodingParameters& native) +{ + if (js.Has(kAttributeNameActive)) { + auto jsActive = js.Get(kAttributeNameActive); + if (jsActive.IsBoolean()) { + native.active = jsActive.As().Value(); + } + } + + if (js.Has(kAttributeNameMaxBitrate)) { + auto jsMaxBitrate = js.Get(kAttributeNameMaxBitrate); + if (jsMaxBitrate.IsNumber()) { + native.max_bitrate_bps = jsMaxBitrate.As().Int32Value(); + } + } + + if (js.Has(kAttributeNameMaxFramerate)) { + auto jsMaxFramerate = js.Get(kAttributeNameMaxFramerate); + if (jsMaxFramerate.IsNumber()) { + native.max_framerate = jsMaxFramerate.As().DoubleValue(); + } + } + + if (js.Has(kAttributeNameScaleResolutionDownBy)) { + auto jsScaleResolutionDownBy = js.Get(kAttributeNameScaleResolutionDownBy); + if (jsScaleResolutionDownBy.IsNumber()) { + native.scale_resolution_down_by = jsScaleResolutionDownBy.As().DoubleValue(); + } + } + + NapiRtpCodingParameters::JsToNative(js, native); +} + +void NapiRtpEncodingParameters::NativeToJs(const RtpEncodingParameters& native, Napi::Object& js) +{ + js.Set(kAttributeNameActive, Boolean::New(js.Env(), native.active)); + if (native.max_bitrate_bps) { + js.Set(kAttributeNameMaxBitrate, Number::New(js.Env(), native.max_bitrate_bps.value())); + } + if (native.max_framerate) { + js.Set(kAttributeNameMaxFramerate, Number::New(js.Env(), native.max_framerate.value())); + } + if (native.scale_resolution_down_by) { + js.Set(kAttributeNameScaleResolutionDownBy, Number::New(js.Env(), native.scale_resolution_down_by.value())); + } + + NapiRtpCodingParameters::NativeToJs(native, js); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/rtp_parameters.h b/sdk/ohos/src/ohos_webrtc/rtp_parameters.h new file mode 100644 index 0000000000000000000000000000000000000000..485b7cd07976617530d667ca48920299816e21fc --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_parameters.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RTP_PARAMETERS_H +#define WEBRTC_RTP_PARAMETERS_H + +#include "napi.h" + +#include "api/rtp_parameters.h" + +namespace webrtc { + +struct NapiRtpSendParameters { + static void JsToNative(const Napi::Object& js, RtpParameters& native); + static void NativeToJs(const RtpParameters& native, Napi::Object& js); +}; + +struct NapiRtpReceiveParameters { + static void JsToNative(const Napi::Object& js, RtpParameters& native); + static void NativeToJs(const RtpParameters& native, Napi::Object& js); +}; + +struct NapiRtpCapabilities { + static void JsToNative(const Napi::Object& js, RtpCapabilities& native); + static void NativeToJs(const RtpCapabilities& native, Napi::Object& js); +}; + +struct NapiRtpCodecCapability { + static void JsToNative(const Napi::Object& js, RtpCodecCapability& native); + static void NativeToJs(const RtpCodecCapability& native, Napi::Object& js); +}; + +struct NapiRtpEncodingParameters { + static void JsToNative(const Napi::Object& js, RtpEncodingParameters& native); + static void NativeToJs(const RtpEncodingParameters& native, Napi::Object& js); +}; + +} // namespace webrtc + +#endif // WEBRTC_RTP_PARAMETERS_H diff --git a/sdk/ohos/src/ohos_webrtc/rtp_receiver.cpp b/sdk/ohos/src/ohos_webrtc/rtp_receiver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4013832d2079106695e46790705946763be23dcd --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_receiver.cpp @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rtp_receiver.h" +#include "media_stream_track.h" +#include "peer_connection_factory.h" +#include "dtls_transport.h" +#include "rtp_parameters.h" +#include "peer_connection.h" + +#include + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "RTCRtpReceiver"; + +const char kAttributeNameTrack[] = "track"; +const char kAttributeNameTransport[] = "transport"; + +const char kMethodNameGetParameters[] = "getParameters"; +const char kMethodNameGetStats[] = "getStats"; +const char kMethodNameGetContributingSources[] = "getContributingSources"; +const char kMethodNameGetSynchronizationSources[] = "getSynchronizationSources"; +const char kMethodNameToJson[] = "toJSON"; + +const char kStaticMethodNameGetCapabilities[] = "getCapabilities"; + +const char kAttributeNameTimestamp[] = "timestamp"; +const char kAttributeNameRtpTimestamp[] = "rtpTimestamp"; +const char kAttributeNameSource[] = "source"; +const char kAttributeNameAudioLevel[] = "audioLevel"; + +/// NapiRtpReceiver +FunctionReference NapiRtpReceiver::constructor_; + +void NapiRtpReceiver::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiRtpReceiver::GetTrack>(kAttributeNameTrack), + InstanceAccessor<&NapiRtpReceiver::GetTransport>(kAttributeNameTransport), + InstanceMethod<&NapiRtpReceiver::GetParameters>(kMethodNameGetParameters), + InstanceMethod<&NapiRtpReceiver::GetStats>(kMethodNameGetStats), + InstanceMethod<&NapiRtpReceiver::GetContributingSources>(kMethodNameGetContributingSources), + InstanceMethod<&NapiRtpReceiver::GetSynchronizationSources>(kMethodNameGetSynchronizationSources), + InstanceMethod<&NapiRtpReceiver::ToJson>(kMethodNameToJson), + StaticMethod<&NapiRtpReceiver::GetCapabilities>(kStaticMethodNameGetCapabilities), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiRtpReceiver::NewInstance( + Napi::Env env, rtc::scoped_refptr receiver, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!receiver || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto externalReceiver = External::New( + env, receiver.release(), [](Napi::Env, RtpReceiverInterface* receiver) { receiver->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalReceiver, externalPcWrapper}); +} + +NapiRtpReceiver::NapiRtpReceiver(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info[0].IsExternal()) { + auto rtpReceiver = info[0].As>().Data(); + rtpReceiver_ = rtc::scoped_refptr(rtpReceiver); + } + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + } +} + +rtc::scoped_refptr NapiRtpReceiver::Get() const +{ + return rtpReceiver_; +} + +Napi::Value NapiRtpReceiver::GetCapabilities(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Null()); + } + + if (!info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "First argument is not string"), info.Env().Null()); + } + + auto kind = info[0].As().Utf8Value(); + auto mediaType = cricket::MEDIA_TYPE_UNSUPPORTED; + if (kind == cricket::kMediaTypeAudio) { + mediaType = cricket::MEDIA_TYPE_AUDIO; + } else if (kind == cricket::kMediaTypeVideo) { + mediaType = cricket::MEDIA_TYPE_VIDEO; + } else { + return info.Env().Null(); + } + + auto factoryWrapper = PeerConnectionFactoryWrapper::GetDefault(); + if (!factoryWrapper) { + NAPI_THROW(Error::New(info.Env(), "Internal error"), info.Env().Null()); + } + + auto capabilities = factoryWrapper->GetFactory()->GetRtpReceiverCapabilities(mediaType); + auto jsCapabilities = Object::New(info.Env()); + NapiRtpCapabilities::NativeToJs(capabilities, jsCapabilities); + + return jsCapabilities; +} + +Napi::Value NapiRtpReceiver::GetTrack(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto track = rtpReceiver_->track(); + if (!track) { + NAPI_THROW(Error::New(info.Env(), "No track"), info.Env().Undefined()); + } + + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(track)); +} + +Napi::Value NapiRtpReceiver::GetTransport(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto transport = rtpReceiver_->dtls_transport(); + if (!transport) { + return info.Env().Null(); + } + + return NapiDtlsTransport::NewInstance(info.Env(), transport, pcWrapper_); +} + +Napi::Value NapiRtpReceiver::GetParameters(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto jsParameters = Object::New(info.Env()); + NapiRtpSendParameters::NativeToJs(rtpReceiver_->GetParameters(), jsParameters); + + return jsParameters; +} + +Napi::Value NapiRtpReceiver::GetStats(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = AsyncWorkerGetStats::Create(info.Env(), "GetStats"); + auto pc = pcWrapper_->GetPeerConnection(); + pc->GetStats(rtpReceiver_, asyncWorker->GetCallback()); + + return asyncWorker->GetPromise(); +} + +Napi::Value NapiRtpReceiver::GetContributingSources(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto sources = rtpReceiver_->GetSources(); + + std::vector csrcs; + for (const auto& source : sources) { + if (source.source_type() == RtpSourceType::CSRC) { + csrcs.push_back(source); + } + } + + auto jsCsrcs = Array::New(info.Env(), csrcs.size()); + for (uint32_t i = 0; i < jsCsrcs.Length(); i++) { + const auto& source = csrcs[i]; + + auto jsSource = Object::New(info.Env()); + jsSource.Set(kAttributeNameTimestamp, Number::New(info.Env(), source.timestamp().ms())); + jsSource.Set(kAttributeNameRtpTimestamp, Number::New(info.Env(), source.rtp_timestamp())); + jsSource.Set(kAttributeNameSource, Number::New(info.Env(), source.source_id())); + + auto audioLevel = source.audio_level(); + if (audioLevel) { + constexpr auto max = std::numeric_limits>::max(); + jsSource.Set(kAttributeNameAudioLevel, Number::New(info.Env(), audioLevel.value() / max)); + } + + jsCsrcs.Set(i, jsSource); + } + + return jsCsrcs; +} + +Napi::Value NapiRtpReceiver::GetSynchronizationSources(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto sources = rtpReceiver_->GetSources(); + + std::vector ssrcs; + for (const auto& source : sources) { + if (source.source_type() == RtpSourceType::SSRC) { + ssrcs.push_back(source); + } + } + + auto jsSsrcs = Array::New(info.Env(), ssrcs.size()); + for (uint32_t i = 0; i < jsSsrcs.Length(); i++) { + const auto& source = ssrcs[i]; + + auto jsSource = Object::New(info.Env()); + jsSource.Set(kAttributeNameTimestamp, Number::New(info.Env(), source.timestamp().ms())); + jsSource.Set(kAttributeNameRtpTimestamp, Number::New(info.Env(), source.rtp_timestamp())); + jsSource.Set(kAttributeNameSource, Number::New(info.Env(), source.source_id())); + + auto audioLevel = source.audio_level(); + if (audioLevel) { + constexpr auto max = std::numeric_limits>::max(); + jsSource.Set(kAttributeNameAudioLevel, Number::New(info.Env(), audioLevel.value() / max)); + } + + jsSsrcs.Set(i, jsSource); + } + + return jsSsrcs; +} + +Napi::Value NapiRtpReceiver::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiRtpReceiver"); +#endif + + return json; +} + +} // namespace webrtc \ No newline at end of file diff --git a/sdk/ohos/src/ohos_webrtc/rtp_receiver.h b/sdk/ohos/src/ohos_webrtc/rtp_receiver.h new file mode 100644 index 0000000000000000000000000000000000000000..4a46a1e1f8ad70bbb9a170b7337b8814a3a5c926 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_receiver.h @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RTP_RECEIVER_H +#define WEBRTC_RTP_RECEIVER_H + +#include "async_work/async_worker_get_stats.h" + +#include "napi.h" + +#include "api/rtp_receiver_interface.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiRtpReceiver : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object + NewInstance(Napi::Env env, rtc::scoped_refptr receiver, NapiPeerConnectionWrapper* pcWrapper); + + explicit NapiRtpReceiver(const Napi::CallbackInfo& info); + + rtc::scoped_refptr Get() const; + +protected: + static Napi::Value GetCapabilities(const Napi::CallbackInfo& info); + + Napi::Value GetTrack(const Napi::CallbackInfo& info); + Napi::Value GetTransport(const Napi::CallbackInfo& info); + + Napi::Value GetParameters(const Napi::CallbackInfo& info); + Napi::Value GetStats(const Napi::CallbackInfo& info); + Napi::Value GetContributingSources(const Napi::CallbackInfo& info); + Napi::Value GetSynchronizationSources(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr rtpReceiver_; + NapiPeerConnectionWrapper* pcWrapper_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_RTP_RECEIVER_H diff --git a/sdk/ohos/src/ohos_webrtc/rtp_sender.cpp b/sdk/ohos/src/ohos_webrtc/rtp_sender.cpp new file mode 100644 index 0000000000000000000000000000000000000000..558462e0062461849df71aa219992451b66b2520 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_sender.cpp @@ -0,0 +1,346 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rtp_sender.h" +#include "media_stream.h" +#include "media_stream_track.h" +#include "dtls_transport.h" +#include "dtmf_sender.h" +#include "rtp_parameters.h" +#include "peer_connection_factory.h" +#include "async_work/async_worker_get_stats.h" +#include "peer_connection.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "RTCRtpSender"; + +const char kAttributeNameTrack[] = "track"; +const char kAttributeNameTransport[] = "transport"; +const char kAttributeNameDtmf[] = "dtmf"; + +const char kMethodNameSetParameters[] = "setParameters"; +const char kMethodNameGetParameters[] = "getParameters"; +const char kMethodNameReplaceTrack[] = "replaceTrack"; +const char kMethodNameSetStreams[] = "setStreams"; +const char kMethodNameGetStats[] = "getStats"; +const char kMethodNameToJson[] = "toJSON"; + +const char kStaticMethodNameGetCapabilities[] = "getCapabilities"; + +class AsyncWorkerReplaceTrack : public Napi::AsyncWorker { +public: + static Napi::Value DoWork( + Napi::Env env, rtc::scoped_refptr rtpSender, + rtc::scoped_refptr track) + { + auto worker = new AsyncWorkerReplaceTrack(env, rtpSender, track); + worker->Queue(); + return worker->deferred_.Promise(); + } + +protected: + AsyncWorkerReplaceTrack( + Napi::Env env, rtc::scoped_refptr rtpSender, + rtc::scoped_refptr track) + : Napi::AsyncWorker(env, "replaceTrack"), + env_(env), + deferred_(Napi::Promise::Deferred::New(env)), + rtpSender_(rtpSender), + track_(track) + { + } + + void Execute() override + { + if (!rtpSender_->SetTrack(track_.get())) { + SetError("Unknown error"); + } + } + + void OnOK() override + { + deferred_.Resolve(env_.Undefined()); + } + + void OnError(const Napi::Error& e) override + { + deferred_.Reject(e.Value()); + } + +private: + Napi::Env env_; + Napi::Promise::Deferred deferred_; + rtc::scoped_refptr rtpSender_; + rtc::scoped_refptr track_; +}; + +FunctionReference NapiRtpSender::constructor_; + +void NapiRtpSender::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiRtpSender::GetTrack>(kAttributeNameTrack), + InstanceAccessor<&NapiRtpSender::GetTransport>(kAttributeNameTransport), + InstanceAccessor<&NapiRtpSender::GetDtmf>(kAttributeNameDtmf), + InstanceMethod<&NapiRtpSender::SetParameters>(kMethodNameSetParameters), + InstanceMethod<&NapiRtpSender::GetParameters>(kMethodNameGetParameters), + InstanceMethod<&NapiRtpSender::ReplaceTrack>(kMethodNameReplaceTrack), + InstanceMethod<&NapiRtpSender::SetStreams>(kMethodNameSetStreams), + InstanceMethod<&NapiRtpSender::GetStats>(kMethodNameGetStats), + InstanceMethod<&NapiRtpSender::ToJson>(kMethodNameToJson), + StaticMethod<&NapiRtpSender::GetCapabilities>(kStaticMethodNameGetCapabilities), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiRtpSender::NewInstance( + Napi::Env env, rtc::scoped_refptr sender, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!sender || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto externalSender = External::New( + env, sender.release(), [](Napi::Env, RtpSenderInterface* sender) { sender->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalSender, externalPcWrapper}); +} + +NapiRtpSender::NapiRtpSender(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() > 0 && info[0].IsExternal()) { + auto rtpSender = info[0].As>().Data(); + rtpSender_ = rtc::scoped_refptr(rtpSender); + } + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + } +} + +rtc::scoped_refptr NapiRtpSender::Get() const +{ + return rtpSender_; +} + +Napi::Value NapiRtpSender::GetCapabilities(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Null()); + } + + if (!info[0].IsString()) { + NAPI_THROW(Error::New(info.Env(), "First argument is not string"), info.Env().Null()); + } + + auto kind = info[0].As().Utf8Value(); + auto mediaType = cricket::MEDIA_TYPE_UNSUPPORTED; + if (kind == cricket::kMediaTypeAudio) { + mediaType = cricket::MEDIA_TYPE_AUDIO; + } else if (kind == cricket::kMediaTypeVideo) { + mediaType = cricket::MEDIA_TYPE_VIDEO; + } else { + return info.Env().Null(); + } + + auto factoryWrapper = PeerConnectionFactoryWrapper::GetDefault(); + if (!factoryWrapper) { + NAPI_THROW(Error::New(info.Env(), "Internal error"), info.Env().Null()); + } + + auto capabilities = factoryWrapper->GetFactory()->GetRtpSenderCapabilities(mediaType); + auto jsCapabilities = Object::New(info.Env()); + NapiRtpCapabilities::NativeToJs(capabilities, jsCapabilities); + + return jsCapabilities; +} + +Napi::Value NapiRtpSender::GetTrack(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto track = rtpSender_->track(); + if (!track) { + return info.Env().Null(); + } + + return NapiMediaStreamTrack::NewInstance(info.Env(), MediaStreamTrackWrapper::Create(track)); +} + +Napi::Value NapiRtpSender::GetTransport(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto transport = rtpSender_->dtls_transport(); + if (!transport) { + return info.Env().Null(); + } + + return NapiDtlsTransport::NewInstance(info.Env(), transport, pcWrapper_); +} + +Napi::Value NapiRtpSender::GetDtmf(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto sender = rtpSender_->GetDtmfSender(); + if (!sender) { + return info.Env().Null(); + } + + return NapiDtmfSender::NewInstance(info.Env(), sender); +} + +Napi::Value NapiRtpSender::SetParameters(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() == 0) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsObject()) { + NAPI_THROW(TypeError::New(info.Env(), "First argument is not object"), info.Env().Undefined()); + } + + auto jsParameters = info[0].As(); + RtpParameters parameters; + NapiRtpSendParameters::JsToNative(jsParameters, parameters); + if (info.Env().IsExceptionPending()) { + NAPI_THROW(info.Env().GetAndClearPendingException(), info.Env().Undefined()); + } + + auto deferred = Promise::Deferred::New(info.Env()); + auto tsfn = ThreadSafeFunction::New( + info.Env(), + Function::New( + info.Env(), + [deferred](const CallbackInfo& info) { + auto error = info[0].As>().Data(); + if (error->ok()) { + deferred.Resolve(info.Env().Undefined()); + } else { + auto type = error->type(); + auto message = error->message(); + RTC_LOG(LS_ERROR) << "SetParametersAsync failed: " << type << ", " << message; + deferred.Reject( + Error::New(info.Env(), (message && strlen(message) > 0) ? message : "Unknown error").Value()); + } + }), + "SetParametersAsync", 0, 1); + + rtpSender_->SetParametersAsync(parameters, [tsfn](RTCError error) { + RTC_DLOG(LS_INFO) << "SetParametersAsync complete: " << error.ok(); + tsfn.BlockingCall([error = new RTCError(std::move(error))](Napi::Env env, Napi::Function jsCallback) { + jsCallback.Call({External::New(env, error, [](Napi::Env, RTCError* e) { delete e; })}); + }); + tsfn.Release(); + }); + + return deferred.Promise(); +} + +Napi::Value NapiRtpSender::GetParameters(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto jsParameters = Object::New(info.Env()); + NapiRtpSendParameters::NativeToJs(rtpSender_->GetParameters(), jsParameters); + + return jsParameters; +} + +Napi::Value NapiRtpSender::ReplaceTrack(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + rtc::scoped_refptr track; + if (info.Length() > 0 && info[0].IsObject()) { + auto napiTrack = NapiMediaStreamTrack::Unwrap(info[0].As()); + if (napiTrack) { + track = napiTrack->Get(); + } + } + + return AsyncWorkerReplaceTrack::DoWork(info.Env(), rtpSender_, track); +} + +Napi::Value NapiRtpSender::SetStreams(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + std::vector streamsIds; + for (uint32_t i = 0; i < info.Length(); i++) { + Napi::Value jsStream = info[i]; + if (!jsStream.IsObject()) { + continue; + } + + auto stream = NapiMediaStream::Unwrap(jsStream.As()); + if (!stream || !stream->Get()) { + continue; + } + + streamsIds.push_back(stream->Get()->id()); + } + + rtpSender_->SetStreams(streamsIds); + + return info.Env().Undefined(); +} + +Napi::Value NapiRtpSender::GetStats(const Napi::CallbackInfo& info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + auto asyncWorker = AsyncWorkerGetStats::Create(info.Env(), "GetStats"); + auto pc = pcWrapper_->GetPeerConnection(); + pc->GetStats(rtpSender_, asyncWorker->GetCallback()); + + return asyncWorker->GetPromise(); +} + +Napi::Value NapiRtpSender::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiRtpSender"); +#endif + + return json; +} + +} // namespace webrtc \ No newline at end of file diff --git a/sdk/ohos/src/ohos_webrtc/rtp_sender.h b/sdk/ohos/src/ohos_webrtc/rtp_sender.h new file mode 100644 index 0000000000000000000000000000000000000000..c02789e226d340a28ed9c38675313dfdc23a0aab --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_sender.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RTP_SENDER_H +#define WEBRTC_RTP_SENDER_H + +#include "async_work/async_worker_get_stats.h" + +#include "napi.h" + +#include "api/rtp_sender_interface.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiRtpSender : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object + NewInstance(Napi::Env env, rtc::scoped_refptr sender, NapiPeerConnectionWrapper* pcWrapper); + + explicit NapiRtpSender(const Napi::CallbackInfo& info); + + rtc::scoped_refptr Get() const; + +protected: + static Napi::Value GetCapabilities(const Napi::CallbackInfo& info); + + Napi::Value GetTrack(const Napi::CallbackInfo& info); + Napi::Value GetTransport(const Napi::CallbackInfo& info); + Napi::Value GetDtmf(const Napi::CallbackInfo& info); + + Napi::Value SetParameters(const Napi::CallbackInfo& info); + Napi::Value GetParameters(const Napi::CallbackInfo& info); + Napi::Value ReplaceTrack(const Napi::CallbackInfo& info); + Napi::Value SetStreams(const Napi::CallbackInfo& info); + Napi::Value GetStats(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr rtpSender_; + NapiPeerConnectionWrapper* pcWrapper_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_RTP_SENDER_H diff --git a/sdk/ohos/src/ohos_webrtc/rtp_transceiver.cpp b/sdk/ohos/src/ohos_webrtc/rtp_transceiver.cpp new file mode 100644 index 0000000000000000000000000000000000000000..14ce1a89b267b6d56cb646f3df3ac448aea92874 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_transceiver.cpp @@ -0,0 +1,325 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "rtp_transceiver.h" +#include "rtp_receiver.h" +#include "rtp_sender.h" +#include "media_stream.h" +#include "rtp_parameters.h" +#include "peer_connection.h" + +#include "rtc_base/logging.h" +#include "api/rtp_transceiver_direction.h" + +namespace webrtc { + +using namespace Napi; + +const char kClassName[] = "RTCRtpTransceiver"; + +const char kAttributeNameTrack[] = "mid"; +const char kAttributeNameSender[] = "sender"; +const char kAttributeNameReceiver[] = "receiver"; +const char kAttributeNameDirection[] = "direction"; +const char kAttributeNameCurrentDirection[] = "currentDirection"; +const char kAttributeNameStreams[] = "streams"; +const char kAttributeNameSendEncodings[] = "sendEncodings"; +const char kAttributeNameActive[] = "active"; +const char kAttributeNameMaxBitrate[] = "maxBitrate"; +const char kAttributeNameMaxFramerate[] = "maxFramerate"; +const char kAttributeNameScaleResolutionDownBy[] = "scaleResolutionDownBy"; + +const char kMethodNameStop[] = "stop"; +const char kMethodNameSetCodecPreferences[] = "setCodecPreferences"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEnumRtpTransceiverDirectionInactive[] = "inactive"; +const char kEnumRtpTransceiverDirectionRecvOnly[] = "recvonly"; +const char kEnumRtpTransceiverDirectionSendOnly[] = "sendonly"; +const char kEnumRtpTransceiverDirectionSendRecv[] = "sendrecv"; +const char kEnumRtpTransceiverDirectionStopped[] = "stopped"; + +FunctionReference NapiRtpTransceiver::constructor_; + +void NapiRtpTransceiver::Init(Napi::Env env, Napi::Object exports) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiRtpTransceiver::GetMid>(kAttributeNameTrack), + InstanceAccessor<&NapiRtpTransceiver::GetSender>(kAttributeNameSender), + InstanceAccessor<&NapiRtpTransceiver::GetReceiver>(kAttributeNameReceiver), + InstanceAccessor<&NapiRtpTransceiver::GetDirection, &NapiRtpTransceiver::SetDirection>( + kAttributeNameDirection), + InstanceAccessor<&NapiRtpTransceiver::GetCurrentDirection>(kAttributeNameCurrentDirection), + InstanceMethod<&NapiRtpTransceiver::Stop>(kMethodNameStop), + InstanceMethod<&NapiRtpTransceiver::SetCodecPreferences>(kMethodNameSetCodecPreferences), + InstanceMethod<&NapiRtpTransceiver::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiRtpTransceiver::NewInstance( + Napi::Env env, rtc::scoped_refptr transceiver, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!transceiver || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto externalTransceiver = External::New( + env, transceiver.release(), [](Napi::Env, RtpTransceiverInterface* transceiver) { transceiver->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalTransceiver, externalPcWrapper}); +} + +NapiRtpTransceiver::NapiRtpTransceiver(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info[0].IsExternal()) { + auto rtpReceiver = info[0].As>().Data(); + rtpTransceiver_ = rtc::scoped_refptr(rtpReceiver); + } + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + } +} + +rtc::scoped_refptr NapiRtpTransceiver::Get() const +{ + return rtpTransceiver_; +} + +Napi::Value NapiRtpTransceiver::GetMid(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto mid = rtpTransceiver_->mid(); + if (!mid) { + return info.Env().Null(); + } + + return String::New(info.Env(), *mid); +} + +Napi::Value NapiRtpTransceiver::GetSender(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return NapiRtpSender::NewInstance(info.Env(), rtpTransceiver_->sender(), pcWrapper_); +} + +Napi::Value NapiRtpTransceiver::GetReceiver(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return NapiRtpReceiver::NewInstance(info.Env(), rtpTransceiver_->receiver(), pcWrapper_); +} + +Napi::Value NapiRtpTransceiver::GetDirection(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto direction = rtpTransceiver_->direction(); + switch (direction) { + case RtpTransceiverDirection::kInactive: + return String::New(info.Env(), kEnumRtpTransceiverDirectionInactive); + case RtpTransceiverDirection::kRecvOnly: + return String::New(info.Env(), kEnumRtpTransceiverDirectionRecvOnly); + case RtpTransceiverDirection::kSendOnly: + return String::New(info.Env(), kEnumRtpTransceiverDirectionSendOnly); + case RtpTransceiverDirection::kSendRecv: + return String::New(info.Env(), kEnumRtpTransceiverDirectionSendRecv); + case RtpTransceiverDirection::kStopped: + return String::New(info.Env(), kEnumRtpTransceiverDirectionStopped); + default: + RTC_LOG(LS_ERROR) << "Invalid direction: " << direction; + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid direction"), info.Env().Null()); +} + +void NapiRtpTransceiver::SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!value.IsString()) { + NAPI_THROW_VOID(Error::New(info.Env(), "The argument is not string")); + } + + auto jsDirection = value.As().Utf8Value(); + auto newDirection = RtpTransceiverDirection::kInactive; + + if (jsDirection == kEnumRtpTransceiverDirectionInactive) { + newDirection = RtpTransceiverDirection::kInactive; + } else if (jsDirection == kEnumRtpTransceiverDirectionRecvOnly) { + newDirection = RtpTransceiverDirection::kRecvOnly; + } else if (jsDirection == kEnumRtpTransceiverDirectionSendOnly) { + newDirection = RtpTransceiverDirection::kSendOnly; + } else if (jsDirection == kEnumRtpTransceiverDirectionSendRecv) { + newDirection = RtpTransceiverDirection::kSendRecv; + } else if (jsDirection == kEnumRtpTransceiverDirectionStopped) { + newDirection = RtpTransceiverDirection::kStopped; + } + + auto error = rtpTransceiver_->SetDirectionWithError(newDirection); + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to set direction: " << error.type() << ", " << error.message(); + NAPI_THROW_VOID(Error::New(info.Env(), "Failed to set direction")); + } +} + +Napi::Value NapiRtpTransceiver::GetCurrentDirection(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!rtpTransceiver_->current_direction()) { + return info.Env().Null(); + } + + switch (rtpTransceiver_->current_direction().value()) { + case RtpTransceiverDirection::kInactive: + return String::New(info.Env(), kEnumRtpTransceiverDirectionInactive); + case RtpTransceiverDirection::kRecvOnly: + return String::New(info.Env(), kEnumRtpTransceiverDirectionRecvOnly); + case RtpTransceiverDirection::kSendOnly: + return String::New(info.Env(), kEnumRtpTransceiverDirectionSendOnly); + case RtpTransceiverDirection::kSendRecv: + return String::New(info.Env(), kEnumRtpTransceiverDirectionSendRecv); + case RtpTransceiverDirection::kStopped: + return String::New(info.Env(), kEnumRtpTransceiverDirectionStopped); + default: + RTC_LOG(LS_WARNING) << "Invalid value: " << rtpTransceiver_->current_direction().value(); + break; + } + + return info.Env().Null(); +} + +Napi::Value NapiRtpTransceiver::Stop(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto error = rtpTransceiver_->StopStandard(); + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to stop: " << error.type() << ", " << error.message(); + NAPI_THROW(Error::New(info.Env(), "Failed to stop"), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiRtpTransceiver::SetCodecPreferences(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (info.Length() < 1) { + NAPI_THROW(Error::New(info.Env(), "Wrong number of arguments"), info.Env().Undefined()); + } + + if (!info[0].IsArray()) { + NAPI_THROW(Error::New(info.Env(), "First argument is not array"), info.Env().Undefined()); + } + + auto jsCodecs = info[0].As(); + auto codecs = std::vector(); + for (uint32_t i = 0; i < jsCodecs.Length(); i++) { + Napi::Value jsCodec = jsCodecs[i]; + RtpCodecCapability codec; + NapiRtpCodecCapability::JsToNative(jsCodec.As(), codec); + if (info.Env().IsExceptionPending()) { + NAPI_THROW(info.Env().GetAndClearPendingException(), info.Env().Undefined()); + } + codecs.push_back(codec); + } + + auto error = rtpTransceiver_->SetCodecPreferences(codecs); + if (!error.ok()) { + RTC_LOG(LS_ERROR) << "Failed to set codec preferences: " << error.type() << ", " << error.message(); + NAPI_THROW(Error::New(info.Env(), "Failed to set codec preferences"), info.Env().Undefined()); + } + + return info.Env().Undefined(); +} + +Napi::Value NapiRtpTransceiver::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiRtpTransceiver"); +#endif + + return json; +} + +void PopulateTransceiverInit(const Object& obj, webrtc::RtpTransceiverInit& init) +{ + // Handle direction + if (obj.Has(kAttributeNameDirection)) { + std::string direction = obj.Get(kAttributeNameDirection).As(); + if (direction == kEnumRtpTransceiverDirectionInactive) { + init.direction = webrtc::RtpTransceiverDirection::kInactive; + } else if (direction == kEnumRtpTransceiverDirectionRecvOnly) { + init.direction = webrtc::RtpTransceiverDirection::kRecvOnly; + } else if (direction == kEnumRtpTransceiverDirectionSendOnly) { + init.direction = webrtc::RtpTransceiverDirection::kSendOnly; + } else if (direction == kEnumRtpTransceiverDirectionSendRecv) { + init.direction = webrtc::RtpTransceiverDirection::kSendRecv; + } else if (direction == kEnumRtpTransceiverDirectionStopped) { + init.direction = webrtc::RtpTransceiverDirection::kStopped; + } + } + + // Handle streams + if (obj.Has(kAttributeNameStreams)) { + Array streamsArray = obj.Get(kAttributeNameStreams).As(); + for (uint32_t i = 0; i < streamsArray.Length(); i++) { + Value streamValue = streamsArray.Get(i); + if (streamValue.IsObject()) { + auto napiStream = NapiMediaStream::Unwrap(streamValue.As()); + auto stream = napiStream->Get(); + init.stream_ids.push_back(stream->id()); + } + } + } + + // Handle sendEncodings + if (obj.Has(kAttributeNameSendEncodings)) { + Array encodingsArray = obj.Get(kAttributeNameSendEncodings).As(); + for (uint32_t i = 0; i < encodingsArray.Length(); i++) { + Value encodingValue = encodingsArray.Get(i); + if (encodingValue.IsObject()) { + Object encodingObj = encodingValue.As(); + webrtc::RtpEncodingParameters encodingParams; + NapiRtpEncodingParameters::JsToNative(encodingObj, encodingParams); + init.send_encodings.push_back(encodingParams); + } + } + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/rtp_transceiver.h b/sdk/ohos/src/ohos_webrtc/rtp_transceiver.h new file mode 100644 index 0000000000000000000000000000000000000000..c93c3a59e8d1256cb56f2c4880501b85b4591345 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/rtp_transceiver.h @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_RTP_TRANSCEIVER_H +#define WEBRTC_RTP_TRANSCEIVER_H + +#include "napi.h" + +#include "api/rtp_transceiver_interface.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiRtpTransceiver : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance( + Napi::Env env, rtc::scoped_refptr transceiver, NapiPeerConnectionWrapper* pcWrapper); + + explicit NapiRtpTransceiver(const Napi::CallbackInfo& info); + + rtc::scoped_refptr Get() const; + +protected: + Napi::Value GetMid(const Napi::CallbackInfo& info); + Napi::Value GetSender(const Napi::CallbackInfo& info); + Napi::Value GetReceiver(const Napi::CallbackInfo& info); + Napi::Value GetDirection(const Napi::CallbackInfo& info); + void SetDirection(const Napi::CallbackInfo& info, const Napi::Value& value); + Napi::Value GetCurrentDirection(const Napi::CallbackInfo& info); + + Napi::Value Stop(const Napi::CallbackInfo& info); + Napi::Value SetCodecPreferences(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + rtc::scoped_refptr rtpTransceiver_; + NapiPeerConnectionWrapper* pcWrapper_{}; +}; + +void PopulateTransceiverInit(const Napi::Object& jsInit, webrtc::RtpTransceiverInit& init); + +} // namespace webrtc + +#endif // WEBRTC_RTP_TRANSCEIVER_H diff --git a/sdk/ohos/src/ohos_webrtc/sctp_transport.cpp b/sdk/ohos/src/ohos_webrtc/sctp_transport.cpp new file mode 100644 index 0000000000000000000000000000000000000000..040c479777134b87b1ecbda45561e46a9c9bb0ce --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/sctp_transport.cpp @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sctp_transport.h" +#include "dtls_transport.h" +#include "peer_connection.h" +#include "peer_connection_factory.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kEnumRTCSctpTransportStateConnecting[] = "connecting"; +const char kEnumRTCSctpTransportStateConnected[] = "connected"; +const char kEnumRTCSctpTransportStateClosed[] = "closed"; + +const char kAttributeNameMaxChannels[] = "maxChannels"; +const char kAttributeNameMaxMessageSize[] = "maxMessageSize"; +const char kAttributeNameTransport[] = "transport"; +const char kAttributeNameState[] = "state"; +const char kMethodNameToJson[] = "toJSON"; + +const char kEventStateChange[] = "statechange"; + +FunctionReference NapiSctpTransport::constructor_; + +void NapiSctpTransport::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, "RTCSctpTransport", + { + InstanceAccessor<&NapiSctpTransport::GetEventHandler, &NapiSctpTransport::SetEventHandler>( + "onstatechange", napi_default, (void*)kEventStateChange), + InstanceAccessor<&NapiSctpTransport::GetState>(kAttributeNameState), + InstanceAccessor<&NapiSctpTransport::GetTransport>(kAttributeNameTransport), + InstanceAccessor<&NapiSctpTransport::GetMaxChannels>(kAttributeNameMaxChannels), + InstanceAccessor<&NapiSctpTransport::GetMaxMessageSize>(kAttributeNameMaxMessageSize), + InstanceMethod<&NapiSctpTransport::ToJson>(kMethodNameToJson), + }); + exports.Set("RTCSctpTransport", func); + + constructor_ = Persistent(func); +} + +Napi::Object NapiSctpTransport::NewInstance( + Napi::Env env, rtc::scoped_refptr transport, NapiPeerConnectionWrapper* pcWrapper) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (!transport || !pcWrapper) { + NAPI_THROW(Error::New(env, "Invalid argument"), Object()); + } + + auto externalTransport = External::New( + env, transport.release(), [](Napi::Env, SctpTransportInterface* transport) { transport->Release(); }); + + auto externalPcWrapper = External::New(env, pcWrapper); + + return constructor_.New({externalTransport, externalPcWrapper}); +} + +NapiSctpTransport::NapiSctpTransport(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + if (info[0].IsExternal()) { + auto sctpTransport = info[0].As>().Data(); + sctpTransport_ = rtc::scoped_refptr(sctpTransport); + } + + if (info.Length() > 1 && info[1].IsExternal()) { + pcWrapper_ = info[1].As>().Data(); + } + + if (!pcWrapper_ || !sctpTransport_) { + NAPI_THROW_VOID(Error::New(info.Env(), "Invalid argument")); + } + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + factoryWrapper->GetNetworkThread()->BlockingCall([this] { + sctpTransport_->RegisterObserver(this); + }); +} + +NapiSctpTransport::~NapiSctpTransport() +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + + auto factoryWrapper = pcWrapper_->GetPeerConnectionFactoryWrapper(); + factoryWrapper->GetNetworkThread()->BlockingCall([this] { + sctpTransport_->UnregisterObserver(); + }); + + std::lock_guard lock(mutex_); + if (eventHandler_.tsfn) { + eventHandler_.tsfn.Release(); + } +} + +rtc::scoped_refptr NapiSctpTransport::Get() const +{ + return sctpTransport_; +} + +Napi::Value NapiSctpTransport::GetMaxChannels(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto maxChannels = sctpTransport_->Information().MaxChannels(); + if (maxChannels.has_value()) { + return Number::New(info.Env(), maxChannels.value()); + } + return info.Env().Undefined(); +} + +Napi::Value NapiSctpTransport::GetMaxMessageSize(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto maxMessageSize = sctpTransport_->Information().MaxMessageSize(); + if (maxMessageSize.has_value()) { + return Number::New(info.Env(), maxMessageSize.value()); + } + return info.Env().Undefined(); +} + +Napi::Value NapiSctpTransport::GetState(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + switch (sctpTransport_->Information().state()) { + case SctpTransportState::kConnecting: + return String::New(info.Env(), kEnumRTCSctpTransportStateConnecting); + case SctpTransportState::kConnected: + return String::New(info.Env(), kEnumRTCSctpTransportStateConnected); + case SctpTransportState::kClosed: + return String::New(info.Env(), kEnumRTCSctpTransportStateClosed); + default: + break; + } + + NAPI_THROW(Error::New(info.Env(), "Invalid state"), info.Env().Undefined()); +} + +Napi::Value NapiSctpTransport::GetTransport(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto transport = sctpTransport_->Information().dtls_transport(); + if (!transport) { + NAPI_THROW(Error::New(info.Env(), "No transport"), info.Env().Undefined()); + } + + return NapiDtlsTransport::NewInstance(info.Env(), transport, pcWrapper_); +} + +Napi::Value NapiSctpTransport::GetEventHandler(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + if (eventHandler_.ref.IsEmpty()) { + return info.Env().Null(); + } + return eventHandler_.ref.Value(); +} + +void NapiSctpTransport::SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + { + std::lock_guard lock(mutex_); + if (eventHandler_.tsfn) { + eventHandler_.tsfn.Release(); + } + } + + if (value.IsFunction()) { + auto fn = value.As(); + + Reference* context = new Reference; + *context = Persistent(info.This()); + + EventHandler handler; + handler.ref = Persistent(fn); + handler.tsfn = ThreadSafeFunction::New( + fn.Env(), fn, "SetEventHandler", 0, 1, context, [](Napi::Env env, Reference* ctx) { + ctx->Reset(); + delete ctx; + }); + std::lock_guard lock(mutex_); + eventHandler_ = std::move(handler); + } else if (value.IsNull()) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " value is null"; + } else { + NAPI_THROW_VOID(Error::New(info.Env(), "value is error")); + } +} + +void NapiSctpTransport::OnStateChange(SctpTransportInformation info) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + std::lock_guard lock(mutex_); + auto& tsfn = eventHandler_.tsfn; + + if (!tsfn) { + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " ThreadSafeFunction is not initialized."; + return; + } + + Reference* context = tsfn.GetContext(); + napi_status status = tsfn.NonBlockingCall([context](Napi::Env env, Napi::Function jsCallback) { + auto jsEvent = Object::New(env); + jsCallback.Call(context ? context->Value() : env.Undefined(), {jsEvent}); + }); + + if (status != napi_ok) { + RTC_LOG(LS_ERROR) << " tsfn call error: " << status; + } +} + +Napi::Value NapiSctpTransport::ToJson(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", String::New(info.Env(), "NapiSctpTransport")); +#endif + + return json; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/sctp_transport.h b/sdk/ohos/src/ohos_webrtc/sctp_transport.h new file mode 100644 index 0000000000000000000000000000000000000000..0d3cd3391d6d0d31ab2479ada90d4b18ebf5610b --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/sctp_transport.h @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_SCTP_TRANSPORT_H +#define WEBRTC_SCTP_TRANSPORT_H + +#include "napi.h" + +#include "api/sctp_transport_interface.h" + +namespace webrtc { + +class NapiPeerConnectionWrapper; + +class NapiSctpTransport : public Napi::ObjectWrap, public SctpTransportObserverInterface { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance( + Napi::Env env, rtc::scoped_refptr transport, NapiPeerConnectionWrapper* pcWrapper); + + ~NapiSctpTransport(); + + explicit NapiSctpTransport(const Napi::CallbackInfo& info); + + rtc::scoped_refptr Get() const; + +protected: + // JS + Napi::Value GetMaxChannels(const Napi::CallbackInfo& info); + Napi::Value GetMaxMessageSize(const Napi::CallbackInfo& info); + Napi::Value GetState(const Napi::CallbackInfo& info); + Napi::Value GetTransport(const Napi::CallbackInfo& info); + + Napi::Value GetEventHandler(const Napi::CallbackInfo& info); + void SetEventHandler(const Napi::CallbackInfo& info, const Napi::Value& value); + + Napi::Value ToJson(const Napi::CallbackInfo& info); + void OnStateChange(SctpTransportInformation info) override; + +private: + static Napi::FunctionReference constructor_; + + struct EventHandler { + Napi::FunctionReference ref; + Napi::ThreadSafeFunction tsfn; + }; + + mutable std::mutex mutex_; + EventHandler eventHandler_; + + rtc::scoped_refptr sctpTransport_; + NapiPeerConnectionWrapper* pcWrapper_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_SCTP_TRANSPORT_H diff --git a/sdk/ohos/src/ohos_webrtc/session_description.cpp b/sdk/ohos/src/ohos_webrtc/session_description.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b5cf23413d92b539f4f9a87db006f113b0f64266 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/session_description.cpp @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "session_description.h" + +#include "rtc_base/logging.h" + +namespace webrtc { + +using namespace Napi; + +const char kAttributeNameSdp[] = "sdp"; +const char kAttributeNameType[] = "type"; + +const char kMethodNameToJson[] = "toJSON"; + +const char kEnumSdpTypeOffer[] = "offer"; +const char kEnumSdpTypeAnswer[] = "answer"; +const char kEnumSdpTypePranswer[] = "pranswer"; +const char kEnumSdpTypeRollback[] = "rollback"; + +FunctionReference NapiSessionDescription::constructor_; + +void NapiSessionDescription::Init(Napi::Env env, Napi::Object exports) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, "RTCSessionDescription", + { + InstanceAccessor<&NapiSessionDescription::GetSdp>(kAttributeNameSdp), + InstanceAccessor<&NapiSessionDescription::GetType>(kAttributeNameType), + InstanceMethod<&NapiSessionDescription::ToJson>(kMethodNameToJson), + }); + + constructor_ = Persistent(func); + + exports.Set("RTCSessionDescription", func); +} + +Napi::Object NapiSessionDescription::NewInstance(Napi::Env /*env*/, Napi::Value arg) +{ + return constructor_.New({arg}); +} + +Napi::Object NapiSessionDescription::NewInstance(Napi::Env env, const std::string& sdp, SdpType type) +{ + auto jsSdp = Object::New(env); + jsSdp.Set(kAttributeNameSdp, String::New(env, sdp)); + switch (type) { + case SdpType::kOffer: + jsSdp.Set(kAttributeNameType, String::New(env, kEnumSdpTypeOffer)); + break; + case SdpType::kAnswer: + jsSdp.Set(kAttributeNameType, String::New(env, kEnumSdpTypeAnswer)); + break; + case SdpType::kPrAnswer: + jsSdp.Set(kAttributeNameType, String::New(env, kEnumSdpTypePranswer)); + break; + case SdpType::kRollback: + jsSdp.Set(kAttributeNameType, String::New(env, kEnumSdpTypeRollback)); + break; + default: + RTC_LOG(LS_WARNING) << "Invalid value of type"; + break; + } + + return constructor_.New({jsSdp}); +} + +NapiSessionDescription::NapiSessionDescription(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) +{ + RTC_LOG(LS_INFO) << "NapiSessionDescription::NapiSessionDescription info.Length()=" << info.Length(); + if (info.Length() > 0) { + auto jsSdpInit = info[0]; + if (jsSdpInit.IsObject()) { + auto jsSdpInitObj = jsSdpInit.As(); + if (jsSdpInitObj.Has(kAttributeNameSdp)) { + sdp_ = jsSdpInitObj.Get(kAttributeNameSdp).As().Utf8Value(); + } + type_ = jsSdpInitObj.Get(kAttributeNameType).As().Utf8Value(); + } + } +} + +Napi::Value NapiSessionDescription::GetSdp(const Napi::CallbackInfo& info) +{ + return String::New(info.Env(), sdp_); +} + +Napi::Value NapiSessionDescription::GetType(const Napi::CallbackInfo& info) +{ + return String::New(info.Env(), type_); +} + +Napi::Value NapiSessionDescription::ToJson(const Napi::CallbackInfo& info) +{ + auto jsSdp = Object::New(info.Env()); + jsSdp.Set(kAttributeNameSdp, String::New(info.Env(), sdp_)); + jsSdp.Set(kAttributeNameType, String::New(info.Env(), type_)); + return jsSdp; +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/session_description.h b/sdk/ohos/src/ohos_webrtc/session_description.h new file mode 100644 index 0000000000000000000000000000000000000000..92e94b7cfd10bc98908e0b40cc963acec6eafbe1 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/session_description.h @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_SESSION_DESCRIPTION_H +#define WEBRTC_SESSION_DESCRIPTION_H + +#include "napi.h" + +#include "api/jsep_session_description.h" + +namespace webrtc { + +class NapiSessionDescription : public Napi::ObjectWrap { +public: + static void Init(Napi::Env env, Napi::Object exports); + + static Napi::Object NewInstance(Napi::Env env, Napi::Value arg); + static Napi::Object NewInstance(Napi::Env env, const std::string& sdp, SdpType type); + + explicit NapiSessionDescription(const Napi::CallbackInfo& info); + +protected: + Napi::Value GetSdp(const Napi::CallbackInfo& info); + Napi::Value GetType(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::string sdp_; + std::string type_; +}; + +} // namespace webrtc + +#endif // WEBRTC_SESSION_DESCRIPTION_H diff --git a/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.cpp b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d65cb9deefc04981b483993fd7a2dee109f9deff --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.cpp @@ -0,0 +1,485 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_constraints.h" + +#include +#include +#include + +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +template +void MaybeEmitNamedValue(rtc::StringBuilder& builder, const char* name, std::optional value) +{ + if (!value) { + return; + } + if (builder.size() > 1) { + builder << ", "; + } + builder << name; + builder << ": "; + builder << *value; +} + +class MediaTrackConstraintsPrivate final { +public: + static std::shared_ptr Create(); + static std::shared_ptr + Create(const MediaTrackConstraintSet& basic, const std::vector& advanced); + + bool IsConstrained() const; + const MediaTrackConstraintSet& Basic() const; + MediaTrackConstraintSet& MutableBasic(); + const std::vector& Advanced() const; + const std::string ToString() const; + +private: + MediaTrackConstraintsPrivate( + const MediaTrackConstraintSet& basic, const std::vector& advanced); + + MediaTrackConstraintSet basic_; + std::vector advanced_; +}; + +BaseConstraint::BaseConstraint(const char* name) : name_(name) {} + +BaseConstraint::~BaseConstraint() = default; + +bool BaseConstraint::HasMandatory() const +{ + return HasMin() || HasMax() || HasExact(); +} + +LongConstraint::LongConstraint(const char* name) : BaseConstraint(name) {} + +bool LongConstraint::IsConstrained() const +{ + return min_.has_value() || max_.has_value() || exact_.has_value() || ideal_.has_value(); +} + +bool LongConstraint::Matches(int32_t value) const +{ + if (min_.has_value() && value < *min_) { + return false; + } + if (max_.has_value() && value > *max_) { + return false; + } + if (exact_.has_value() && value != *exact_) { + return false; + } + return true; +} + +void LongConstraint::Reset() +{ + *this = LongConstraint(GetName()); +} + +std::string LongConstraint::ToString() const +{ + rtc::StringBuilder builder; + + builder << "{"; + MaybeEmitNamedValue(builder, "min", min_); + MaybeEmitNamedValue(builder, "max", max_); + MaybeEmitNamedValue(builder, "exact", exact_); + MaybeEmitNamedValue(builder, "ideal", ideal_); + builder << "}"; + + return builder.str(); +} + +const double DoubleConstraint::kConstraintEpsilon = 0.00001; + +DoubleConstraint::DoubleConstraint(const char* name) : BaseConstraint(name) {} + +bool DoubleConstraint::IsConstrained() const +{ + return min_.has_value() || max_.has_value() || exact_.has_value() || ideal_.has_value(); +} + +void DoubleConstraint::Reset() +{ + *this = DoubleConstraint(GetName()); +} + +std::string DoubleConstraint::ToString() const +{ + rtc::StringBuilder builder; + + builder << "{"; + MaybeEmitNamedValue(builder, "min", min_); + MaybeEmitNamedValue(builder, "max", max_); + MaybeEmitNamedValue(builder, "exact", exact_); + MaybeEmitNamedValue(builder, "ideal", ideal_); + builder << "}"; + + return builder.str(); +} + +bool DoubleConstraint::Matches(int32_t value) const +{ + if (min_.has_value() && value < *min_ - kConstraintEpsilon) { + return false; + } + if (max_.has_value() && value > *max_ + kConstraintEpsilon) { + return false; + } + if (exact_.has_value() && fabs(static_cast(value) - *exact_) > kConstraintEpsilon) { + return false; + } + return true; +} + +StringConstraint::StringConstraint(const char* name) : BaseConstraint(name) {} + +bool StringConstraint::IsConstrained() const +{ + return !exact_.empty() || !ideal_.empty(); +} + +void StringConstraint::Reset() +{ + *this = StringConstraint(GetName()); +} + +std::string StringConstraint::ToString() const +{ + rtc::StringBuilder builder; + builder << "{"; + if (!ideal_.empty()) { + builder << "ideal: ["; + bool first = true; + for (const auto& iter : ideal_) { + if (!first) { + builder << ", "; + } + builder << "\""; + builder << iter; + builder << "\""; + first = false; + } + builder << "]"; + } + if (!exact_.empty()) { + if (builder.size() > 1) { + builder << ", "; + } + builder << "exact: ["; + bool first = true; + for (const auto& iter : exact_) { + if (!first) { + builder << ", "; + } + builder << "\""; + builder << iter; + builder << "\""; + } + builder << "]"; + } + builder << "}"; + + return builder.str(); +} + +bool StringConstraint::Matches(std::string value) const +{ + if (exact_.empty()) { + return true; + } + for (const auto& choice : exact_) { + if (value == choice) { + return true; + } + } + return false; +} + +BooleanConstraint::BooleanConstraint(const char* name) : BaseConstraint(name) {} + +bool BooleanConstraint::IsConstrained() const +{ + return ideal_.has_value() || exact_.has_value(); +} + +bool BooleanConstraint::Matches(bool value) const +{ + if (exact_ && *exact_ != value) { + return false; + } + return true; +} + +void BooleanConstraint::Reset() +{ + *this = BooleanConstraint(GetName()); +} + +std::string BooleanConstraint::ToString() const +{ + rtc::StringBuilder builder; + builder << "{"; + if (exact_) { + builder << "exact: " << (*exact_ ? "true" : "false"); + } + + if (ideal_) { + if (exact_) { + builder << ", "; + } + builder << "ideal: " << (*ideal_ ? "true" : "false"); + } + builder << "}"; + + return builder.str(); +} + +MediaTrackConstraintSet::MediaTrackConstraintSet() + : width("width"), + height("height"), + aspectRatio("aspectRatio"), + frameRate("frameRate"), + facingMode("facingMode"), + resizeMode("resizeMode"), + sampleRate("sampleRate"), + sampleSize("sampleSize"), + echoCancellation("echoCancellation"), + autoGainControl("autoGainControl"), + noiseSuppression("noiseSuppression"), + latency("latency"), + channelCount("channelCount"), + deviceId("deviceId"), + groupId("groupId"), + backgroundBlur("backgroundBlur"), + displaySurface("displaySurface"), + googEchoCancellation("googEchoCancellation"), + googAutoGainControl("autoGainControl"), + googNoiseSuppression("noiseSuppression"), + googHighpassFilter("googHighpassFilter"), + googAudioMirroring("googAudioMirroring") +{ +} + +bool MediaTrackConstraintSet::IsConstrained() const +{ + for (auto* const constraint : AllConstraints()) { + if (constraint->IsConstrained()) { + return true; + } + } + return false; +} + +std::vector MediaTrackConstraintSet::AllConstraints() const +{ + return { + &width, + &height, + &aspectRatio, + &frameRate, + &facingMode, + &resizeMode, + &sampleRate, + &sampleSize, + &echoCancellation, + &autoGainControl, + &noiseSuppression, + &latency, + &channelCount, + &deviceId, + &groupId, + &googEchoCancellation, + &googAutoGainControl, + &googNoiseSuppression, + &googHighpassFilter, + &googAudioMirroring, + }; +} + +bool MediaTrackConstraintSet::HasMin() const +{ + for (auto* const constraint : AllConstraints()) { + if (constraint->HasMin()) { + return true; + } + } + return false; +} + +bool MediaTrackConstraintSet::HasExact() const +{ + for (auto* const constraint : AllConstraints()) { + if (constraint->HasExact()) { + return true; + } + } + return false; +} + +std::string MediaTrackConstraintSet::ToString() const +{ + rtc::StringBuilder builder; + bool first = true; + for (auto* const constraint : AllConstraints()) { + if (constraint->IsConstrained()) { + if (!first) { + builder << ", "; + } + builder << constraint->GetName(); + builder << ": "; + builder << constraint->ToString(); + first = false; + } + } + + return builder.str(); +} + +std::shared_ptr MediaTrackConstraintsPrivate::Create() +{ + MediaTrackConstraintSet basic; + std::vector advanced; + return std::shared_ptr(new MediaTrackConstraintsPrivate(basic, advanced)); +} + +std::shared_ptr MediaTrackConstraintsPrivate::Create( + const MediaTrackConstraintSet& basic, const std::vector& advanced) +{ + return std::shared_ptr(new MediaTrackConstraintsPrivate(basic, advanced)); +} + +MediaTrackConstraintsPrivate::MediaTrackConstraintsPrivate( + const MediaTrackConstraintSet& basic, const std::vector& advanced) + : basic_(basic), advanced_(advanced) +{ +} + +bool MediaTrackConstraintsPrivate::IsConstrained() const +{ + return basic_.IsConstrained() || !advanced_.empty(); +} + +const MediaTrackConstraintSet& MediaTrackConstraintsPrivate::Basic() const +{ + return basic_; +} + +MediaTrackConstraintSet& MediaTrackConstraintsPrivate::MutableBasic() +{ + return basic_; +} + +const std::vector& MediaTrackConstraintsPrivate::Advanced() const +{ + return advanced_; +} + +const std::string MediaTrackConstraintsPrivate::ToString() const +{ + rtc::StringBuilder builder; + if (IsConstrained()) { + builder << "{"; + builder << Basic().ToString(); + if (!Advanced().empty()) { + if (builder.size() > 1) { + builder << ", "; + } + builder << "advanced: ["; + bool first = true; + for (const auto& constraint_set : Advanced()) { + if (!first) { + builder << ", "; + } + builder << "{"; + builder << constraint_set.ToString(); + builder << "}"; + first = false; + } + builder << "]"; + } + builder << "}"; + } + + return builder.str(); +} + +void MediaTrackConstraints::Assign(const MediaTrackConstraints& other) +{ + private_ = other.private_; +} + +MediaTrackConstraints::MediaTrackConstraints() = default; + +MediaTrackConstraints::MediaTrackConstraints(const MediaTrackConstraints& other) +{ + Assign(other); +} + +void MediaTrackConstraints::Reset() +{ + private_.reset(); +} + +bool MediaTrackConstraints::IsConstrained() const +{ + return private_ && private_->IsConstrained(); +} + +void MediaTrackConstraints::Initialize() +{ + RTC_DCHECK(IsNull()); + private_ = MediaTrackConstraintsPrivate::Create(); +} + +void MediaTrackConstraints::Initialize( + const MediaTrackConstraintSet& basic, const std::vector& advanced) +{ + RTC_DCHECK(IsNull()); + private_ = MediaTrackConstraintsPrivate::Create(basic, advanced); +} + +const MediaTrackConstraintSet& MediaTrackConstraints::Basic() const +{ + RTC_DCHECK(!IsNull()); + return private_->Basic(); +} + +MediaTrackConstraintSet& MediaTrackConstraints::MutableBasic() +{ + RTC_DCHECK(!IsNull()); + return private_->MutableBasic(); +} + +const std::vector& MediaTrackConstraints::Advanced() const +{ + RTC_DCHECK(!IsNull()); + return private_->Advanced(); +} + +const std::string MediaTrackConstraints::ToString() const +{ + if (IsNull()) { + return std::string(""); + } + return private_->ToString(); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.h b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.h new file mode 100644 index 0000000000000000000000000000000000000000..e1d78b9a199a4ee10860285e68163ad7f50377c1 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints.h @@ -0,0 +1,391 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_MEDIA_CONSTRAINTS_H +#define WEBRTC_MEDIA_CONSTRAINTS_H + +#include +#include +#include +#include + +#include "api/audio_options.h" + +namespace webrtc { + +class MediaTrackConstraintsPrivate; + +class BaseConstraint { +public: + explicit BaseConstraint(const char* name); + virtual ~BaseConstraint(); + + virtual bool IsConstrained() const = 0; + virtual void Reset() = 0; + virtual bool HasExact() const = 0; + virtual std::string ToString() const = 0; + + bool HasMandatory() const; + + virtual bool HasMin() const + { + return false; + } + + virtual bool HasMax() const + { + return false; + } + + const char* GetName() const + { + return name_; + } + +private: + const char* name_; +}; + +class LongConstraint : public BaseConstraint { +public: + explicit LongConstraint(const char* name); + + bool IsConstrained() const override; + void Reset() override; + std::string ToString() const override; + bool Matches(int32_t value) const; + + void SetMin(int32_t value) + { + min_ = value; + } + + void SetMax(int32_t value) + { + max_ = value; + } + + void SetExact(int32_t value) + { + exact_ = value; + } + + void SetIdeal(int32_t value) + { + ideal_ = value; + } + + bool HasMin() const override + { + return min_.has_value(); + } + + bool HasMax() const override + { + return max_.has_value(); + } + + bool HasExact() const override + { + return exact_.has_value(); + } + + int32_t Min() const + { + return min_.value(); + } + + int32_t Max() const + { + return max_.value(); + } + + int32_t Exact() const + { + return exact_.value(); + } + + bool HasIdeal() const + { + return ideal_.has_value(); + } + + int32_t Ideal() const + { + return ideal_.value(); + } + +private: + std::optional min_; + std::optional max_; + std::optional exact_; + std::optional ideal_; +}; + +class DoubleConstraint : public BaseConstraint { +public: + // Permit a certain leeway when comparing floats. The offset of 0.00001 + // is chosen based on observed behavior of doubles formatted with + // rtc::ToString. + static const double kConstraintEpsilon; + + explicit DoubleConstraint(const char* name); + + bool IsConstrained() const override; + void Reset() override; + std::string ToString() const override; + bool Matches(int32_t value) const; + + void SetMin(double value) + { + min_ = value; + } + + void SetMax(double value) + { + max_ = value; + } + + void SetExact(double value) + { + exact_ = value; + } + + void SetIdeal(double value) + { + ideal_ = value; + } + + bool HasMin() const override + { + return min_.has_value(); + } + bool HasMax() const override + { + return max_.has_value(); + } + bool HasExact() const override + { + return exact_.has_value(); + } + + double Min() const + { + return min_.value(); + } + double Max() const + { + return max_.value(); + } + double Exact() const + { + return exact_.value(); + } + bool HasIdeal() const + { + return ideal_.has_value(); + } + double Ideal() const + { + return ideal_.value(); + } + +private: + std::optional min_; + std::optional max_; + std::optional exact_; + std::optional ideal_; +}; + +class StringConstraint : public BaseConstraint { +public: + // String-valued options don't have min or max, but can have multiple + // values for ideal and exact. + explicit StringConstraint(const char* name); + + bool IsConstrained() const override; + void Reset() override; + std::string ToString() const override; + bool Matches(std::string value) const; + + void SetExact(const std::string& exact) + { + exact_ = {exact}; + } + + void SetExact(const std::vector& exact) + { + exact_ = exact; + } + + void SetIdeal(const std::string& ideal) + { + ideal_ = {ideal}; + } + + void SetIdeal(const std::vector& ideal) + { + ideal_ = ideal; + } + + bool HasExact() const override + { + return !exact_.empty(); + } + + bool HasIdeal() const + { + return !ideal_.empty(); + } + + const std::vector& Exact() const + { + return exact_; + } + + const std::vector& Ideal() const + { + return ideal_; + } + +private: + std::vector exact_; + std::vector ideal_; +}; + +class BooleanConstraint : public BaseConstraint { +public: + explicit BooleanConstraint(const char* name); + + bool IsConstrained() const override; + bool Matches(bool value) const; + std::string ToString() const override; + + void Reset() override; + + bool Exact() const + { + return exact_.value(); + } + + bool Ideal() const + { + return ideal_.value(); + } + + void SetIdeal(bool value) + { + ideal_ = value; + } + + void SetExact(bool value) + { + exact_ = value; + } + + bool HasExact() const override + { + return exact_.has_value(); + } + + bool HasIdeal() const + { + return ideal_.has_value(); + } + +private: + std::optional ideal_{}; + std::optional exact_{}; +}; + +struct MediaTrackConstraintSet { +public: + MediaTrackConstraintSet(); + + bool IsConstrained() const; + + LongConstraint width; + LongConstraint height; + DoubleConstraint aspectRatio; + DoubleConstraint frameRate; + StringConstraint facingMode; + StringConstraint resizeMode; + LongConstraint sampleRate; + LongConstraint sampleSize; + BooleanConstraint echoCancellation; + BooleanConstraint autoGainControl; + BooleanConstraint noiseSuppression; + DoubleConstraint latency; + LongConstraint channelCount; + StringConstraint deviceId; + StringConstraint groupId; + BooleanConstraint backgroundBlur; + StringConstraint displaySurface; + BooleanConstraint googEchoCancellation; + BooleanConstraint googAutoGainControl; + BooleanConstraint googNoiseSuppression; + BooleanConstraint googHighpassFilter; + BooleanConstraint googAudioMirroring; + + bool HasMin() const; + bool HasExact() const; + + std::string ToString() const; + +private: + std::vector AllConstraints() const; +}; + +class MediaTrackConstraints { +public: + MediaTrackConstraints(); + MediaTrackConstraints(const MediaTrackConstraints& other); + ~MediaTrackConstraints() + { + Reset(); + } + + MediaTrackConstraints& operator=(const MediaTrackConstraints& other) + { + Assign(other); + return *this; + } + + bool IsConstrained() const; + + void Initialize(); + void Initialize(const MediaTrackConstraintSet& basic, const std::vector& advanced); + + void Assign(const MediaTrackConstraints&); + + void Reset(); + bool IsNull() const + { + return private_ == nullptr; + } + + const MediaTrackConstraintSet& Basic() const; + MediaTrackConstraintSet& MutableBasic(); + const std::vector& Advanced() const; + + const std::string ToString() const; + +private: + std::shared_ptr private_; +}; + +} // namespace webrtc + +#endif // WEBRTC_MEDIA_CONSTRAINTS_H diff --git a/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.cpp b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.cpp new file mode 100644 index 0000000000000000000000000000000000000000..18e0cb163991374c3fb9d429a8fdab8565c15cd0 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.cpp @@ -0,0 +1,584 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_constraints_util.h" + +#include +#include +#include +#include + +#include "rtc_base/logging.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +static constexpr int kMaxDimension = std::numeric_limits::max(); +static constexpr int kMaxFrameRate = 1000; + +class CandidateSettings; + +template +bool ConstraintHasMax(const ConstraintType& constraint) +{ + return constraint.HasMax() || constraint.HasExact(); +} + +template +bool ConstraintHasMin(const ConstraintType& constraint) +{ + return constraint.HasMin() || constraint.HasExact(); +} + +template +auto ConstraintMax(const ConstraintType& constraint) -> decltype(constraint.Max()) +{ + RTC_DCHECK(ConstraintHasMax(constraint)); + return constraint.HasExact() ? constraint.Exact() : constraint.Max(); +} + +template +auto ConstraintMin(const ConstraintType& constraint) -> decltype(constraint.Min()) +{ + RTC_DCHECK(ConstraintHasMin(constraint)); + return constraint.HasExact() ? constraint.Exact() : constraint.Min(); +} + +template +class NumericRangeSet { +public: + NumericRangeSet() = default; + NumericRangeSet(std::optional min, std::optional max) : min_(std::move(min)), max_(std::move(max)) {} + NumericRangeSet(const NumericRangeSet& other) = default; + NumericRangeSet& operator=(const NumericRangeSet& other) = default; + ~NumericRangeSet() = default; + + const std::optional& Min() const + { + return min_; + } + const std::optional& Max() const + { + return max_; + } + bool IsEmpty() const + { + return max_ && min_ && *max_ < *min_; + } + + NumericRangeSet Intersection(const NumericRangeSet& other) const + { + std::optional min = min_; + if (other.Min()) + min = min ? std::max(*min, *other.Min()) : other.Min(); + + std::optional max = max_; + if (other.Max()) + max = max ? std::min(*max, *other.Max()) : other.Max(); + + return NumericRangeSet(min, max); + } + + bool Contains(T value) const + { + return (!Min() || value >= *Min()) && (!Max() || value <= *Max()); + } + + template + static NumericRangeSet FromConstraint(ConstraintType constraint, T lowerBound, T upperBound) + { + RTC_DCHECK_LE(lowerBound, upperBound); + if ((ConstraintHasMax(constraint) && ConstraintMax(constraint) < lowerBound) || + (ConstraintHasMin(constraint) && ConstraintMin(constraint) > upperBound)) + { + return NumericRangeSet(1, 0); + } + + return NumericRangeSet( + ConstraintHasMin(constraint) && ConstraintMin(constraint) >= lowerBound ? ConstraintMin(constraint) + : std::optional(), + ConstraintHasMax(constraint) && ConstraintMax(constraint) <= upperBound ? ConstraintMax(constraint) + : std::optional()); + } + + template + static NumericRangeSet FromConstraint(ConstraintType constraint) + { + return NumericRangeSet( + ConstraintHasMin(constraint) ? ConstraintMin(constraint) : std::optional(), + ConstraintHasMax(constraint) ? ConstraintMax(constraint) : std::optional()); + } + + static NumericRangeSet FromValue(T value) + { + return NumericRangeSet(value, value); + } + + static NumericRangeSet EmptySet() + { + return NumericRangeSet(1, 0); + } + +private: + std::optional min_; + std::optional max_; +}; + +using DoubleRangeSet = NumericRangeSet; +using IntRangeSet = NumericRangeSet; + +double SquareEuclideanDistance(double x1, double y1, double x2, double y2) +{ + double x = x1 - x2; + double y = y1 - y2; + return x * 2 + y * y; +} + +std::string FacingModeToString(FacingMode facingMode) +{ + switch (facingMode) { + case FacingMode::kUser: + return "user"; + case FacingMode::kEnvironment: + return "environment"; + default: + return ""; + } +} + +void UpdateFailedConstraintName(const BaseConstraint& constraint, std::string* failedConstraintName) +{ + if (failedConstraintName) { + *failedConstraintName = constraint.GetName(); + } +} + +double NumericConstraintFitnessDistance(double value1, double value2) +{ + if (std::fabs(value1 - value2) <= DoubleConstraint::kConstraintEpsilon) { + return 0.0; + } + + return std::fabs(value1 - value2) / std::max(std::fabs(value1), std::fabs(value2)); +} + +template +double NumericValueFitness(const NumericConstraint& constraint, decltype(constraint.Min()) value) +{ + return constraint.HasIdeal() ? NumericConstraintFitnessDistance(value, constraint.Ideal()) : 0.0; +} + +template +double +NumericRangeFitness(const NumericConstraint& constraint, decltype(constraint.Min()) min, decltype(constraint.Min()) max) +{ + if (constraint.HasIdeal()) { + auto ideal = constraint.Ideal(); + if (ideal < min) { + return NumericConstraintFitnessDistance(min, ideal); + } else if (ideal > max) { + return NumericConstraintFitnessDistance(max, ideal); + } + } + + return 0.0; +} + +double StringConstraintFitnessDistance(const std::string& value, const StringConstraint& constraint) +{ + if (!constraint.HasIdeal()) { + return 0.0; + } + + for (auto& ideal_value : constraint.Ideal()) { + if (value == ideal_value) { + return 0.0; + } + } + + return 1.0; +} + +class CandidateSettings { +public: + CandidateSettings(std::string deviceId, std::string groupId, FacingMode facingMode, video::VideoProfile profile) + : deviceId_(deviceId), + groupId_(groupId), + facingMode_(facingMode), + profile_(profile), + targetWidth_(profile.resolution.width), + targetHeight_(profile.resolution.height), + targetAspectRatio_(static_cast(profile.resolution.width) / profile.resolution.height), + targetFrameRate_({profile.frameRateRange.min, profile.frameRateRange.max}) + { + } + + double Fitness(const MediaTrackConstraintSet& constraintSet) + { + return DeviceFitness(constraintSet) + ProfileFitness(constraintSet); + } + + double DeviceFitness(const MediaTrackConstraintSet& constraintSet) + { + return StringConstraintFitnessDistance(deviceId_, constraintSet.deviceId) + + StringConstraintFitnessDistance(groupId_, constraintSet.groupId) + + StringConstraintFitnessDistance(FacingModeToString(facingMode_), constraintSet.facingMode); + } + + double ProfileFitness(const MediaTrackConstraintSet& constraintSet) + { + return NumericValueFitness(constraintSet.width, targetWidth_) + + NumericValueFitness(constraintSet.height, targetHeight_) + + NumericValueFitness(constraintSet.aspectRatio, targetAspectRatio_) + + NumericRangeFitness(constraintSet.frameRate, targetFrameRate_.min, targetFrameRate_.max); + } + + bool + ApplyConstraintSet(const MediaTrackConstraintSet& constraintSet, std::string* failedConstraintName = nullptr) + { + // resizeMode is not supported + auto constrainedWidth = IntRangeSet::FromConstraint(constraintSet.width); + if (!constrainedWidth.Contains(targetWidth_)) { + UpdateFailedConstraintName(constraintSet.width, failedConstraintName); + return false; + } + + auto constrainedHeight = IntRangeSet::FromConstraint(constraintSet.height); + if (!constrainedHeight.Contains(targetHeight_)) { + UpdateFailedConstraintName(constraintSet.height, failedConstraintName); + return false; + } + + auto constrainedAspectRatio = DoubleRangeSet::FromConstraint(constraintSet.aspectRatio); + if (!constrainedAspectRatio.Contains(targetAspectRatio_)) { + UpdateFailedConstraintName(constraintSet.aspectRatio, failedConstraintName); + return false; + } + + auto constrainedFrameRate = + DoubleRangeSet::FromConstraint(constraintSet.frameRate, MinFrameRate(), MaxFrameRate()); + if (constrainedFrameRate.IsEmpty()) { + UpdateFailedConstraintName(constraintSet.frameRate, failedConstraintName); + return false; + } + if (constrainedFrameRate.Min()) { + profile_.frameRateRange.min = constrainedFrameRate.Min().value(); + } + if (constrainedFrameRate.Max()) { + profile_.frameRateRange.max = constrainedFrameRate.Max().value(); + } + + constrainedWidth_ = + constrainedWidth_.Intersection(IntRangeSet::FromConstraint(constraintSet.width, 1L, kMaxDimension)); + constrainedHeight_ = + constrainedHeight_.Intersection(IntRangeSet::FromConstraint(constraintSet.height, 1L, kMaxDimension)); + constrainedAspectRatio_ = constrainedAspectRatio_.Intersection( + DoubleRangeSet::FromConstraint(constraintSet.aspectRatio, 0.0, HUGE_VAL)); + constrainedFrameRate_ = constrainedFrameRate_.Intersection( + DoubleRangeSet::FromConstraint(constraintSet.frameRate, 0.0, kMaxFrameRate)); + + return true; + } + + bool SatisfiesFrameRateConstraint(const DoubleConstraint& constraint) const + { + double constraintMin = ConstraintHasMin(constraint) ? ConstraintMin(constraint) : -1.0; + double constraintMax = + ConstraintHasMax(constraint) ? ConstraintMax(constraint) : static_cast(kMaxFrameRate); + bool constraintMinOutOfRange = constraintMin > MaxFrameRate(); + bool constraintMaxOutOfRange = constraintMax < MinFrameRate(); + bool constraintSelfContradicts = constraintMin > constraintMax; + + return !constraintMinOutOfRange && !constraintMaxOutOfRange && !constraintSelfContradicts; + } + + CameraCaptureSettings GetSetting() + { + CameraCaptureSettings setting; + + setting.deviceId = deviceId_; + setting.profile.resolution.width = targetWidth_; + setting.profile.resolution.height = targetHeight_; + setting.profile.frameRateRange.min = targetFrameRate_.min; + setting.profile.frameRateRange.max = targetFrameRate_.max; + setting.profile.format = profile_.format; + + return setting; + } + + int NativeWidth() const + { + return profile_.resolution.width; + } + int NativeHeight() const + { + return profile_.resolution.height; + } + double NativeAspectRatio() const + { + RTC_DCHECK(NativeWidth() > 0 || NativeHeight() > 0); + return static_cast(NativeWidth()) / NativeHeight(); + } + video::FrameRateRange NativeFrameRateRange() const + { + return profile_.frameRateRange; + } + + const std::optional& MinFrameRateConstraint() const + { + return constrainedFrameRate_.Min(); + } + const std::optional& MaxFrameRateConstraint() const + { + return constrainedFrameRate_.Max(); + } + + double MaxFrameRate() const + { + if (MaxFrameRateConstraint()) { + return std::min(*MaxFrameRateConstraint(), static_cast(profile_.frameRateRange.max)); + } + return static_cast(profile_.frameRateRange.max); + } + + double MinFrameRate() const + { + if (MinFrameRateConstraint()) { + return std::max(*MinFrameRateConstraint(), static_cast(profile_.frameRateRange.min)); + } + return static_cast(profile_.frameRateRange.min); + } + +private: + std::string deviceId_; + std::string groupId_; + FacingMode facingMode_; + video::VideoProfile profile_; + + uint32_t targetWidth_; + uint32_t targetHeight_; + double targetAspectRatio_; + video::FrameRateRange targetFrameRate_; + + NumericRangeSet constrainedWidth_; + NumericRangeSet constrainedHeight_; + NumericRangeSet constrainedAspectRatio_; + NumericRangeSet constrainedFrameRate_; +}; + +bool FacingModeSatisfiesConstraint(webrtc::FacingMode value, const webrtc::StringConstraint& constraint) +{ + std::string stringValue = FacingModeToString(value); + if (stringValue.empty()) { + return constraint.Exact().empty(); + } + + return constraint.Matches(stringValue); +} + +bool DeviceSatisfiesConstraintSet( + const CameraDeviceInfo& device, const MediaTrackConstraintSet& constraintSet, std::string* failedConstraintName) +{ + if (!constraintSet.deviceId.Matches(device.deviceId)) { + UpdateFailedConstraintName(constraintSet.deviceId, failedConstraintName); + return false; + } + + if (!constraintSet.groupId.Matches(device.groupId)) { + UpdateFailedConstraintName(constraintSet.groupId, failedConstraintName); + return false; + } + + if (!FacingModeSatisfiesConstraint(device.facingMode, constraintSet.facingMode)) { + UpdateFailedConstraintName(constraintSet.facingMode, failedConstraintName); + return false; + } + + return true; +} + +bool SelectSettingsForVideo( + const std::vector& devices, const MediaTrackConstraints& constraints, int defaultWidth, + int defaultHeight, double defaultFrameRate, CameraCaptureSettings& setting, std::string& failedConstraintName) +{ + RTC_DLOG(LS_INFO) << __FUNCTION__; + RTC_DLOG(LS_VERBOSE) << "Constraints: " << constraints.ToString(); + + // This function works only if infinity is defined for the double type. + static_assert(std::numeric_limits::has_infinity, "Requires infinity"); + + uint32_t success = 0; + std::vector bestDistance(constraints.Advanced().size() + 4); + std::fill(bestDistance.begin(), bestDistance.end(), HUGE_VAL); + + for (auto& device : devices) { + RTC_DLOG(LS_INFO) << "device: " << device.deviceId << ", " << device.groupId << ", " + << FacingModeToString(device.facingMode); + if (!DeviceSatisfiesConstraintSet(device, constraints.Basic(), &failedConstraintName)) { + RTC_DLOG(LS_ERROR) << "Failed to satisfies basic constraints: " << failedConstraintName; + continue; + } + + for (auto& profile : device.profiles) { + RTC_DLOG(LS_INFO) << "-- profile: " << profile.resolution.width << "x" << profile.resolution.height << "," + << profile.frameRateRange.min << "-" << profile.frameRateRange.max << ", " + << profile.format; + CandidateSettings candidate(device.deviceId, device.groupId, device.facingMode, profile); + if (!candidate.ApplyConstraintSet(constraints.Basic(), &failedConstraintName)) { + RTC_DLOG(LS_ERROR) << "Failed to apply basic constraints: " << failedConstraintName; + continue; + } + + // At this point we have a candidate that satisfies all basic constraints. + std::vector candidateDistanceVector; + + // 1. satisfaction of advanced constraint sets. + for (const auto& advancedSet : constraints.Advanced()) { + bool satisfiesAdvancedSet = false; + + if (DeviceSatisfiesConstraintSet(device, advancedSet, nullptr)) { + if (candidate.ApplyConstraintSet(advancedSet, nullptr)) { + satisfiesAdvancedSet = true; + } else { + RTC_DLOG(LS_ERROR) << "Failed to apply advanced constraints"; + } + } else { + RTC_DLOG(LS_ERROR) << "Failed to satisfies advanced constraints"; + } + + candidateDistanceVector.push_back(satisfiesAdvancedSet ? 0 : HUGE_VAL); + } + + // 2. fitness distance. + candidateDistanceVector.push_back(candidate.Fitness(constraints.Basic())); + + // 3. default resolution + candidateDistanceVector.push_back(SquareEuclideanDistance( + candidate.NativeWidth(), candidate.NativeHeight(), defaultWidth, defaultHeight)); + + // 4. default frame rate + double frameRateDistance = 0.0; + if (defaultFrameRate < candidate.NativeFrameRateRange().min) { + frameRateDistance = + NumericConstraintFitnessDistance(candidate.NativeFrameRateRange().min, defaultFrameRate); + } else if (defaultFrameRate > candidate.NativeFrameRateRange().max) { + frameRateDistance = + NumericConstraintFitnessDistance(candidate.NativeFrameRateRange().max, defaultFrameRate); + } + candidateDistanceVector.push_back(frameRateDistance); + + // 5. order in devices + for (std::size_t i = 0; i < devices.size(); ++i) { + if (device.deviceId == devices[i].deviceId) { + candidateDistanceVector.push_back(i); + break; + } + } + +#ifndef NDEBUG + { + rtc::StringBuilder builder; + builder << "["; + for (auto& distance : candidateDistanceVector) { + if (builder.size() > 1) { + builder << ", "; + } + builder << distance; + } + builder << "]"; + RTC_DLOG(LS_INFO) << "candidateDistanceVector: " << builder.str(); + } +#endif + RTC_DCHECK_EQ(bestDistance.size(), candidateDistanceVector.size()); + if (std::lexicographical_compare( + candidateDistanceVector.begin(), candidateDistanceVector.end(), bestDistance.begin(), + bestDistance.end())) + { + bestDistance = candidateDistanceVector; +#ifndef NDEBUG + { + rtc::StringBuilder builder; + builder << "["; + for (auto& distance : bestDistance) { + if (builder.size() > 1) { + builder << ", "; + } + builder << distance; + } + builder << "]"; + RTC_DLOG(LS_INFO) << "bestDistance: " << builder.str(); + } +#endif + setting = candidate.GetSetting(); + success = 1; + } + } + } + RTC_DLOG(LS_INFO) << "success: " << success; + + return success > 0; +} + +template +bool GetConstraint(const ConstraintType& constraint, T* value, size_t* mandatoryConstraints) +{ + if (constraint.HasExact()) { + *value = constraint.Exact(); + return true; + } + if (constraint.HasIdeal()) { + *value = constraint.Ideal(); + return true; + } + return false; +} + +template +void ConstraintToOptional(const ConstraintType& constraint, absl::optional* value_out) +{ + T value; + bool present = GetConstraint(constraint, &value, nullptr); + if (present) { + *value_out = value; + } +} + +void CopyConstraintsIntoAudioOptions(const MediaTrackConstraints& constraints, cricket::AudioOptions& options) +{ + if (constraints.IsNull()) { + return; + } + + // ignore advanced constraints + auto& basicSet = constraints.Basic(); + ConstraintToOptional(basicSet.echoCancellation, &options.echo_cancellation); + ConstraintToOptional(basicSet.autoGainControl, &options.auto_gain_control); + ConstraintToOptional(basicSet.noiseSuppression, &options.noise_suppression); +} + +void GetScreenCaptureConstraints(const MediaTrackConstraints& constraints, int& width, int& height) +{ + if (constraints.IsNull()) { + return; + } + + // ignore advanced constraints + auto& basicSet = constraints.Basic(); + + GetConstraint(basicSet.width, &width, nullptr); + GetConstraint(basicSet.height, &height, nullptr); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.h b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.h new file mode 100644 index 0000000000000000000000000000000000000000..d3eb05f099480587c42c8cb582d68453c7316e99 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/user_media/media_constraints_util.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_MEDIA_CONSTRAINTS_UTIL_H +#define WEBRTC_MEDIA_CONSTRAINTS_UTIL_H + +#include "media_constraints.h" +#include "../camera/camera_device_info.h" + +#include + +#include "api/audio_options.h" +#include "rtc_base/checks.h" + +namespace webrtc { + +struct CameraCaptureSettings { + std::string deviceId; + video::VideoProfile profile; +}; + +bool SelectSettingsForVideo( + const std::vector& devices, const MediaTrackConstraints& constraints, int defaultWidth, + int defaultHeight, double defaultFrameRate, CameraCaptureSettings& setting, std::string& failedConstraintName); + +// Copy all relevant constraints into an AudioOptions object. +void CopyConstraintsIntoAudioOptions(const MediaTrackConstraints& constraints, cricket::AudioOptions& options); + +void GetScreenCaptureConstraints(const MediaTrackConstraints& constraints, int& width, int& height); + +} // namespace webrtc + +#endif // WEBRTC_MEDIA_CONSTRAINTS_UTIL_H diff --git a/sdk/ohos/src/ohos_webrtc/utils/marcos.h b/sdk/ohos/src/ohos_webrtc/utils/marcos.h new file mode 100644 index 0000000000000000000000000000000000000000..676ca9af60cf0176cb5de5e328121dd22d5ea956 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/utils/marcos.h @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_UTILS_MARCOS_H +#define WEBRTC_UTILS_MARCOS_H + +#ifndef UNUSED +#define UNUSED __attribute__((unused)) +#endif + +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) + +#define NAPI_CLASS_NAME_DECLARE(value) static constexpr char kClassName[] = #value +#define NAPI_ATTRIBUTE_NAME_DECLARE(name, value) static constexpr char kAttributeName##name[] = #value +#define NAPI_METHOD_NAME_DECLARE(name, value) static constexpr char kMethodName##name[] = #value +#define NAPI_EVENT_NAME_DECLARE(name, value) static constexpr char kEventName##name[] = #value +#define NAPI_ENUM_NAME_DECLARE(name, value) static constexpr char kEnumName##name[] = #value + +#define NAPI_TYPE_TAG_DECLARE(lower, upper) static constexpr napi_type_tag kTypeTag = {lower, upper} +#define NAPI_CHECK_TYPE_TAG(object, type) (object).CheckTypeTag(&type::kTypeTag) + +#endif // WEBRTC_UTILS_MARCOS_H diff --git a/sdk/ohos/src/ohos_webrtc/video/texture_buffer.cpp b/sdk/ohos/src/ohos_webrtc/video/texture_buffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12993bd1349279527e9aa277f5c1e15619620ee6 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/texture_buffer.cpp @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "texture_buffer.h" +#include "../render/yuv_converter.h" + +#include "api/make_ref_counted.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +rtc::scoped_refptr +TextureBuffer::Create(std::weak_ptr texture, int width, int height, const Matrix4f& transformMatrix) +{ + return rtc::make_ref_counted(texture, width, height, transformMatrix); +} + +TextureBuffer::TextureBuffer(std::weak_ptr texture, int width, int height, const Matrix4f& transformMatrix) + : texture_(texture), width_(width), height_(height), transformMatrix_(transformMatrix) +{ + RTC_DLOG(LS_VERBOSE) << "TextureBuffer ctor"; +} + +TextureBuffer::~TextureBuffer() +{ + RTC_DLOG(LS_VERBOSE) << "TextureBuffer dtor"; +} + +rtc::scoped_refptr TextureBuffer::ToI420() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + auto texture = texture_.lock(); + + rtc::Thread* toI420Handler = texture->getToI420Handler(); + YuvConverter* yuvConverter = texture->getYuvConverter(); + if (!toI420Handler || !yuvConverter) { + return nullptr; + } + + auto textureBuffer = rtc::scoped_refptr(this); + return toI420Handler->BlockingCall([yuvConverter, textureBuffer] { return yuvConverter->Convert(textureBuffer); }); +} + +rtc::scoped_refptr TextureBuffer::CropAndScale( + int offset_x, int offset_y, int crop_width, int crop_height, int scaled_width, int scaled_height) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + // crop and scale by transformMatrix_ + // for now, just return self without crop and scale. + return rtc::scoped_refptr(this); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video/texture_buffer.h b/sdk/ohos/src/ohos_webrtc/video/texture_buffer.h new file mode 100644 index 0000000000000000000000000000000000000000..1e889c7361d1028df091489c3c401cdd9e2eab73 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/texture_buffer.h @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_TEXTURE_BUFFER_H +#define WEBRTC_VIDEO_TEXTURE_BUFFER_H + +#include "../render/render_common.h" + +#include "api/scoped_refptr.h" +#include "api/video/video_frame_buffer.h" +#include "rtc_base/thread.h" + +#include + +namespace webrtc { + +class YuvConverter; + +class TextureData { +public: + enum class Type { + OES, // GL_TEXTURE_EXTERNAL_OES + RGB, // GL_TEXTURE_2D + }; + + TextureData(unsigned int id, Type type, rtc::Thread* toI420Handler, YuvConverter* yuvConverter) + : id_(id), type_(type), toI420Handler_(toI420Handler), yuvConverter_(yuvConverter) + { + } + + unsigned int GetId() const + { + return id_; + } + + Type GetType() const + { + return type_; + } + + void Lock() const + { + mutex_.lock(); + } + + void Unlock() const + { + mutex_.unlock(); + } + + rtc::Thread* getToI420Handler() + { + return toI420Handler_; + } + + YuvConverter* getYuvConverter() + { + return yuvConverter_; + } + +private: + const unsigned int id_; + const Type type_; + rtc::Thread* toI420Handler_{}; + YuvConverter* yuvConverter_{}; + mutable std::mutex mutex_; +}; + +class TextureBuffer : public VideoFrameBuffer { +public: + static rtc::scoped_refptr + Create(std::weak_ptr texture, int width, int height, const Matrix4f& transformMatrix); + + ~TextureBuffer() override; + + Type type() const override + { + return Type::kNative; + } + + int width() const override + { + return width_; + } + + int height() const override + { + return height_; + } + + rtc::scoped_refptr ToI420() override; + + rtc::scoped_refptr CropAndScale( + int offset_x, int offset_y, int crop_width, int crop_height, int scaled_width, int scaled_height) override; + + Matrix4f GetTransformMatrix() const + { + return transformMatrix_; + } + + std::shared_ptr GetTexture() const + { + return texture_.lock(); + } + +protected: + TextureBuffer(std::weak_ptr texture, int width, int height, const Matrix4f& transformMatrix); + +private: + const int width_; + const int height_; + const Matrix4f transformMatrix_; + std::weak_ptr texture_; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_TEXTURE_BUFFER_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_capturer.h b/sdk/ohos/src/ohos_webrtc/video/video_capturer.h new file mode 100644 index 0000000000000000000000000000000000000000..f68ddaf37bb278b3c770b8167c722d2d61396351 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_capturer.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_VIDEO_CAPTURER_H +#define WEBRTC_VIDEO_VIDEO_CAPTURER_H + +#include "../video/video_frame_receiver.h" + +#include "api/video/video_frame_buffer.h" +#include "api/video/video_rotation.h" + +#include +#include + +namespace webrtc { + +class VideoCapturer { +public: + class Observer { + public: + virtual ~Observer() {} + virtual void OnCapturerStarted(bool success) = 0; + virtual void OnCapturerStopped() = 0; + virtual void + OnFrameCaptured(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) = 0; + }; + + virtual ~VideoCapturer() = default; + + // virtual void Init(std::unique_ptr, std::weak_ptr observer) = 0; + virtual void Init(std::unique_ptr, Observer* observer) = 0; + virtual void Release() = 0; + virtual void Start() = 0; + virtual void Stop() = 0; + virtual bool IsScreencast() = 0; +}; + +} // namespace webrtc + +#endif //WEBRTC_VIDEO_VIDEO_CAPTURER_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver.h b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver.h new file mode 100644 index 0000000000000000000000000000000000000000..c876635b937d3b6d77f661d0d66b01aee26013ef --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_FRAME_RECEIVER_H +#define WEBRTC_VIDEO_FRAME_RECEIVER_H + +#include + +#include "api/scoped_refptr.h" +#include "api/video/video_frame.h" +#include "api/video/video_frame_buffer.h" + +namespace webrtc { + +class VideoFrameReceiver { +public: + class Callback { + public: + virtual ~Callback() {} + virtual void + OnFrameAvailable(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) = 0; + }; + + virtual ~VideoFrameReceiver() {} + virtual uint64_t GetSurfaceId() const = 0; + virtual void SetVideoFrameSize(int32_t width, int32_t height) = 0; + + void SetCallback(Callback* observer) + { + callback_ = observer; + } + +protected: + Callback* callback_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_FRAME_RECEIVER_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.cpp b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8fffd110078c2f12dd091351f364c7a7e5ec9c25 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.cpp @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "video_frame_receiver_gl.h" +#include "../render/egl_config_attributes.h" +#include "../render/yuv_converter.h" + +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" + +#include + +namespace webrtc { + +std::unique_ptr +VideoFrameReceiverGl::Create(const std::string& threadName, std::shared_ptr sharedContext) +{ + return std::unique_ptr(new VideoFrameReceiverGl(threadName, sharedContext)); +} + +VideoFrameReceiverGl::VideoFrameReceiverGl(const std::string& threadName, std::shared_ptr sharedContext) + : thread_(rtc::Thread::Create()), yuvConverter_(std::make_unique()) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << ": this=" << this; + + thread_->SetName(threadName, this); + thread_->Start(); + thread_->BlockingCall([this, sharedContext] { + eglEnv_ = EglEnv::Create(sharedContext, EglConfigAttributes::RGBA_PIXEL_BUFFER); + eglEnv_->CreatePbufferSurface(1, 1); + eglEnv_->MakeCurrent(); + CreateNativeImage(); + }); +} + +VideoFrameReceiverGl::~VideoFrameReceiverGl() +{ + thread_->PostTask([this] { ReleaseNativeImage(); }); + thread_->Stop(); +} + +uint64_t VideoFrameReceiverGl::GetSurfaceId() const +{ + return nativeImage_.GetSurfaceId(); +} + +void VideoFrameReceiverGl::SetVideoFrameSize(int width, int height) +{ + SetTextureSize(width, height); +} + +bool VideoFrameReceiverGl::CreateNativeImage() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + // 创建 OpenGL 纹理 + GLuint textureId; + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); + + // 创建 NativeImage 实例,关联 OpenGL 纹理 + nativeImage_ = ohos::NativeImage::Create(textureId, GL_TEXTURE_EXTERNAL_OES); + if (nativeImage_.IsEmpty()) { + return false; + } + + OH_OnFrameAvailableListener listener; + listener.context = this; + listener.onFrameAvailable = &VideoFrameReceiverGl::OnNativeImageFrameAvailable1; + nativeImage_.SetOnFrameAvailableListener(listener); + + textureData_ = std::make_shared(textureId, TextureData::Type::OES, thread_.get(), yuvConverter_.get()); + + return true; +} + +void VideoFrameReceiverGl::ReleaseNativeImage() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + nativeImage_.UnsetOnFrameAvailableListener(); + nativeImage_.Reset(); + + std::shared_ptr temp; + textureData_.swap(temp); + auto textureId = temp->GetId(); + glDeleteTextures(1, &textureId); + + yuvConverter_.reset(); +} + +void VideoFrameReceiverGl::SetTextureSize(int32_t textureWidth, int32_t textureHeight) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (textureWidth <= 0 || textureHeight <= 0) { + RTC_LOG(LS_ERROR) << "Texture size must be positive: " << textureWidth << "x" << textureHeight; + return; + } + + auto nativeWindow = nativeImage_.AcquireNativeWindow(); + int32_t ret = + OH_NativeWindow_NativeWindowHandleOpt(nativeWindow.Raw(), SET_BUFFER_GEOMETRY, textureWidth, textureHeight); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set buffer geometry: " << ret; + return; + } + + thread_->PostTask([this, textureWidth, textureHeight]() { + this->width_ = textureWidth; + this->height_ = textureHeight; + }); +} + +void VideoFrameReceiverGl::OnNativeImageFrameAvailable1(void* data) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!data) { + return; + } + + auto self = reinterpret_cast(data); + self->OnNativeImageFrameAvailable(); +} + +void VideoFrameReceiverGl::OnNativeImageFrameAvailable() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!thread_->IsCurrent()) { + thread_->PostTask([this] { OnNativeImageFrameAvailable(); }); + return; + } + + textureData_->Lock(); + // 更新内容到OpenGL纹理。 + nativeImage_.UpdateSurfaceImage(); + textureData_->Unlock(); + + if (ohos::NativeError::HasPendingException()) { + auto error = ohos::NativeError::GetAndClearPendingException(); + RTC_LOG(LS_ERROR) << "Failed to update surface image: " << error.Code() << ", " << error.what(); + return; + } + + // 获取最近调用OH_NativeImage_UpdateSurfaceImage的纹理图像的时间戳和变化矩阵。 + auto timestamp = nativeImage_.GetTimestamp(); + auto matrix = nativeImage_.GetTransformMatrix(); + RTC_DLOG(LS_VERBOSE) << "timestamp: " << timestamp; + RTC_DLOG(LS_VERBOSE) << "transform: [ " << matrix[0] << ", " << matrix[1] << ", " << matrix[2] << ", " << matrix[3] + << ", " << matrix[4] << ", " << matrix[5] << ", " << matrix[6] << ", " << matrix[7] << ", " + << matrix[8] << ", " << matrix[9] << ", " << matrix[10] << ", " << matrix[11] << ", " + << matrix[12] << ", " << matrix[13] << ", " << matrix[14] << ", " << matrix[15] << " ]"; + + // create video frame + auto buffer = TextureBuffer::Create(textureData_, width_, height_, matrix); + + if (callback_) { + callback_->OnFrameAvailable(buffer, timestamp > 0 ? (timestamp / 1000) : rtc::TimeMicros(), kVideoRotation_0); + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.h b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.h new file mode 100644 index 0000000000000000000000000000000000000000..1bcb905f250a9115c01fe7d7123b41619fff935a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_gl.h @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_BUFFER_RECEIVER_GL_H +#define WEBRTC_VIDEO_BUFFER_RECEIVER_GL_H + +#include "texture_buffer.h" +#include "video_frame_receiver.h" +#include "../helper/native_image.h" +#include "../render/egl_env.h" + +#include +#include + +#include + +#include "rtc_base/thread.h" + +namespace webrtc { + +class VideoFrameReceiverGl : public VideoFrameReceiver { +public: + static std::unique_ptr + Create(const std::string& threadName, std::shared_ptr sharedContext); + + ~VideoFrameReceiverGl() override; + + uint64_t GetSurfaceId() const override; + void SetVideoFrameSize(int32_t width, int32_t height) override; + +protected: + VideoFrameReceiverGl(const std::string& threadName, std::shared_ptr sharedContext); + + bool CreateNativeImage(); + void ReleaseNativeImage(); + void SetTextureSize(int textureWidth, int textureHeight); + + static void OnNativeImageFrameAvailable1(void* data); + void OnNativeImageFrameAvailable(); + +private: + std::unique_ptr thread_; + std::unique_ptr eglEnv_; + uint32_t width_{0}; + uint32_t height_{0}; + ohos::NativeImage nativeImage_; + std::shared_ptr textureData_; + std::unique_ptr yuvConverter_; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_BUFFER_RECEIVER_GL_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.cpp b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40579f2f889335f4a2ec4c31d06339cdfe575da6 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.cpp @@ -0,0 +1,310 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "video_frame_receiver_native.h" + +#include "api/video/i420_buffer.h" +#include "rtc_base/time_utils.h" +#include "rtc_base/logging.h" +#include "libyuv.h" + +namespace webrtc { + +std::map VideoFrameReceiverNative::receiverMap_; + +std::unique_ptr VideoFrameReceiverNative::Create(const std::string& threadName) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return std::unique_ptr(new VideoFrameReceiverNative(threadName)); +} + +VideoFrameReceiverNative::VideoFrameReceiverNative(const std::string& threadName) + : thread_(rtc::Thread::Create()) +{ + thread_->SetName(threadName, this); + thread_->Start(); +} + +VideoFrameReceiverNative::~VideoFrameReceiverNative() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + ReleaseImageReceiver(); + thread_->Stop(); +} + +uint64_t VideoFrameReceiverNative::GetSurfaceId() const +{ + uint64_t surfaceId = 0; + + if (imageReceiver_) { + Image_ErrorCode ret = OH_ImageReceiverNative_GetReceivingSurfaceId(imageReceiver_, &surfaceId); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to get surface id of image receiver"; + } + } + + return surfaceId; +} + +void VideoFrameReceiverNative::SetVideoFrameSize(int32_t width, int32_t height) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (width <= 0 || height <= 0) { + RTC_LOG(LS_ERROR) << "invalid size"; + return; + } + + if (width_ == width && height_ == height) { + RTC_LOG(LS_VERBOSE) << "Same size"; + return; + } + + width_ = width; + height_ = height; + + ReleaseImageReceiver(); + CreateImageReceiver(); +} + +void VideoFrameReceiverNative::CreateImageReceiver() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + Image_ErrorCode ret = IMAGE_SUCCESS; + OH_ImageReceiverOptions* options = nullptr; + OH_ImageReceiverNative* imageReceiver = nullptr; + + do { + ret = OH_ImageReceiverOptions_Create(&options); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to create image receiver options"; + break; + } + + ret = OH_ImageReceiverOptions_SetSize(options, {static_cast(width_), static_cast(height_)}); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to set size of image receiver options"; + break; + } + + ret = OH_ImageReceiverOptions_SetCapacity(options, 8); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to set capacity of image receiver options"; + break; + } + + ret = OH_ImageReceiverNative_Create(options, &imageReceiver); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to create image receiver"; + break; + } + + receiverMap_[imageReceiver] = this; + imageReceiver_ = imageReceiver; + ret = OH_ImageReceiverNative_On(imageReceiver_, VideoFrameReceiverNative::OnImageReceiverCallback1); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to set callback of image receiver"; + } + } while (0); + + if (options) { + ret = OH_ImageReceiverOptions_Release(options); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to release image receiver options"; + } + } +} + +void VideoFrameReceiverNative::ReleaseImageReceiver() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (imageReceiver_) { + Image_ErrorCode ret = OH_ImageReceiverNative_Off(imageReceiver_); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to unset callback of image receiver"; + } + + ret = OH_ImageReceiverNative_Release(imageReceiver_); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to release image receiver"; + } + + receiverMap_.erase(imageReceiver_); + imageReceiver_ = nullptr; + } +} + +void VideoFrameReceiverNative::OnImageReceiverCallback1(OH_ImageReceiverNative* receiver) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto it = receiverMap_.find(receiver); + if (it == receiverMap_.end()) { + return; + } + + it->second->OnImageReceiverCallback(); +} + +void VideoFrameReceiverNative::OnImageReceiverCallback() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!thread_->IsCurrent()) { + thread_->PostTask([this] { OnImageReceiverCallback(); }); + return; + } + + OH_ImageNative* image; + Image_ErrorCode ret = OH_ImageReceiverNative_ReadNextImage(imageReceiver_, &image); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to read latest image: " << ret; + return; + } + + Image_Size imageSize; + ret = OH_ImageNative_GetImageSize(image, &imageSize); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + RTC_LOG(LS_ERROR) << "Failed to get image size: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Image size: " << imageSize.width << " x " << imageSize.height; + + // ComponentType没有明确的定义,似乎可参照OH_NativeBuffer_Format。此处不检查ComponentType,默认与相机预览的格式一致(RGBA)。 + uint32_t* types; + size_t typeSize; + ret = OH_ImageNative_GetComponentTypes(image, nullptr, &typeSize); + if (ret != IMAGE_SUCCESS || typeSize <= 0) { + OH_ImageNative_Release(image); + RTC_LOG(LS_ERROR) << "Failed to get size of component types: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Component types size: " << typeSize; + + types = new uint32_t[typeSize]; + ret = OH_ImageNative_GetComponentTypes(image, &types, &typeSize); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + delete[] types; + RTC_LOG(LS_ERROR) << "Failed to get component types: " << ret; + return; + } + for (uint32_t i = 0; i < typeSize; i++) { + RTC_DLOG(LS_VERBOSE) << "Component type: " << types[i]; + } + + int32_t rowStride; + ret = OH_ImageNative_GetRowStride(image, types[0], &rowStride); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + delete[] types; + RTC_LOG(LS_ERROR) << "Failed to get row stride: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Row stride: " << rowStride; + + int32_t pixelStride; + ret = OH_ImageNative_GetPixelStride(image, types[0], &pixelStride); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + delete[] types; + RTC_LOG(LS_ERROR) << "Failed to get pixel stride: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Pixel stride: " << pixelStride; + + size_t bufferSize; + ret = OH_ImageNative_GetBufferSize(image, types[0], &bufferSize); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + delete[] types; + RTC_LOG(LS_ERROR) << "Failed to get buffer size: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Buffer size: " << bufferSize; + + OH_NativeBuffer* buffer; + ret = OH_ImageNative_GetByteBuffer(image, types[0], &buffer); + if (ret != IMAGE_SUCCESS) { + OH_ImageNative_Release(image); + delete[] types; + RTC_LOG(LS_ERROR) << "Failed to get byte buffer: " << ret; + return; + } + RTC_DLOG(LS_VERBOSE) << "Buffer: " << buffer; + + delete[] types; + + OH_NativeBuffer_Config bufferConfig{}; + OH_NativeBuffer_GetConfig(buffer, &bufferConfig); + RTC_DLOG(LS_VERBOSE) << "Buffer config: format=" << bufferConfig.format << " usage=" << bufferConfig.usage; + + void* addr = nullptr; + OH_NativeBuffer_Map(buffer, &addr); + RTC_DLOG(LS_VERBOSE) << "Buffer map addr: " << addr; + + rtc::scoped_refptr i420Buffer = I420Buffer::Create(bufferConfig.width, bufferConfig.height); + + switch (bufferConfig.format) { + case NATIVEBUFFER_PIXEL_FMT_RGBA_8888: { + libyuv::ABGRToI420( + (uint8_t*)addr, bufferConfig.stride, i420Buffer->MutableDataY(), i420Buffer->StrideY(), + i420Buffer->MutableDataU(), i420Buffer->StrideU(), i420Buffer->MutableDataV(), i420Buffer->StrideV(), + bufferConfig.width, bufferConfig.height); + RTC_DLOG(LS_VERBOSE) << "ABGRToI420 ret = " << ret; + } break; + case NATIVEBUFFER_PIXEL_FMT_YCBCR_420_SP: { + int32_t ret = libyuv::NV12ToI420( + (uint8_t*)addr, bufferConfig.width, (uint8_t*)addr + bufferConfig.width * bufferConfig.height, + bufferConfig.width, i420Buffer->MutableDataY(), i420Buffer->StrideY(), i420Buffer->MutableDataU(), + i420Buffer->StrideU(), i420Buffer->MutableDataV(), i420Buffer->StrideV(), bufferConfig.width, + bufferConfig.height); + RTC_DLOG(LS_VERBOSE) << "NV12ToI420 ret = " << ret; + } break; + case NATIVEBUFFER_PIXEL_FMT_YCRCB_420_SP: { + int32_t ret = libyuv::NV21ToI420( + (uint8_t*)addr, bufferConfig.width, (uint8_t*)addr + bufferConfig.width * bufferConfig.height, + bufferConfig.width, i420Buffer->MutableDataY(), i420Buffer->StrideY(), i420Buffer->MutableDataU(), + i420Buffer->StrideU(), i420Buffer->MutableDataV(), i420Buffer->StrideV(), bufferConfig.width, + bufferConfig.height); + RTC_DLOG(LS_VERBOSE) << "NV21ToI420 ret = " << ret; + } break; + default: { + RTC_LOG(LS_ERROR) << "Unsupported pixel format: " << bufferConfig.format; + ret = OH_ImageNative_Release(image); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to release image: " << ret; + } + } + return; + } + + if (callback_) { + callback_->OnFrameAvailable(i420Buffer, rtc::TimeMicros(), kVideoRotation_0); + } + + ret = OH_ImageNative_Release(image); + if (ret != IMAGE_SUCCESS) { + RTC_LOG(LS_ERROR) << "Failed to release image: " << ret; + return; + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.h b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.h new file mode 100644 index 0000000000000000000000000000000000000000..92862f7859398bcad6ff986e47a6b025087c28df --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_frame_receiver_native.h @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_FRAME_RECEIVER_RASTER_H +#define WEBRTC_VIDEO_FRAME_RECEIVER_RASTER_H + +#include "video_frame_receiver.h" + +#include +#include +#include + +#include + +#include "rtc_base/thread.h" + +namespace webrtc { + +class VideoFrameReceiverNative : public VideoFrameReceiver { +public: + static std::unique_ptr Create(const std::string& threadName); + + ~VideoFrameReceiverNative() override; + + uint64_t GetSurfaceId() const override; + void SetVideoFrameSize(int32_t width, int32_t height) override; + +protected: + explicit VideoFrameReceiverNative(const std::string& threadName); + + void CreateImageReceiver(); + void ReleaseImageReceiver(); + + static void OnImageReceiverCallback1(OH_ImageReceiverNative* receiver); + void OnImageReceiverCallback(); + +private: + static std::map receiverMap_; + + std::unique_ptr thread_; + int32_t width_{}; + int32_t height_{}; + OH_ImageReceiverNative* imageReceiver_{nullptr}; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_FRAME_RECEIVER_RASTER_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_info.h b/sdk/ohos/src/ohos_webrtc/video/video_info.h new file mode 100644 index 0000000000000000000000000000000000000000..c1770d7b0258af4996ff8971f5049771a8d52731 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_info.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_INFO_H +#define WEBRTC_VIDEO_INFO_H + +#include + +namespace video { + +enum class PixelFormat { + Unsupported = -1, + RGBA, + YU12, // I420 (YUV 4:2:0 Planar) + NV12, // (YUV 4:2:0 Packed) + NV21 // (YUV 4:2:0 Packed) +}; + +struct Resolution { + uint32_t width; + uint32_t height; +}; + +struct FrameRateRange { + uint32_t min; + uint32_t max; +}; + +struct VideoProfile { + PixelFormat format; + Resolution resolution; + FrameRateRange frameRateRange; +}; + +} // namespace video + +#endif // WEBRTC_VIDEO_INFO_H diff --git a/sdk/ohos/src/ohos_webrtc/video/video_track_source.cpp b/sdk/ohos/src/ohos_webrtc/video/video_track_source.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8a9ffe97a779d9bf6e3c74dac911aa5feeac3087 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_track_source.cpp @@ -0,0 +1,316 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "video_track_source.h" +#include "utils/marcos.h" +#include "video_frame_receiver_gl.h" + +#include "api/video/i420_buffer.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +namespace { + +const int kRequiredResolutionAlignment = 2; + +} // namespace + +rtc::scoped_refptr OhosVideoTrackSource::Create( + std::unique_ptr capturer, rtc::Thread* signalingThread, std::shared_ptr sharedContext) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!capturer) { + RTC_LOG(LS_ERROR) << "The capturer is nullptr"; + return nullptr; + } + + return rtc::make_ref_counted(std::move(capturer), signalingThread, sharedContext); +} + +OhosVideoTrackSource::OhosVideoTrackSource( + std::unique_ptr capturer, rtc::Thread* signalingThread, std::shared_ptr sharedContext) + : thread_(rtc::Thread::Create()), + signalingThread_(signalingThread), + capturer_(std::move(capturer)), + sharedContext_(sharedContext), + videoAdapter_(kRequiredResolutionAlignment) +{ + RTC_LOG(LS_INFO) << "OhosVideoTrackSource ctor: " << this; + + SetState(kInitializing); + + thread_->SetName("v-track-source", capturer_.get()); + thread_->Start(); + thread_->PostTask( + [this] { capturer_->Init(VideoFrameReceiverGl::Create("v-frame-receiver", sharedContext_), this); }); +} + +OhosVideoTrackSource::~OhosVideoTrackSource() +{ + RTC_LOG(LS_INFO) << __FUNCTION__; + + thread_->PostTask([this] { + capturer_->Stop(); + capturer_->Release(); + capturer_.reset(); + }); + thread_->Stop(); +} + +void OhosVideoTrackSource::Start() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + thread_->PostTask([this] { capturer_->Start(); }); +} + +void OhosVideoTrackSource::Stop() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + thread_->PostTask([this] { capturer_->Stop(); }); +} + +void OhosVideoTrackSource::SetCapturerObserver(VideoCapturer::Observer* observer) +{ + UNUSED std::lock_guard lock(obsMutex_); + capturerObserver_ = observer; +} + +void OhosVideoTrackSource::SetState(SourceState state) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__ << " state: " << state; + + if (state_.exchange(state) != state) { + if (rtc::Thread::Current() == signalingThread_) { + FireOnChanged(); + } else { + signalingThread_->PostTask([this] { +#ifndef NDEBUG + std::list observers = observers_; + for (std::list::iterator it = observers.begin(); it != observers.end(); ++it) { + RTC_DLOG(LS_VERBOSE) << "observer: " << *it; + } +#endif + FireOnChanged(); + }); + } + } +} + +void OhosVideoTrackSource::SetState(bool isLive) +{ + SetState(isLive ? kLive : kEnded); +} + +bool OhosVideoTrackSource::ApplyRotation() +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return broadcaster_.wants().rotation_applied; +} + +bool OhosVideoTrackSource::AdaptFrame( + int width, int height, int64_t time_us, int* out_width, int* out_height, int* crop_width, int* crop_height, + int* crop_x, int* crop_y) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + { + UNUSED MutexLock lock(&statsMutex_); + stats_ = Stats{width, height}; + } + + if (!broadcaster_.frame_wanted()) { + return false; + } + + if (!videoAdapter_.AdaptFrameResolution( + width, height, time_us * rtc::kNumNanosecsPerMicrosec, crop_width, crop_height, out_width, out_height)) + { + // VideoAdapter dropped the frame. + return false; + } + + *crop_x = (width - *crop_width) / 2; + *crop_y = (height - *crop_height) / 2; + return true; +} + +void OhosVideoTrackSource::AddOrUpdateSink(rtc::VideoSinkInterface* sink, const rtc::VideoSinkWants& wants) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + broadcaster_.AddOrUpdateSink(sink, wants); + videoAdapter_.OnSinkWants(wants); + + if (broadcaster_.frame_wanted()) { + thread_->PostTask([this] { capturer_->Start(); }); + } +} + +void OhosVideoTrackSource::RemoveSink(rtc::VideoSinkInterface* sink) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + broadcaster_.RemoveSink(sink); + videoAdapter_.OnSinkWants(broadcaster_.wants()); + + if (!broadcaster_.frame_wanted()) { + thread_->PostTask([this] { capturer_->Stop(); }); + } +} + +MediaSourceInterface::SourceState OhosVideoTrackSource::state() const +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return state_.load(); +} + +bool OhosVideoTrackSource::remote() const +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return false; +} + +bool OhosVideoTrackSource::is_screencast() const +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return capturer_->IsScreencast(); +} + +absl::optional OhosVideoTrackSource::needs_denoising() const +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + return false; +} + +bool OhosVideoTrackSource::GetStats(Stats* stats) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + UNUSED MutexLock lock(&statsMutex_); + + if (!stats_) { + return false; + } + + *stats = *stats_; + return true; +} + +void OhosVideoTrackSource::ProcessConstraints(const VideoTrackSourceConstraints& constraints) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + broadcaster_.ProcessConstraints(constraints); +} + +void OhosVideoTrackSource::OnCapturerStarted(bool success) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + SetState(success); + + VideoCapturer::Observer* observer = nullptr; + { + UNUSED std::lock_guard lock(obsMutex_); + observer = capturerObserver_; + } + + if (observer) { + observer->OnCapturerStarted(success); + } +} + +void OhosVideoTrackSource::OnCapturerStopped() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + SetState(false); + + VideoCapturer::Observer* observer = nullptr; + { + UNUSED std::lock_guard lock(obsMutex_); + observer = capturerObserver_; + } + + if (observer) { + observer->OnCapturerStopped(); + } +} + +void OhosVideoTrackSource::OnFrameCaptured( + rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " timestampUs=" << timestampUs << ", rotation=" << rotation; + + int adapted_width = 0; + int adapted_height = 0; + int crop_width = 0; + int crop_height = 0; + int crop_x = 0; + int crop_y = 0; + bool drop; + + if (rotation % 180 == 0) { + drop = !AdaptFrame( + buffer->width(), buffer->height(), timestampUs, &adapted_width, &adapted_height, &crop_width, &crop_height, + &crop_x, &crop_y); + } else { + // Swap all width/height and x/y. + drop = !AdaptFrame( + buffer->height(), buffer->width(), timestampUs, &adapted_height, &adapted_width, &crop_height, &crop_width, + &crop_y, &crop_x); + } + + RTC_DLOG(LS_VERBOSE) << "adapted_width=" << adapted_width << ", adapted_height=" << adapted_height + << ", crop_width=" << crop_width << ", crop_height=" << crop_height << ", crop_x=" << crop_x + << ", crop_y=" << crop_y; + + if (drop) { + RTC_DLOG(LS_VERBOSE) << "dropped"; + broadcaster_.OnDiscardedFrame(); + return; + } + + if (adapted_height != buffer->height() || adapted_width != buffer->width()) { + buffer = buffer->CropAndScale(crop_x, crop_y, crop_width, crop_height, adapted_width, adapted_height); + } else { + // No adaptations needed, just return the frame as is. + } + + auto frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_rotation(rotation) + .set_timestamp_us(timestampUs) + .build(); + + if (ApplyRotation() && frame.rotation() != kVideoRotation_0 && buffer->type() == VideoFrameBuffer::Type::kI420) { + /* Apply pending rotation. */ + VideoFrame rotatedFrame(frame); + rotatedFrame.set_video_frame_buffer(I420Buffer::Rotate(*buffer->GetI420(), frame.rotation())); + rotatedFrame.set_rotation(kVideoRotation_0); + broadcaster_.OnFrame(rotatedFrame); + } else { + broadcaster_.OnFrame(frame); + } +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video/video_track_source.h b/sdk/ohos/src/ohos_webrtc/video/video_track_source.h new file mode 100644 index 0000000000000000000000000000000000000000..68264d870d7a45b53ad6d5958a0f17a9319b9695 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video/video_track_source.h @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_CAMERA_TRACK_SOURCE_H +#define WEBRTC_CAMERA_TRACK_SOURCE_H + +#include "video_capturer.h" +#include "../render/egl_context.h" + +#include "api/notifier.h" +#include "api/media_stream_interface.h" +#include "media/base/video_adapter.h" +#include "media/base/video_broadcaster.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread.h" + +#include + +namespace webrtc { + +class OhosVideoTrackSource : public Notifier, VideoCapturer::Observer { +public: + static rtc::scoped_refptr Create( + std::unique_ptr capturer, rtc::Thread* signalingThread, + std::shared_ptr sharedContext = nullptr); + + ~OhosVideoTrackSource() override; + + void Start(); + void Stop(); + + void SetCapturerObserver(VideoCapturer::Observer* observer); + +protected: + OhosVideoTrackSource( + std::unique_ptr capturer, rtc::Thread* signaling_thread, + std::shared_ptr sharedContext); + + void SetState(SourceState state); + void SetState(bool isLive); + + bool ApplyRotation(); + + bool AdaptFrame( + int width, int height, int64_t time_us, int* out_width, int* out_height, int* crop_width, int* crop_height, + int* crop_x, int* crop_y); + +protected: + // Implements rtc::VideoSourceInterface. + void AddOrUpdateSink(rtc::VideoSinkInterface* sink, const rtc::VideoSinkWants& wants) override; + void RemoveSink(rtc::VideoSinkInterface* sink) override; + + // Implements MediaSourceInterface + SourceState state() const override; + bool remote() const override; + + // Part of VideoTrackSourceInterface. + bool is_screencast() const override; + absl::optional needs_denoising() const override; + bool GetStats(Stats* stats) override; + + // Encoded sinks not implemented. + bool SupportsEncodedOutput() const override + { + return false; + } + void GenerateKeyFrame() override {} + void AddEncodedSink(rtc::VideoSinkInterface* sink) override {} + void RemoveEncodedSink(rtc::VideoSinkInterface* sink) override {} + void ProcessConstraints(const VideoTrackSourceConstraints& constraints) override; + + // VideoCapturer::Observer + void OnCapturerStarted(bool success) override; + void OnCapturerStopped() override; + void + OnFrameCaptured(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) override; + +private: + std::unique_ptr thread_; + rtc::Thread* signalingThread_; + std::unique_ptr capturer_; + std::shared_ptr sharedContext_; + cricket::VideoAdapter videoAdapter_; + rtc::VideoBroadcaster broadcaster_; + std::atomic state_; + Mutex statsMutex_; + std::optional stats_ RTC_GUARDED_BY(statsMutex_); + std::mutex obsMutex_; + VideoCapturer::Observer* capturerObserver_{}; +}; + +} // namespace webrtc + +#endif // WEBRTC_CAMERA_TRACK_SOURCE_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/codec_common.h b/sdk/ohos/src/ohos_webrtc/video_codec/codec_common.h new file mode 100644 index 0000000000000000000000000000000000000000..64fe865a10958bc05c0d541ef035376901810db7 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/codec_common.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_CODEC_COMMON_H +#define WEBRTC_VIDEO_CODEC_CODEC_COMMON_H + +#include + +namespace ohos { + +struct CodecBuffer { + CodecBuffer() : index(-1), buf(nullptr) {} + CodecBuffer(int32_t _index, OH_AVBuffer* _buf) : index(_index), buf(_buf) {} + + int32_t index; + OH_AVBuffer* buf; +}; + +} // namespace ohos + +#endif // WEBRTC_VIDEO_CODEC_CODEC_COMMON_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6e187ebf8cd5751f9feb0614096fb7cc2d4f2bb4 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.cpp @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "default_video_decoder_factory.h" +#include "hardware_video_decoder_factory.h" +#include "software_video_decoder_factory.h" + +#include +#include + +namespace webrtc { +namespace adapter { + +DefaultVideoDecoderFactory::DefaultVideoDecoderFactory(std::shared_ptr sharedContext) + : DefaultVideoDecoderFactory(std::make_unique(sharedContext)) +{ +} + +DefaultVideoDecoderFactory::DefaultVideoDecoderFactory(std::unique_ptr hardwareVideoDecoderFactory) + : hardwareVideoDecoderFactory_(std::move(hardwareVideoDecoderFactory)), + softwareVideoDecoderFactory_(std::make_unique()) +{ +} + +std::vector DefaultVideoDecoderFactory::GetSupportedFormats() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto hardwareSupportedFormats = hardwareVideoDecoderFactory_->GetSupportedFormats(); + auto softwareSupportedFormats = softwareVideoDecoderFactory_->GetSupportedFormats(); + + std::vector supportedFormats; + supportedFormats.insert(supportedFormats.end(), hardwareSupportedFormats.begin(), hardwareSupportedFormats.end()); + supportedFormats.insert(supportedFormats.end(), softwareSupportedFormats.begin(), softwareSupportedFormats.end()); + + RTC_DLOG(LS_VERBOSE) << "Supported formats: "; + for (auto& format : supportedFormats) { + RTC_DLOG(LS_VERBOSE) << "\t format: " << format.ToString(); + } + + return supportedFormats; +} + +std::unique_ptr DefaultVideoDecoderFactory::CreateVideoDecoder(const SdpVideoFormat& format) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto hardwareDecoder = hardwareVideoDecoderFactory_->CreateVideoDecoder(format); + auto softwareDecoder = softwareVideoDecoderFactory_->CreateVideoDecoder(format); + if (hardwareDecoder && softwareDecoder) { + // Both hardware and software supported, wrap it in a software fallback + return CreateVideoDecoderSoftwareFallbackWrapper(std::move(softwareDecoder), std::move(hardwareDecoder)); + } + + return hardwareDecoder ? std::move(hardwareDecoder) : std::move(softwareDecoder); +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..f2a69709eb1abfcdff8f69e541b029ec61c79196 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_decoder_factory.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_DECODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_DECODER_FACTORY_H + +#include "../render/egl_context.h" + +#include "api/video_codecs/video_decoder.h" +#include "api/video_codecs/video_decoder_factory.h" + +namespace webrtc { +namespace adapter { + +class DefaultVideoDecoderFactory : public VideoDecoderFactory { +public: + explicit DefaultVideoDecoderFactory(std::shared_ptr sharedEglContext); + + explicit DefaultVideoDecoderFactory(std::unique_ptr hardwareVideoDecoderFactory); + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoDecoder(const SdpVideoFormat& format) override; + +private: + std::unique_ptr hardwareVideoDecoderFactory_; + std::unique_ptr softwareVideoDecoderFactory_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_DECODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7d2d41503f6c4179fec54310e60dc98264a568fc --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "default_video_encoder_factory.h" +#include "hardware_video_encoder_factory.h" +#include "software_video_encoder_factory.h" + +#include "api/video_codecs/video_encoder_software_fallback_wrapper.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace adapter { + +DefaultVideoEncoderFactory::DefaultVideoEncoderFactory( + std::shared_ptr sharedContext, bool enableH264HighProfile) + : DefaultVideoEncoderFactory(std::make_unique(sharedContext, enableH264HighProfile)) +{ +} + +DefaultVideoEncoderFactory::DefaultVideoEncoderFactory(std::unique_ptr hardwareVideoEncoderFactory) + : hardwareVideoEncoderFactory_(std::move(hardwareVideoEncoderFactory)), + softwareVideoEncoderFactory_(std::make_unique()) +{ +} + +std::vector DefaultVideoEncoderFactory::GetSupportedFormats() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto hardwareSupportedFormats = hardwareVideoEncoderFactory_->GetSupportedFormats(); + auto softwareSupportedFormats = softwareVideoEncoderFactory_->GetSupportedFormats(); + + std::vector supportedFormats; + supportedFormats.insert(supportedFormats.end(), hardwareSupportedFormats.begin(), hardwareSupportedFormats.end()); + supportedFormats.insert(supportedFormats.end(), softwareSupportedFormats.begin(), softwareSupportedFormats.end()); + + RTC_DLOG(LS_VERBOSE) << "Supported formats: "; + for (auto& format : supportedFormats) { + RTC_DLOG(LS_VERBOSE) << "\t format: " << format.ToString(); + } + + return supportedFormats; +} + +std::unique_ptr DefaultVideoEncoderFactory::CreateVideoEncoder(const SdpVideoFormat& format) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + auto hardwareEncoder = hardwareVideoEncoderFactory_->CreateVideoEncoder(format); + auto softwareEncoder = softwareVideoEncoderFactory_->CreateVideoEncoder(format); + + if (hardwareEncoder && softwareEncoder) { + // Both hardware and software supported, wrap it in a software fallback + return CreateVideoEncoderSoftwareFallbackWrapper(std::move(softwareEncoder), std::move(hardwareEncoder)); + } + + return hardwareEncoder ? std::move(hardwareEncoder) : std::move(softwareEncoder); +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..85c091764728997ca1ec441f7d24ab90fbdb914a --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/default_video_encoder_factory.h @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_ENCODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_ENCODER_FACTORY_H + +#include "../render/egl_context.h" + +#include "api/video_codecs/video_encoder_factory.h" + +namespace webrtc { +namespace adapter { + +class DefaultVideoEncoderFactory : public VideoEncoderFactory { +public: + DefaultVideoEncoderFactory(std::shared_ptr sharedEglContext, bool enableH264HighProfile); + + explicit DefaultVideoEncoderFactory(std::unique_ptr hardwareVideoEncoderFactory); + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoEncoder(const SdpVideoFormat& format) override; + +private: + std::unique_ptr hardwareVideoEncoderFactory_; + std::unique_ptr softwareVideoEncoderFactory_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_DEFAULT_VIDEO_ENCODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1b382ddd47900712e61059b9aab189a99c7f382c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.cpp @@ -0,0 +1,533 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hardware_video_decoder.h" +#include "../video/video_frame_receiver_gl.h" +#include "../utils/marcos.h" + +#include +#include + +#include "api/video/i420_buffer.h" +#include "api/video/nv12_buffer.h" +#include "modules/video_coding/include/video_error_codes.h" +#include "rtc_base/time_utils.h" +#include "libyuv/convert.h" + +namespace webrtc { +namespace adapter { + +// RTP timestamps are 90 kHz. +const int64_t kNumRtpTicksPerMillisec = 90000 / rtc::kNumMillisecsPerSec; + +std::unique_ptr HardwareVideoDecoder::Create( + const std::string& codecName, int32_t colorFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext) +{ + return std::unique_ptr( + new HardwareVideoDecoder(codecName, colorFormat, format, sharedContext)); +} + +HardwareVideoDecoder::HardwareVideoDecoder( + const std::string& codecName, int32_t colorFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext) + : codecName_(codecName), format_(format), colorFormat_(colorFormat), sharedContext_(sharedContext) +{ +} + +HardwareVideoDecoder::~HardwareVideoDecoder() = default; + +bool HardwareVideoDecoder::Configure(const Settings& settings) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + decoderSettings_ = settings; + return InitDecode(); +} + +int32_t HardwareVideoDecoder::Release() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (initialized_) { + int32_t ret = OH_VideoDecoder_Stop(decoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to stop" << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + if (videoFrameReceiver_) { + videoFrameReceiver_.reset(); + } + + UNUSED std::unique_lock lock(inputMutex_); + std::queue temp; + std::swap(temp, inputBufferQueue_); + } + + { + UNUSED std::lock_guard lock(extraInfosMutex_); + extraInfos_.clear(); + } + + initialized_ = false; + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t HardwareVideoDecoder::RegisterDecodeCompleteCallback(DecodedImageCallback* callback) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + RTC_DCHECK_RUNS_SERIALIZED(&callbackRaceChecker_); + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +// `missing_frames`, `fragmentation` and `render_time_ms` are ignored. +int32_t HardwareVideoDecoder::Decode(const EncodedImage& input_image, bool /*missing_frames*/, int64_t render_time_ms) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (!initialized_) { + // Most likely initializing the codec failed. + RTC_LOG(LS_ERROR) << "Not initialized"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + if (input_image.data() == nullptr || input_image.size() == 0) { + RTC_LOG(LS_ERROR) << "input image is empty"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + RTC_DLOG(LS_VERBOSE) << "input image, size=" << input_image.size() + << " capture time=" << input_image.capture_time_ms_ + << " rtp timestamp=" << input_image.RtpTimestamp(); + + ohos::CodecBuffer codecBuffer; + if (!DequeueInputBuffer(codecBuffer)) { + RTC_LOG(LS_ERROR) << "Failed to get cached input buffer"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + OH_AVCodecBufferAttr attr; + int32_t ret = OH_AVBuffer_GetBufferAttr(codecBuffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + uint8_t* addr = OH_AVBuffer_GetAddr(codecBuffer.buf); + if (!addr) { + RTC_LOG(LS_ERROR) << "Failed to get buffer addr"; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + // copy data + memcpy(addr + attr.offset, input_image.data(), input_image.size()); + + attr.pts = input_image.RtpTimestamp() / kNumRtpTicksPerMillisec * rtc::kNumMicrosecsPerMillisec; + attr.size = input_image.size(); + if (input_image.FrameType() == VideoFrameType::kVideoFrameKey) { + RTC_DLOG(LS_VERBOSE) << "Key frame occurred"; + attr.flags |= AVCODEC_BUFFER_FLAGS_SYNC_FRAME; + } + + ret = OH_AVBuffer_SetBufferAttr(codecBuffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + { + FrameExtraInfo info; + info.timestampUs = input_image.RtpTimestamp() / kNumRtpTicksPerMillisec * rtc::kNumMicrosecsPerMillisec; + info.timestampRtp = input_image.RtpTimestamp(); + info.timestampNtp = input_image.NtpTimeMs(); + + UNUSED std::lock_guard lock(extraInfosMutex_); + extraInfos_.push_back(info); + } + + RTC_DLOG(LS_VERBOSE) << "push input buffer, pts=" << attr.pts; + ret = OH_VideoDecoder_PushInputBuffer(decoder_.Raw(), codecBuffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to push input buffer: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +VideoDecoder::DecoderInfo HardwareVideoDecoder::GetDecoderInfo() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + VideoDecoder::DecoderInfo decoder_info; + decoder_info.implementation_name = codecName_; + decoder_info.is_hardware_accelerated = true; + return decoder_info; +} + +const char* HardwareVideoDecoder::ImplementationName() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + return codecName_.c_str(); +} + +void HardwareVideoDecoder::OnFrameAvailable( + rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + FrameExtraInfo extraInfo; + { + UNUSED std::lock_guard lock(extraInfosMutex_); + + do { + if (extraInfos_.empty()) { + RTC_LOG(LS_WARNING) << "unexpected frame: " << timestampUs; + return; + } + + extraInfo = extraInfos_.front(); + extraInfos_.pop_front(); + // If the decoder might drop frames so iterate through the queue until we + // find a matching timestamp. + } while (extraInfo.timestampUs != timestampUs); + } + + auto frame = VideoFrame::Builder() + .set_id(65535) + .set_video_frame_buffer(buffer) + .set_rotation(kVideoRotation_0) + .set_timestamp_us(timestampUs) + .set_timestamp_rtp(extraInfo.timestampRtp) + .set_ntp_time_ms(extraInfo.timestampNtp) + .build(); + + RTC_DCHECK_RUNS_SERIALIZED(&callbackRaceChecker_); + callback_->Decoded(frame, std::nullopt, std::nullopt); +} + +void HardwareVideoDecoder::OnCodecError1(OH_AVCodec* codec, int32_t errorCode, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoDecoder*)userData; + self->OnCodecError(codec, errorCode); +} + +void HardwareVideoDecoder::OnStreamChanged1(OH_AVCodec* codec, OH_AVFormat* format, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoDecoder*)userData; + self->OnStreamChanged(codec, format); +} + +void HardwareVideoDecoder::OnNeedInputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoDecoder*)userData; + self->OnNeedInputBuffer(codec, index, buffer); +} + +void HardwareVideoDecoder::OnNewOutputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoDecoder*)userData; + self->OnNewOutputBuffer(codec, index, buffer); +} + +void HardwareVideoDecoder::OnCodecError(OH_AVCodec* codec, int32_t errorCode) +{ + RTC_LOG(LS_ERROR) << __FUNCTION__; + (void)codec; + (void)errorCode; +} + +void HardwareVideoDecoder::OnStreamChanged(OH_AVCodec* codec, OH_AVFormat* format) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + (void)codec; + (void)format; +} + +void HardwareVideoDecoder::OnNeedInputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " buffer index: " << index; + (void)codec; + + QueueInputBuffer(ohos::CodecBuffer(index, buffer)); +} + +void HardwareVideoDecoder::OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " buffer index: " << index; + (void)codec; + + if (videoFrameReceiver_) { + DeliverTextureFrame(ohos::CodecBuffer(index, buffer)); + } else { + DeliverByteFrame(ohos::CodecBuffer(index, buffer)); + } +} + +bool HardwareVideoDecoder::InitDecode() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + decoder_ = ohos::VideoDecoder::CreateByName(codecName_.c_str()); + + OH_AVCodecCallback callback; + callback.onError = &HardwareVideoDecoder::OnCodecError1; + callback.onStreamChanged = &HardwareVideoDecoder::OnStreamChanged1; + callback.onNeedInputBuffer = &HardwareVideoDecoder::OnNeedInputBuffer1; + callback.onNewOutputBuffer = &HardwareVideoDecoder::OnNewOutputBuffer1; + int32_t ret = OH_VideoDecoder_RegisterCallback(decoder_.Raw(), callback, this); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to register callback: " << ret; + return ret; + } + + RenderResolution resolution = decoderSettings_.max_render_resolution(); + + auto format = ohos::AVFormat::Create(); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_WIDTH, resolution.Width()); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_HEIGHT, resolution.Height()); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_PIXEL_FORMAT, colorFormat_); + + ret = OH_VideoDecoder_Configure(decoder_.Raw(), format.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to configure: " << ret; + return false; + } + + if (sharedContext_) { + videoFrameReceiver_ = VideoFrameReceiverGl::Create("decoder-texture-thread", sharedContext_); + videoFrameReceiver_->SetVideoFrameSize(resolution.Width(), resolution.Height()); + videoFrameReceiver_->SetCallback(this); + nativeWindow_ = ohos::NativeWindow::CreateFromSurfaceId(videoFrameReceiver_->GetSurfaceId()); + ret = OH_VideoDecoder_SetSurface(decoder_.Raw(), nativeWindow_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set surface: " << ret; + return false; + } + } + + ret = OH_VideoDecoder_Prepare(decoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to prepare: " << ret; + return false; + } + + ret = OH_VideoDecoder_Start(decoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to start: " << ret; + return false; + } + + initialized_ = true; + + return true; +} + +void HardwareVideoDecoder::QueueInputBuffer(const ohos::CodecBuffer& buffer) +{ + UNUSED std::unique_lock lock(inputMutex_); + inputBufferQueue_.push(buffer); + inputCond_.notify_all(); +} + +bool HardwareVideoDecoder::DequeueInputBuffer(ohos::CodecBuffer& buffer) +{ + std::unique_lock lock(inputMutex_); + inputCond_.wait_for( + lock, std::chrono::milliseconds(10), [this]() { return !inputBufferQueue_.empty() || !initialized_; }); + if (!initialized_) { + return false; + } + if (this->inputBufferQueue_.empty()) { + return false; + } + buffer = inputBufferQueue_.front(); + inputBufferQueue_.pop(); + + return true; +} + +void HardwareVideoDecoder::DeliverTextureFrame(const ohos::CodecBuffer& buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + OH_AVCodecBufferAttr attr; + int32_t ret = OH_AVBuffer_GetBufferAttr(buffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + return; + } + RTC_DLOG(LS_VERBOSE) << "buffer attr: offset=" << attr.offset << ", size=" << attr.size << ", flags=" << attr.flags + << ", pts=" << attr.pts; + + RTC_DLOG(LS_VERBOSE) << "render output buffer, index=" << buffer.index; + ret = OH_VideoDecoder_RenderOutputBufferAtTime(decoder_.Raw(), buffer.index, attr.pts * 1000); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to render output buffer" << ret; + } +} + +void HardwareVideoDecoder::DeliverByteFrame(const ohos::CodecBuffer& buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + OH_AVCodecBufferAttr attr; + int32_t ret = OH_AVBuffer_GetBufferAttr(buffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + return; + } + RTC_DLOG(LS_VERBOSE) << "buffer attr: offset=" << attr.offset << ", size=" << attr.size << ", flags=" << attr.flags + << ", pts=" << attr.pts; + + OH_NativeBuffer* nativeBuffer = OH_AVBuffer_GetNativeBuffer(buffer.buf); + if (nativeBuffer == nullptr) { + RTC_LOG(LS_ERROR) << "Failed to get native buffer"; + return; + } + + OH_NativeBuffer_Config config; + OH_NativeBuffer_GetConfig(nativeBuffer, &config); + RTC_DLOG(LS_VERBOSE) << "buffer config: format=" << config.format << ", width=" << config.width + << ", height=" << config.height << ", stride=" << config.stride; + + const int64_t timestampUs = attr.pts; + + FrameExtraInfo extraInfo; + { + UNUSED std::lock_guard lock(extraInfosMutex_); + + do { + if (extraInfos_.empty()) { + RTC_LOG(LS_WARNING) << "unexpected frame: " << timestampUs; + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + return; + } + + extraInfo = extraInfos_.front(); + extraInfos_.pop_front(); + // If the decoder might drop frames so iterate through the queue until we + // find a matching timestamp. + } while (extraInfo.timestampUs != timestampUs); + } + + uint8_t* addr = OH_AVBuffer_GetAddr(buffer.buf); + if (!addr) { + RTC_LOG(LS_ERROR) << "Failed to get buffer addr"; + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + return; + } + + RenderResolution resolution = decoderSettings_.max_render_resolution(); + int width = resolution.Width(); + int height = resolution.Height(); + + auto i420Buffer = I420Buffer::Create(width, height); + switch (colorFormat_) { + case AV_PIXEL_FORMAT_YUVI420: { + // copy data + memcpy(i420Buffer->MutableDataY(), addr, width * height); + memcpy(i420Buffer->MutableDataU(), addr + width * height, width * height / 4); + memcpy(i420Buffer->MutableDataV(), addr + width * height * 5 / 4, width * height / 4); + } break; + case AV_PIXEL_FORMAT_NV12: { + int32_t ret = libyuv::NV12ToI420( + (uint8_t*)addr, width, (uint8_t*)addr + width * height, width, i420Buffer->MutableDataY(), + i420Buffer->StrideY(), i420Buffer->MutableDataU(), i420Buffer->StrideU(), i420Buffer->MutableDataV(), + i420Buffer->StrideV(), width, height); + RTC_DLOG(LS_VERBOSE) << "NV12ToI420 ret = " << ret; + } break; + case AV_PIXEL_FORMAT_NV21: { + int32_t ret = libyuv::NV21ToI420( + (uint8_t*)addr, width, (uint8_t*)addr + width * height, width, i420Buffer->MutableDataY(), + i420Buffer->StrideY(), i420Buffer->MutableDataU(), i420Buffer->StrideU(), i420Buffer->MutableDataV(), + i420Buffer->StrideV(), width, height); + RTC_DLOG(LS_VERBOSE) << "NV21ToI420 ret = " << ret; + } break; + case AV_PIXEL_FORMAT_RGBA: { + int32_t ret = libyuv::ABGRToI420( + (uint8_t*)addr, width * 4, i420Buffer->MutableDataY(), i420Buffer->StrideY(), + i420Buffer->MutableDataU(), i420Buffer->StrideU(), i420Buffer->MutableDataV(), i420Buffer->StrideV(), + width, height); + RTC_DLOG(LS_VERBOSE) << "ABGRToI420 ret = " << ret; + } break; + default: { + RTC_LOG(LS_ERROR) << "Unsupported color format: " << colorFormat_; + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + return; + } + } + + ret = OH_VideoDecoder_FreeOutputBuffer(decoder_.Raw(), buffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + + auto frame = VideoFrame::Builder() + .set_id(65535) + .set_video_frame_buffer(i420Buffer) + .set_rotation(kVideoRotation_0) + .set_timestamp_us(attr.pts) + .set_timestamp_rtp(extraInfo.timestampRtp) + .set_ntp_time_ms(extraInfo.timestampNtp) + .build(); + + RTC_DCHECK_RUNS_SERIALIZED(&callbackRaceChecker_); + callback_->Decoded(frame, std::nullopt, std::nullopt); +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.h b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.h new file mode 100644 index 0000000000000000000000000000000000000000..65c3eefd0db7dcb255a42458082e836b66fc74d9 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder.h @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_H +#define WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_H + +#include "codec_common.h" +#include "../render/egl_context.h" +#include "../helper/avcodec.h" +#include "helper/native_window.h" +#include "video/video_frame_receiver.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace webrtc { +namespace adapter { + +class HardwareVideoDecoder : public VideoDecoder, public VideoFrameReceiver::Callback { +public: + static std::unique_ptr Create( + const std::string& codecName, int32_t colorFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext = nullptr); + + ~HardwareVideoDecoder() override; + +protected: + HardwareVideoDecoder( + const std::string& codecName, int32_t colorFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext); + + bool Configure(const Settings& settings) override; + + int32_t RegisterDecodeCompleteCallback(DecodedImageCallback* callback) override; + + // `missing_frames`, `fragmentation` and `render_time_ms` are ignored. + int32_t Decode(const EncodedImage& input_image, bool /*missing_frames*/, int64_t render_time_ms = -1) override; + + int32_t Release() override; + + DecoderInfo GetDecoderInfo() const override; + + const char* ImplementationName() const override; + + void + OnFrameAvailable(rtc::scoped_refptr buffer, int64_t timestampUs, VideoRotation rotation) override; + +protected: + static void OnCodecError1(OH_AVCodec* codec, int32_t errorCode, void* userData); + static void OnStreamChanged1(OH_AVCodec* codec, OH_AVFormat* format, void* userData); + static void OnNeedInputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData); + static void OnNewOutputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData); + + void OnCodecError(OH_AVCodec* codec, int32_t errorCode); + void OnStreamChanged(OH_AVCodec* codec, OH_AVFormat* format); + void OnNeedInputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer); + void OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer); + +private: + bool InitDecode(); + void QueueInputBuffer(const ohos::CodecBuffer& buffer); + bool DequeueInputBuffer(ohos::CodecBuffer& buffer); + void DeliverTextureFrame(const ohos::CodecBuffer& buffer); + void DeliverByteFrame(const ohos::CodecBuffer& buffer); + +private: + struct FrameExtraInfo { + int64_t timestampUs; // Used as an identifier of the frame. + uint32_t timestampRtp; + int64_t timestampNtp; + }; + + std::atomic initialized_{false}; + + const std::string codecName_; + const SdpVideoFormat& format_; + const int32_t colorFormat_; + const std::shared_ptr sharedContext_; + + Settings decoderSettings_; + + ohos::VideoDecoder decoder_; + + std::unique_ptr videoFrameReceiver_; + ohos::NativeWindow nativeWindow_; + + rtc::RaceChecker callbackRaceChecker_; + DecodedImageCallback* callback_ RTC_GUARDED_BY(callbackRaceChecker_); + + std::mutex inputMutex_; + std::condition_variable inputCond_; + std::queue inputBufferQueue_; + + std::mutex extraInfosMutex_; + std::deque extraInfos_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..807ed45614d7d0687c484207c51b2044ea44df67 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.cpp @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hardware_video_decoder_factory.h" +#include "hardware_video_decoder.h" +#include "media_codec_utils.h" +#include "video_codec_mime_type.h" + +#include +#include + +#include +#include +#include + +namespace webrtc { +namespace adapter { + +HardwareVideoDecoderFactory::HardwareVideoDecoderFactory(std::shared_ptr sharedContext) + : sharedContext_(sharedContext) +{ +} + +HardwareVideoDecoderFactory::~HardwareVideoDecoderFactory() {} + +std::vector HardwareVideoDecoderFactory::GetSupportedFormats() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::vector supportedFormats; + + for (const VideoCodecMimeType type : {VideoCodecMimeType::H264, VideoCodecMimeType::H265}) { + OH_AVCapability* capability = OH_AVCodec_GetCapabilityByCategory(type.mimeType(), false, HARDWARE); + if (!capability) { + RTC_LOG(LS_WARNING) << "No capability for mime type: " << type.mimeType(); + continue; + } + + const char* name = OH_AVCapability_GetName(capability); + RTC_DLOG(LS_VERBOSE) << "capability codec name: " << name; + + uint32_t pixelFormatNum = 0; + const int32_t* pixelFormats = nullptr; + OH_AVCapability_GetVideoSupportedPixelFormats(capability, &pixelFormats, &pixelFormatNum); + for (uint32_t i = 0; i < pixelFormatNum; i++) { + RTC_DLOG(LS_VERBOSE) << "supported pixel format: " << pixelFormats[i]; + } + + if (type == VideoCodecMimeType::H264) { + if (OH_AVCapability_AreProfileAndLevelSupported(capability, AVC_PROFILE_HIGH, AVC_LEVEL_31)) { + supportedFormats.push_back( + CreateH264Format(H264Profile::kProfileConstrainedHigh, H264Level::kLevel3_1, "1")); + } + supportedFormats.push_back( + CreateH264Format(H264Profile::kProfileConstrainedBaseline, H264Level::kLevel3_1, "1")); + } else { + supportedFormats.push_back(SdpVideoFormat(type.name())); + } + } + + RTC_DLOG(LS_VERBOSE) << "supported formats: " << supportedFormats.size(); + + return supportedFormats; +} + +std::unique_ptr HardwareVideoDecoderFactory::CreateVideoDecoder(const SdpVideoFormat& format) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + RTC_DLOG(LS_VERBOSE) << "format: " << format.ToString(); + + VideoCodecMimeType type = VideoCodecMimeType::valueOf(format.name); + OH_AVCapability* capability = OH_AVCodec_GetCapabilityByCategory(type.mimeType(), false, HARDWARE); + if (!capability) { + return nullptr; + } + + const char* codecName = OH_AVCapability_GetName(capability); + RTC_DLOG(LS_VERBOSE) << "codec name: " << codecName; + + std::optional pixelFormat = + ohos::MediaCodecUtils::SelectPixelFormat(ohos::MediaCodecUtils::DECODER_PIXEL_FORMATS, capability); + if (!pixelFormat) { + RTC_LOG(LS_ERROR) << "No supported pixel format"; + return nullptr; + } + RTC_DLOG(LS_VERBOSE) << "selected pixel format: " << *pixelFormat; + + return HardwareVideoDecoder::Create(codecName, *pixelFormat, format, sharedContext_); +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..ee502f087ef438a3a799eeca77dac301470dadf6 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_decoder_factory.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_FACTORY_H + +#include "../render/egl_context.h" + +#include "api/video_codecs/video_decoder_factory.h" + +namespace webrtc { +namespace adapter { + +class HardwareVideoDecoderFactory : public VideoDecoderFactory { +public: + explicit HardwareVideoDecoderFactory(std::shared_ptr sharedContext); + ~HardwareVideoDecoderFactory() override; + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoDecoder(const SdpVideoFormat& format) override; + +private: + const std::shared_ptr sharedContext_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_DECODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ba11abd680df40b75710e0a3ab76ccdd8fbc2d1 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.cpp @@ -0,0 +1,617 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hardware_video_encoder.h" +#include "../render/native_window_renderer_gl.h" +#include "../video_codec/video_codec_mime_type.h" +#include "../helper/native_buffer.h" +#include "../utils/marcos.h" + +#include +#include + +#include +#include + +#include "api/video/video_codec_type.h" +#include "render/egl_config_attributes.h" +#include +#include +#include +#include +#include +#include + +namespace webrtc { +namespace adapter { + +// QP scaling thresholds. +static const int kH264QpThresholdLow = 24; +static const int kH264QpThresholdHigh = 37; + +std::unique_ptr HardwareVideoEncoder::Create( + const std::string& codecName, int32_t pixelFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return std::unique_ptr( + new HardwareVideoEncoder(codecName, pixelFormat, format, sharedContext)); +} + +HardwareVideoEncoder::HardwareVideoEncoder( + const std::string& codecName, int32_t pixelFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext) + : codecName_(codecName), + pixelFormat_(pixelFormat), + format_(format), + sharedContext_(sharedContext), + textureDrawer_(std::make_unique()), + videoFrameDrawer_(std::make_unique()) +{ + UpdateEncoderInfo(); +} + +HardwareVideoEncoder::~HardwareVideoEncoder() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + Release(); +} + +int HardwareVideoEncoder::InitEncode(const VideoCodec* codec_settings, const Settings& settings) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + codecSettings_ = *codec_settings; + + curBitrate_ = codecSettings_.startBitrate * 1000; + targetBitrate_ = curBitrate_; + curFramerate_ = codecSettings_.maxFramerate; + targetFramerate_ = curFramerate_; + + RTC_DLOG(LS_INFO) << "codec settings: codecType=" << codecSettings_.codecType; + RTC_DLOG(LS_INFO) << "codec settings: width=" << codecSettings_.width; + RTC_DLOG(LS_INFO) << "codec settings: height=" << codecSettings_.height; + RTC_DLOG(LS_INFO) << "codec settings: startBitrate=" << codecSettings_.startBitrate; + RTC_DLOG(LS_INFO) << "codec settings: minBitrate=" << codecSettings_.minBitrate; + RTC_DLOG(LS_INFO) << "codec settings: maxBitrate=" << codecSettings_.maxBitrate; + RTC_DLOG(LS_INFO) << "codec settings: maxFramerate=" << codecSettings_.maxFramerate; + RTC_DLOG(LS_INFO) << "codec settings: expect_encode_from_texture=" << codecSettings_.expect_encode_from_texture; + if (codecSettings_.codecType == kVideoCodecH264) { + RTC_DLOG(LS_INFO) << "codec settings: H264.keyFrameInterval=" << codecSettings_.H264()->keyFrameInterval; + } + + auto type = VideoCodecMimeType::valueOf(format_.name); + OH_AVCapability* capability = OH_AVCodec_GetCapabilityByCategory(type.mimeType(), true, HARDWARE); + OH_AVCapability_GetEncoderBitrateRange(capability, &supportedBitrateRange_); + RTC_DLOG(LS_VERBOSE) << "supportedBitrateRange=[" << supportedBitrateRange_.minVal << "~" + << supportedBitrateRange_.maxVal << "]"; + OH_AVRange qualityRange; + OH_AVCapability_GetEncoderQualityRange(capability, &qualityRange); + RTC_DLOG(LS_VERBOSE) << "qualityRange=[" << qualityRange.minVal << "~" << qualityRange.maxVal << "]"; + + encoder_ = ohos::VideoEncoder::CreateByName(codecName_.c_str()); + + OH_AVCodecCallback callback; + callback.onError = &HardwareVideoEncoder::OnCodecError1; + callback.onStreamChanged = &HardwareVideoEncoder::OnStreamChanged1; + callback.onNeedInputBuffer = &HardwareVideoEncoder::OnNeedInputBuffer1; + callback.onNewOutputBuffer = &HardwareVideoEncoder::OnNewOutputBuffer1; + int32_t ret = OH_VideoEncoder_RegisterCallback(encoder_.Raw(), callback, this); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to register callback: " << ret; + return ret; + } + + // 写入format + auto format = ohos::AVFormat::Create(); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_WIDTH, codec_settings->width); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_HEIGHT, codec_settings->height); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_PIXEL_FORMAT, pixelFormat_); + OH_AVFormat_SetDoubleValue(format.Raw(), OH_MD_KEY_FRAME_RATE, targetFramerate_); + OH_AVFormat_SetLongValue(format.Raw(), OH_MD_KEY_BITRATE, targetBitrate_); + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_VIDEO_ENCODE_BITRATE_MODE, OH_VideoEncodeBitrateMode::CBR); + + if (codecSettings_.codecType == kVideoCodecH264) { + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_I_FRAME_INTERVAL, codecSettings_.H264()->keyFrameInterval); + auto profileLevelId = ParseSdpForH264ProfileLevelId(format_.parameters); + if (profileLevelId && (profileLevelId->profile == H264Profile::kProfileConstrainedHigh && + profileLevelId->level == H264Level::kLevel3_1)) + { + OH_AVFormat_SetIntValue(format.Raw(), OH_MD_KEY_PROFILE, AVC_PROFILE_HIGH); + // OH_MD_KEY_LEVEL ? + } + } + + ret = OH_VideoEncoder_Configure(encoder_.Raw(), format.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to configure: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + if (sharedContext_) { + OHNativeWindow* nativeWindow = nullptr; + ret = OH_VideoEncoder_GetSurface(encoder_.Raw(), &nativeWindow); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get surface: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + ret = OH_NativeWindow_NativeWindowHandleOpt( + nativeWindow, SET_BUFFER_GEOMETRY, codecSettings_.width, codecSettings_.height); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set buffer geometry: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + nativeWindow_ = ohos::NativeWindow::TakeOwnership(nativeWindow); + eglEnv_ = EglEnv::Create(sharedContext_, EglConfigAttributes::RGBA); + eglEnv_->CreateWindowSurface(nativeWindow_); + eglEnv_->MakeCurrent(); + } + + ret = OH_VideoEncoder_Prepare(encoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to prepare: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + ret = OH_VideoEncoder_Start(encoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to start: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + initialized_ = true; + RTC_DLOG(LS_VERBOSE) << "Initialized"; + + UpdateEncoderInfo(); + + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t HardwareVideoEncoder::RegisterEncodeCompleteCallback(EncodedImageCallback* callback) +{ + callback_ = callback; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t HardwareVideoEncoder::Encode(const VideoFrame& frame, const std::vector* frame_types) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " frame size=" << frame.width() << "x" << frame.height() + << ", pts=" << frame.timestamp_us(); + + if (!initialized_) { + RTC_LOG(LS_ERROR) << "Not initialized"; + return WEBRTC_VIDEO_CODEC_UNINITIALIZED; + } + + bool requestedKeyFrame = false; + if (frame_types) { + for (auto frameType : *frame_types) { + if (frameType == VideoFrameType::kVideoFrameKey) { + requestedKeyFrame = true; + } + } + } + + if (requestedKeyFrame) { + RTC_DLOG(LS_VERBOSE) << "Request key frame"; + OH_AVFormat* format = OH_AVFormat_Create(); + OH_AVFormat_SetIntValue(format, OH_MD_KEY_REQUEST_I_FRAME, true); + int32_t ret = OH_VideoEncoder_SetParameter(encoder_.Raw(), format); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to set parameter OH_MD_KEY_REQUEST_I_FRAME: " << ret; + } + } + + FrameExtraInfo info; + info.timestampUs = frame.timestamp_us(); + info.timestampRtp = frame.timestamp(); + { + UNUSED std::lock_guard lock(extraInfosMutex_); + extraInfos_.push_back(info); + } + + if (sharedContext_ && eglEnv_) { + return EncodeTextureBuffer(frame); + } + + return EncodeByteBuffer(frame); +} + +void HardwareVideoEncoder::SetRates(const RateControlParameters& parameters) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + RTC_DLOG(LS_VERBOSE) << "bitrate=" << parameters.bitrate.get_sum_bps() + << ", target_bitrate=" << parameters.target_bitrate.get_sum_bps(); + RTC_DLOG(LS_VERBOSE) << "framerate_fps=" << parameters.framerate_fps; + + targetBitrate_ = std::clamp( + parameters.bitrate.get_sum_bps(), static_cast(supportedBitrateRange_.minVal), + static_cast(supportedBitrateRange_.maxVal)); + + if (parameters.framerate_fps <= 0) { + targetFramerate_ = codecSettings_.maxFramerate; + } else { + targetFramerate_ = parameters.framerate_fps; + } + + if (targetBitrate_ != curBitrate_) { + RTC_LOG(LS_INFO) << "Update bitrate: " << targetBitrate_; + auto format = ohos::AVFormat::Create(); + OH_AVFormat_SetLongValue(format.Raw(), OH_MD_KEY_BITRATE, targetBitrate_); + int32_t ret = OH_VideoEncoder_SetParameter(encoder_.Raw(), format.Raw()); + if (ret == AV_ERR_OK) { + curBitrate_ = targetBitrate_; + } else { + RTC_LOG(LS_ERROR) << "Failed to update bitrate" << ret; + } + } + + if (targetFramerate_ != curFramerate_) { + RTC_LOG(LS_INFO) << "Update framerate: " << targetFramerate_; + auto format = ohos::AVFormat::Create(); + OH_AVFormat_SetDoubleValue(format.Raw(), OH_MD_KEY_FRAME_RATE, targetFramerate_); + int32_t ret = OH_VideoEncoder_SetParameter(encoder_.Raw(), format.Raw()); + if (ret == AV_ERR_OK) { + curFramerate_ = targetFramerate_; + } else { + RTC_LOG(LS_ERROR) << "Failed to update framerate" << ret; + } + } +} + +int32_t HardwareVideoEncoder::Release() +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (initialized_) { + int32_t ret = OH_VideoEncoder_Stop(encoder_.Raw()); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to stop" << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + UNUSED std::unique_lock lock(inputMutex_); + std::queue temp; + std::swap(temp, inputBufferQueue_); + } + + { + UNUSED std::lock_guard lock(extraInfosMutex_); + extraInfos_.clear(); + } + + initialized_ = false; + + return WEBRTC_VIDEO_CODEC_OK; +} + +VideoEncoder::EncoderInfo HardwareVideoEncoder::GetEncoderInfo() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return encoderInfo_; +} + +void HardwareVideoEncoder::OnCodecError1(OH_AVCodec* codec, int32_t errorCode, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoEncoder*)userData; + self->OnCodecError(codec, errorCode); +} + +void HardwareVideoEncoder::OnStreamChanged1(OH_AVCodec* codec, OH_AVFormat* format, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoEncoder*)userData; + self->OnStreamChanged(codec, format); +} + +void HardwareVideoEncoder::OnNeedInputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoEncoder*)userData; + self->OnNeedInputBuffer(codec, index, buffer); +} + +void HardwareVideoEncoder::OnNewOutputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData) +{ + if (!userData) { + return; + } + + auto self = (HardwareVideoEncoder*)userData; + self->OnNewOutputBuffer(codec, index, buffer); +} + +void HardwareVideoEncoder::OnCodecError(OH_AVCodec* codec, int32_t errorCode) +{ + (void)codec; + (void)errorCode; + RTC_LOG(LS_ERROR) << __FUNCTION__; +} + +void HardwareVideoEncoder::OnStreamChanged(OH_AVCodec* codec, OH_AVFormat* format) +{ + (void)codec; + (void)format; + RTC_LOG(LS_INFO) << __FUNCTION__; +} + +void HardwareVideoEncoder::OnNeedInputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " buffer index: " << index; + (void)codec; + + QueueInputBuffer(ohos::CodecBuffer(index, buffer)); +} + +void HardwareVideoEncoder::OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " buffer index: " << index; + + OH_AVCodecBufferAttr attr; + int32_t ret = OH_AVBuffer_GetBufferAttr(buffer, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + OH_VideoEncoder_FreeOutputBuffer(codec, index); + return; + } + RTC_DLOG(LS_VERBOSE) << "Output buffer attr: flags=" << attr.flags << ", pts=" << attr.pts << ", size=" << attr.size + << ", offset=" << attr.offset; + + uint8_t* addr = OH_AVBuffer_GetAddr(buffer); + if (!addr) { + RTC_LOG(LS_ERROR) << "Failed to get buffer addr"; + OH_VideoEncoder_FreeOutputBuffer(codec, index); + return; + } + + OH_AVFormat* format = OH_AVBuffer_GetParameter(buffer); + if (!format) { + RTC_LOG(LS_ERROR) << "Failed to get buffer parameter: " << ret; + } else { + RTC_DLOG(LS_VERBOSE) << "buffer parameter: " << OH_AVFormat_DumpInfo(format); + } + + if ((attr.flags & AVCODEC_BUFFER_FLAGS_CODEC_DATA) != 0) { + configData_ = EncodedImageBuffer::Create(addr, attr.size); + OH_VideoEncoder_FreeOutputBuffer(codec, index); + return; + } + + if ((attr.flags & AVCODEC_BUFFER_FLAGS_INCOMPLETE_FRAME) != 0) { + RTC_DLOG(LS_VERBOSE) << "Incomplete frame"; + } + + bool isKeyFrame = (attr.flags & AVCODEC_BUFFER_FLAGS_SYNC_FRAME) != 0; + if (isKeyFrame) { + RTC_DLOG(LS_VERBOSE) << "Sync frame generated"; + } + + if (!initialized_) { + RTC_LOG(LS_ERROR) << "Not initialized"; + OH_VideoEncoder_FreeOutputBuffer(codec, index); + return; + } + + rtc::scoped_refptr encodedData; + if (isKeyFrame && configData_) { + encodedData = EncodedImageBuffer::Create(attr.size + configData_->size()); + std::memcpy(encodedData->data(), configData_->data(), configData_->size()); + std::memcpy(encodedData->data() + configData_->size(), addr + attr.offset, attr.size); + } else { + encodedData = EncodedImageBuffer::Create(addr + attr.offset, attr.size); + } + + ret = OH_VideoEncoder_FreeOutputBuffer(codec, index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to free output buffer"; + } + + const int64_t timestampUs = attr.pts; + FrameExtraInfo extraInfo; + { + UNUSED std::lock_guard lock(extraInfosMutex_); + + do { + if (extraInfos_.empty()) { + RTC_LOG(LS_WARNING) << "Unexpected frame with timestamp: " << timestampUs; + return; + } + + extraInfo = extraInfos_.front(); + extraInfos_.pop_front(); + } while (extraInfo.timestampUs != timestampUs); + } + + EncodedImage encodedImage; + encodedImage._encodedWidth = codecSettings_.width; + encodedImage._encodedHeight = codecSettings_.height; + encodedImage.capture_time_ms_ = timestampUs / rtc::kNumMicrosecsPerMillisec; + encodedImage.SetRtpTimestamp(extraInfo.timestampRtp); + encodedImage.SetEncodedData(encodedData); + encodedImage.set_size(encodedData->size()); + + CodecSpecificInfo info; + info.codecType = codecSettings_.codecType; + + callback_->OnEncodedImage(encodedImage, &info); +} + +void HardwareVideoEncoder::UpdateEncoderInfo() +{ + encoderInfo_.implementation_name = codecName_; + encoderInfo_.supports_native_handle = true; + encoderInfo_.is_hardware_accelerated = true; + encoderInfo_.supports_simulcast = true; + encoderInfo_.scaling_settings = GetScalingSettings(); + + encoderInfo_.requested_resolution_alignment = 16; + encoderInfo_.apply_alignment_to_all_simulcast_layers = false; +} + +VideoEncoder::ScalingSettings HardwareVideoEncoder::GetScalingSettings() +{ + if (codecSettings_.codecType == VideoCodecType::kVideoCodecH264) { + return VideoEncoder::ScalingSettings(kH264QpThresholdLow, kH264QpThresholdHigh); + } + + return VideoEncoder::ScalingSettings::kOff; +} + +bool HardwareVideoEncoder::DequeueInputBuffer(ohos::CodecBuffer& buffer) +{ + std::unique_lock lock(inputMutex_); + inputCond_.wait_for( + lock, std::chrono::milliseconds(10), [this]() { return !inputBufferQueue_.empty() || !initialized_; }); + if (!initialized_) { + return false; + } + if (this->inputBufferQueue_.empty()) { + return false; + } + buffer = inputBufferQueue_.front(); + inputBufferQueue_.pop(); + + return true; +} + +void HardwareVideoEncoder::QueueInputBuffer(const ohos::CodecBuffer& buffer) +{ + UNUSED std::unique_lock lock(inputMutex_); + inputBufferQueue_.push(buffer); + inputCond_.notify_all(); +} + +int32_t HardwareVideoEncoder::EncodeTextureBuffer(const VideoFrame& frame) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " enter"; + + int ret = OH_NativeWindow_NativeWindowHandleOpt(nativeWindow_.Raw(), SET_UI_TIMESTAMP, frame.timestamp_us()); + if (ret != 0) { + RTC_LOG(LS_ERROR) << "Failed to set ui timestamp: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + glClear(GL_COLOR_BUFFER_BIT); + + Matrix4f matrix = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; + videoFrameDrawer_->DrawFrame(frame, *textureDrawer_, matrix); + + if (!eglEnv_->SwapBuffers(frame.timestamp_us() * 1000)) { + return WEBRTC_VIDEO_CODEC_ERROR; + } + + RTC_DLOG(LS_VERBOSE) << __FUNCTION__ << " exit"; + return WEBRTC_VIDEO_CODEC_OK; +} + +int32_t HardwareVideoEncoder::EncodeByteBuffer(const VideoFrame& frame) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + if (frame.is_texture()) { + RTC_LOG(LS_ERROR) << "Texture buffer is not supported in buffer mode yet"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + ohos::CodecBuffer codecBuffer; + if (!DequeueInputBuffer(codecBuffer)) { + RTC_LOG(LS_ERROR) << "Failed to get cached input buffer"; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + OH_AVCodecBufferAttr attr; + int32_t ret = OH_AVBuffer_GetBufferAttr(codecBuffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + RTC_DLOG(LS_VERBOSE) << "Input buffer attr: flags=" << attr.flags << ", pts=" << attr.pts << ", size=" << attr.size + << ", offset=" << attr.offset; + + uint8_t* addr = OH_AVBuffer_GetAddr(codecBuffer.buf); + if (!addr) { + RTC_LOG(LS_ERROR) << "Failed to get buffer addr"; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + auto srcBuffer = frame.video_frame_buffer()->ToI420(); + int width = frame.width(); + int height = frame.height(); + + switch (pixelFormat_) { + case AV_PIXEL_FORMAT_YUVI420: { + // copy + memcpy(addr, srcBuffer->DataY(), width * height); + memcpy(addr + width * height, srcBuffer->DataU(), width * height / 4); + memcpy(addr + width * height * 5 / 4, srcBuffer->DataV(), width * height / 4); + } break; + case AV_PIXEL_FORMAT_NV12: { + int32_t ret = libyuv::I420ToNV12( + srcBuffer->DataY(), srcBuffer->StrideY(), srcBuffer->DataU(), srcBuffer->StrideU(), srcBuffer->DataV(), + srcBuffer->StrideV(), addr, width, addr + width * height, width, width, height); + RTC_DLOG(LS_VERBOSE) << "I420ToNV12 ret = " << ret; + } break; + case AV_PIXEL_FORMAT_NV21: { + int32_t ret = libyuv::I420ToNV21( + srcBuffer->DataY(), srcBuffer->StrideY(), srcBuffer->DataU(), srcBuffer->StrideU(), srcBuffer->DataV(), + srcBuffer->StrideV(), addr, width, addr + width * height, width, width, height); + RTC_DLOG(LS_VERBOSE) << "I420ToNV12 ret = " << ret; + } break; + case AV_PIXEL_FORMAT_RGBA: { + int ret = libyuv::I420ToABGR( + srcBuffer->DataY(), srcBuffer->StrideY(), srcBuffer->DataU(), srcBuffer->StrideU(), srcBuffer->DataV(), + srcBuffer->StrideV(), addr, width * 4, width, height); + RTC_DLOG(LS_VERBOSE) << "I420ToABGR ret = " << ret; + } break; + default: { + RTC_LOG(LS_ERROR) << "Unsupported pixel format: " << pixelFormat_; + QueueInputBuffer(codecBuffer); + } + return WEBRTC_VIDEO_CODEC_ERROR; + } + + attr.pts = frame.timestamp_us(); + attr.size = frame.size(); + ret = OH_AVBuffer_SetBufferAttr(codecBuffer.buf, &attr); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get buffer attr: " << ret; + QueueInputBuffer(codecBuffer); + return WEBRTC_VIDEO_CODEC_ERROR; + } + + ret = OH_VideoEncoder_PushInputBuffer(encoder_.Raw(), codecBuffer.index); + if (ret != AV_ERR_OK) { + RTC_LOG(LS_ERROR) << "Failed to get push input buffer: " << ret; + return WEBRTC_VIDEO_CODEC_ERROR; + } + + return WEBRTC_VIDEO_CODEC_OK; +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.h b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.h new file mode 100644 index 0000000000000000000000000000000000000000..a260c5471a37e914f732f2c220cf5b22e91565d2 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder.h @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_H +#define WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_H + +#include "codec_common.h" +#include "../render/egl_env.h" +#include "../render/egl_context.h" +#include "../render/video_frame_drawer.h" +#include "../video/texture_buffer.h" +#include "../helper/avcodec.h" +#include "../helper/native_window.h" +#include "../helper/native_buffer.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace webrtc { +namespace adapter { + +class HardwareVideoEncoder : public VideoEncoder { +public: + static std::unique_ptr Create( + const std::string& codecName, int32_t pixelFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext = nullptr); + + ~HardwareVideoEncoder() override; + +protected: + HardwareVideoEncoder( + const std::string& codecName, int32_t pixelFormat, const SdpVideoFormat& format, + std::shared_ptr sharedContext); + + int InitEncode(const VideoCodec* codec_settings, const Settings& settings) override; + int32_t RegisterEncodeCompleteCallback(EncodedImageCallback* callback) override; + int32_t Encode(const VideoFrame& frame, const std::vector* frame_types) override; + void SetRates(const RateControlParameters& parameters) override; + EncoderInfo GetEncoderInfo() const override; + int32_t Release() override; + +protected: + static void OnCodecError1(OH_AVCodec* codec, int32_t errorCode, void* userData); + static void OnStreamChanged1(OH_AVCodec* codec, OH_AVFormat* format, void* userData); + static void OnNeedInputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData); + static void OnNewOutputBuffer1(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer, void* userData); + + void OnCodecError(OH_AVCodec* codec, int32_t errorCode); + void OnStreamChanged(OH_AVCodec* codec, OH_AVFormat* format); + void OnNeedInputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer); + void OnNewOutputBuffer(OH_AVCodec* codec, uint32_t index, OH_AVBuffer* buffer); + +private: + void UpdateEncoderInfo(); + VideoEncoder::ScalingSettings GetScalingSettings(); + + bool DequeueInputBuffer(ohos::CodecBuffer& buffer); + void QueueInputBuffer(const ohos::CodecBuffer& buffer); + + int32_t EncodeTextureBuffer(const VideoFrame& frame); + int32_t EncodeByteBuffer(const VideoFrame& frame); + +private: + struct FrameExtraInfo { + int64_t timestampUs; // Used as an identifier of the frame. + uint32_t timestampRtp; + }; + + const std::string codecName_; + const int32_t pixelFormat_; + const SdpVideoFormat format_; + const std::shared_ptr sharedContext_; + + std::atomic initialized_{false}; + + OH_AVRange supportedBitrateRange_; + ohos::VideoEncoder encoder_; + ohos::NativeWindow nativeWindow_; + + std::unique_ptr eglEnv_; + std::unique_ptr textureDrawer_; + std::unique_ptr videoFrameDrawer_; + + VideoCodec codecSettings_; + EncoderInfo encoderInfo_; + uint32_t targetBitrate_; + uint32_t curBitrate_; + uint32_t targetFramerate_; + uint32_t curFramerate_; + + EncodedImageCallback* callback_; + + std::mutex inputMutex_; + std::condition_variable inputCond_; + std::queue inputBufferQueue_; + + rtc::scoped_refptr configData_; + + std::mutex extraInfosMutex_; + std::deque extraInfos_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2b7811b9be6a8257a6b983136a68b79198ab6926 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.cpp @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hardware_video_encoder_factory.h" +#include "hardware_video_encoder.h" +#include "media_codec_utils.h" +#include "video_codec_mime_type.h" + +#include +#include + +#include +#include +#include + +namespace webrtc { +namespace adapter { + +HardwareVideoEncoderFactory::HardwareVideoEncoderFactory( + std::shared_ptr sharedContext, bool enableH264HighProfile) + : sharedContext_(sharedContext), enableH264HighProfile_(enableH264HighProfile) +{ +} + +HardwareVideoEncoderFactory::~HardwareVideoEncoderFactory() {} + +std::vector HardwareVideoEncoderFactory::GetSupportedFormats() const +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + + std::vector supportedFormats; + + for (const VideoCodecMimeType type : {VideoCodecMimeType::H264, VideoCodecMimeType::H265}) { + OH_AVCapability* capability = OH_AVCodec_GetCapabilityByCategory(type.mimeType(), true, HARDWARE); + if (!capability) { + RTC_LOG(LS_WARNING) << "No capability for mime type: " << type.mimeType(); + continue; + } + + const char* name = OH_AVCapability_GetName(capability); + RTC_DLOG(LS_VERBOSE) << "capability codec name: " << name; + + uint32_t pixelFormatNum = 0; + const int32_t* pixelFormats = nullptr; + OH_AVCapability_GetVideoSupportedPixelFormats(capability, &pixelFormats, &pixelFormatNum); + for (uint32_t i = 0; i < pixelFormatNum; i++) { + RTC_DLOG(LS_VERBOSE) << "supported pixel format: " << pixelFormats[i]; + } + + if (type == VideoCodecMimeType::H264) { + if (enableH264HighProfile_ && + OH_AVCapability_AreProfileAndLevelSupported(capability, AVC_PROFILE_HIGH, AVC_LEVEL_31)) + { + supportedFormats.push_back( + CreateH264Format(H264Profile::kProfileConstrainedHigh, H264Level::kLevel3_1, "1")); + } + + supportedFormats.push_back( + CreateH264Format(H264Profile::kProfileConstrainedBaseline, H264Level::kLevel3_1, "1")); + } else { + supportedFormats.push_back(SdpVideoFormat(type.name())); + } + } + + RTC_DLOG(LS_VERBOSE) << "supported formats: " << supportedFormats.size(); + + return supportedFormats; +} + +std::unique_ptr HardwareVideoEncoderFactory::CreateVideoEncoder(const SdpVideoFormat& format) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + RTC_DLOG(LS_VERBOSE) << "format: " << format.ToString(); + + VideoCodecMimeType type = VideoCodecMimeType::valueOf(format.name); + OH_AVCapability* capability = OH_AVCodec_GetCapabilityByCategory(type.mimeType(), true, HARDWARE); + if (!capability) { + return nullptr; + } + + if (type == VideoCodecMimeType::H264) { + bool isHighProfile = + format.IsSameCodec(CreateH264Format(H264Profile::kProfileConstrainedHigh, H264Level::kLevel3_1, "1")); + bool isBaselineProfile = + format.IsSameCodec(CreateH264Format(H264Profile::kProfileConstrainedBaseline, H264Level::kLevel3_1, "1")); + + if (!isHighProfile && !isBaselineProfile) { + return nullptr; + } + if (isHighProfile && !enableH264HighProfile_) { + return nullptr; + } + } + + const char* codecName = OH_AVCapability_GetName(capability); + RTC_DLOG(LS_VERBOSE) << "codec name: " << codecName; + + std::optional pixelFormat = + ohos::MediaCodecUtils::SelectPixelFormat(ohos::MediaCodecUtils::ENCODER_PIXEL_FORMATS, capability); + if (!pixelFormat) { + RTC_LOG(LS_ERROR) << "No supported pixel format"; + return nullptr; + } + RTC_DLOG(LS_VERBOSE) << "supported pixel format: " << *pixelFormat; + + return HardwareVideoEncoder::Create(codecName, *pixelFormat, format, sharedContext_); +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..c4c1ae69542ba6a6b20e153e7d549e4b4be40c98 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/hardware_video_encoder_factory.h @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_FACTORY_H + +#include "../render/egl_context.h" + +#include + +namespace webrtc { +namespace adapter { + +class HardwareVideoEncoderFactory : public VideoEncoderFactory { +public: + HardwareVideoEncoderFactory(std::shared_ptr sharedContext, bool enableH264HighProfile); + ~HardwareVideoEncoderFactory() override; + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoEncoder(const SdpVideoFormat& format) override; + +private: + const std::shared_ptr sharedContext_; + const bool enableH264HighProfile_{false}; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_HARDWARE_VIDEO_ENCODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5bc514f5c35c749052798a14e70a9dd7f2fc83d4 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.cpp @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "media_codec_utils.h" + +#include + +namespace ohos { + +const std::vector MediaCodecUtils::DECODER_PIXEL_FORMATS{ + AV_PIXEL_FORMAT_RGBA, + AV_PIXEL_FORMAT_YUVI420, + AV_PIXEL_FORMAT_NV12, + AV_PIXEL_FORMAT_NV21, +}; + +const std::vector MediaCodecUtils::ENCODER_PIXEL_FORMATS{ + AV_PIXEL_FORMAT_RGBA, + AV_PIXEL_FORMAT_YUVI420, + AV_PIXEL_FORMAT_NV12, + AV_PIXEL_FORMAT_NV21, +}; + +std::optional +MediaCodecUtils::SelectPixelFormat(const std::vector& supportedPixelFormats, OH_AVCapability* capability) +{ + if (capability) { + const int32_t* pixelFormats = nullptr; + uint32_t pixelFormatNum = 0; + OH_AVCapability_GetVideoSupportedPixelFormats(capability, &pixelFormats, &pixelFormatNum); + + for (int32_t supportedColorFormat : supportedPixelFormats) { + for (uint32_t i = 0; i < pixelFormatNum; i++) { + if (supportedColorFormat == pixelFormats[i]) { + return supportedColorFormat; + } + } + } + } + + return std::nullopt; +} + +} // namespace ohos diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.h b/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..40aa199089876ece5ead581c61586f8a1c4f5524 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/media_codec_utils.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_MEDIA_CODEC_UTILS_H +#define WEBRTC_VIDEO_CODEC_MEDIA_CODEC_UTILS_H + +#include +#include + +#include +#include + +namespace ohos { + +class MediaCodecUtils { +public: + // OH_AVPixelFormat + static const std::vector DECODER_PIXEL_FORMATS; + static const std::vector ENCODER_PIXEL_FORMATS; + + static std::optional + SelectPixelFormat(const std::vector& pixelFormats, OH_AVCapability* capability); +}; + +} // namespace ohos + +#endif // WEBRTC_VIDEO_CODEC_MEDIA_CODEC_UTILS_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1530a0dbebb9fa081e0bf54442a0a6c03116271d --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "software_video_decoder_factory.h" + +#include "api/video_codecs/video_decoder_factory_template.h" +#include "api/video_codecs/video_decoder_factory_template_dav1d_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_decoder_factory_template_libvpx_vp9_adapter.h" +#ifdef WEBRTC_USE_H264 +#include +#endif + +namespace webrtc { +namespace adapter { + +namespace { + +using BuiltinVideoDecoderFactory = VideoDecoderFactoryTemplate< +#ifdef WEBRTC_USE_H264 + OpenH264DecoderTemplateAdapter, +#endif + LibvpxVp8DecoderTemplateAdapter, LibvpxVp9DecoderTemplateAdapter, + Dav1dDecoderTemplateAdapter +>; + +} // namespace + +SoftwareVideoDecoderFactory::SoftwareVideoDecoderFactory() : internal_(std::make_unique()) +{ +} + +std::vector SoftwareVideoDecoderFactory::GetSupportedFormats() const +{ + return internal_->GetSupportedFormats(); +} + +std::unique_ptr SoftwareVideoDecoderFactory::CreateVideoDecoder(const SdpVideoFormat& format) +{ + auto original_format = FuzzyMatchSdpVideoFormat(internal_->GetSupportedFormats(), format); + return original_format ? internal_->CreateVideoDecoder(*original_format) : nullptr; +} + +VideoDecoderFactory::CodecSupport +SoftwareVideoDecoderFactory::QueryCodecSupport(const SdpVideoFormat& format, bool reference_scaling) const +{ + auto original_format = FuzzyMatchSdpVideoFormat(internal_->GetSupportedFormats(), format); + return original_format ? internal_->QueryCodecSupport(*original_format, reference_scaling) + : VideoDecoderFactory::CodecSupport{.is_supported = false}; +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..03eed48a9e1510d1be9f64e90fcc51a2a844c785 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_decoder_factory.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_DECODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_DECODER_FACTORY_H + +#include "api/video_codecs/video_decoder.h" +#include "api/video_codecs/video_decoder_factory.h" + +namespace webrtc { +namespace adapter { + +class SoftwareVideoDecoderFactory : public VideoDecoderFactory { +public: + SoftwareVideoDecoderFactory(); + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoDecoder(const SdpVideoFormat& format) override; + + CodecSupport QueryCodecSupport(const SdpVideoFormat& format, bool reference_scaling) const override; + +private: + const std::unique_ptr internal_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_DECODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..52efd2588e89ff10b0652f668a2dea2a7c9525b2 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.cpp @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "software_video_encoder_factory.h" + +#include "api/video_codecs/video_encoder_factory_template.h" +#include "api/video_codecs/video_encoder_factory_template_libaom_av1_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp8_adapter.h" +#include "api/video_codecs/video_encoder_factory_template_libvpx_vp9_adapter.h" +#ifdef WEBRTC_USE_H264 +#include +#endif + +namespace webrtc { +namespace adapter { + +namespace { + +using BuiltinVideoEncoderFactory = VideoEncoderFactoryTemplate< +#ifdef WEBRTC_USE_H264 + OpenH264EncoderTemplateAdapter, +#endif + LibvpxVp8EncoderTemplateAdapter, LibaomAv1EncoderTemplateAdapter, + LibvpxVp9EncoderTemplateAdapter>; + +} // namespace + +SoftwareVideoEncoderFactory::SoftwareVideoEncoderFactory() : internal_(std::make_unique()) +{ +} + +std::vector SoftwareVideoEncoderFactory::GetSupportedFormats() const +{ + return internal_->GetSupportedFormats(); +} + +std::unique_ptr SoftwareVideoEncoderFactory::CreateVideoEncoder(const SdpVideoFormat& format) +{ + auto original_format = FuzzyMatchSdpVideoFormat(internal_->GetSupportedFormats(), format); + return original_format ? internal_->CreateVideoEncoder(*original_format) : nullptr; +} + +VideoEncoderFactory::CodecSupport SoftwareVideoEncoderFactory::QueryCodecSupport( + const SdpVideoFormat& format, absl::optional scalability_mode) const +{ + auto original_format = FuzzyMatchSdpVideoFormat(internal_->GetSupportedFormats(), format); + return original_format ? internal_->QueryCodecSupport(*original_format, scalability_mode) + : VideoEncoderFactory::CodecSupport{.is_supported = false}; +} + +} // namespace adapter +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..ad911c57bc2033ade9c4c92840572282dfe6096e --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/software_video_encoder_factory.h @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_ENCODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_ENCODER_FACTORY_H + +#include "api/video_codecs/video_encoder_factory.h" + +namespace webrtc { +namespace adapter { + +class SoftwareVideoEncoderFactory : public VideoEncoderFactory { +public: + SoftwareVideoEncoderFactory(); + + std::vector GetSupportedFormats() const override; + + std::unique_ptr CreateVideoEncoder(const SdpVideoFormat& format) override; + + CodecSupport + QueryCodecSupport(const SdpVideoFormat& format, absl::optional scalability_mode) const override; + +private: + const std::unique_ptr internal_; +}; + +} // namespace adapter +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_SOFTWARE_VIDEO_ENCODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_codec/video_codec_mime_type.h b/sdk/ohos/src/ohos_webrtc/video_codec/video_codec_mime_type.h new file mode 100644 index 0000000000000000000000000000000000000000..ac3b5e6005610dd08edc4410992c53fbe14c1866 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_codec/video_codec_mime_type.h @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_VIDEO_CODEC_MIME_TYPE_H +#define WEBRTC_VIDEO_CODEC_VIDEO_CODEC_MIME_TYPE_H + +#include + +namespace webrtc { + +struct VideoCodecMimeType { + struct VideoCodecMimeTypeInit { + std::string_view name; + std::string_view mimeType; + constexpr operator VideoCodecMimeType() const + { + return VideoCodecMimeType{name, mimeType}; + } + }; + + static constexpr VideoCodecMimeTypeInit VP8{"VP8", "video/x-vnd.on2.vp8"}; + static constexpr VideoCodecMimeTypeInit VP9{"VP9", "video/x-vnd.on2.vp9"}; + static constexpr VideoCodecMimeTypeInit AV1{"AV1", "video/av01"}; + static constexpr VideoCodecMimeTypeInit H264{"H264", "video/avc"}; + static constexpr VideoCodecMimeTypeInit H265{"H265", "video/hevc"}; + + constexpr VideoCodecMimeType(std::string_view name, std::string_view mimeType) : name_{name}, mimeType_(mimeType) {} + + const char* name() const + { + return name_.data(); + } + + const char* mimeType() const + { + return mimeType_.data(); + } + + static VideoCodecMimeType valueOf(const std::string_view name) + { + if (name == VP8.name) { + return VP8; + } else if (name == VP9.name) { + return VP9; + } else if (name == AV1.name) { + return AV1; + } else if (name == H264.name) { + return H264; + } else if (name == H265.name) { + return H265; + } + + return VideoCodecMimeType("", ""); + } + + constexpr bool operator==(const VideoCodecMimeType& other) const noexcept + { + return name_ == other.name_ && mimeType_ == other.mimeType_; + } + +private: + std::string_view name_; + std::string_view mimeType_; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_VIDEO_CODEC_MIME_TYPE_H diff --git a/sdk/ohos/src/ohos_webrtc/video_decoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_decoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..95d695139ca73ca156db37a50f002b4a67ea0d3c --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_decoder_factory.cpp @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "video_decoder_factory.h" +#include "utils/marcos.h" +#include "video_codec/hardware_video_decoder_factory.h" +#include "video_codec/software_video_decoder_factory.h" +#include "video_codec/default_video_decoder_factory.h" + +#include + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiHardwareVideoDecoderFactory::constructor_; + +void NapiHardwareVideoDecoderFactory::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiHardwareVideoDecoderFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiHardwareVideoDecoderFactory::~NapiHardwareVideoDecoderFactory() = default; + +NapiHardwareVideoDecoderFactory::NapiHardwareVideoDecoderFactory(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + if (info.Length() > 0) { + if (info[0].IsObject()) { + sharedContext_ = NapiEglContext::Unwrap(info[0].As())->Get(); + } + } else { + sharedContext_ = EglEnv::GetDefault().GetContext(); + } + + info.This().As().TypeTag(&kTypeTag); +} + +Napi::Value NapiHardwareVideoDecoderFactory::GetSharedContext(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return NapiEglContext::NewInstance(info.Env(), sharedContext_); +} + +Napi::Value NapiHardwareVideoDecoderFactory::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiHardwareVideoDecoderFactory"); +#endif + return json; +} + +FunctionReference NapiSoftwareVideoDecoderFactory::constructor_; + +void NapiSoftwareVideoDecoderFactory::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiSoftwareVideoDecoderFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiSoftwareVideoDecoderFactory::~NapiSoftwareVideoDecoderFactory() = default; + +NapiSoftwareVideoDecoderFactory::NapiSoftwareVideoDecoderFactory(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + info.This().As().TypeTag(&kTypeTag); +} + +Napi::Value NapiSoftwareVideoDecoderFactory::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiSoftwareVideoDecoderFactory"); +#endif + return json; +} + +std::unique_ptr CreateVideoDecoderFactory(const Napi::Object& jsVideoDecoderFactory) +{ + if (NAPI_CHECK_TYPE_TAG(jsVideoDecoderFactory, NapiHardwareVideoDecoderFactory)) { + auto napiFactory = NapiHardwareVideoDecoderFactory::Unwrap(jsVideoDecoderFactory); + auto sharedContext = napiFactory->GetSharedContext(); + return std::make_unique(sharedContext); + } else if (NAPI_CHECK_TYPE_TAG(jsVideoDecoderFactory, NapiSoftwareVideoDecoderFactory)) { + return std::make_unique(); + } + + return nullptr; +} + +std::unique_ptr CreateDefaultVideoDecoderFactory() +{ + return std::make_unique(EglEnv::GetDefault().GetContext()); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_decoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_decoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..b371a62594b210c73dee5c8d9ead948fc77f4d75 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_decoder_factory.h @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_VIDEO_DECODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_VIDEO_DECODER_FACTORY_H + +#include "render/egl_env.h" +#include "utils/marcos.h" + +#include + +#include + +namespace webrtc { + +class NapiHardwareVideoDecoderFactory : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(HardwareVideoDecoderFactory); + NAPI_ATTRIBUTE_NAME_DECLARE(SharedContext, sharedContext); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + NAPI_TYPE_TAG_DECLARE(0xd8917b4837764a46, 0xb69d705ec2e65b37); + + static void Init(Napi::Env env, Napi::Object exports); + + ~NapiHardwareVideoDecoderFactory(); + + std::shared_ptr GetSharedContext() + { + return sharedContext_; + } + +protected: + friend class ObjectWrap; + + explicit NapiHardwareVideoDecoderFactory(const Napi::CallbackInfo& info); + + Napi::Value GetSharedContext(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::shared_ptr sharedContext_; +}; + +class NapiSoftwareVideoDecoderFactory : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(SoftwareVideoDecoderFactory); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + NAPI_TYPE_TAG_DECLARE(0x6c59721271134289, 0xa4a93236ff9897df); + + static void Init(Napi::Env env, Napi::Object exports); + + ~NapiSoftwareVideoDecoderFactory(); + +protected: + friend class ObjectWrap; + + explicit NapiSoftwareVideoDecoderFactory(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; +}; + +std::unique_ptr CreateVideoDecoderFactory(const Napi::Object& jsVideoDecoderFactory); + +std::unique_ptr CreateDefaultVideoDecoderFactory(); + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_VIDEO_DECODER_FACTORY_H diff --git a/sdk/ohos/src/ohos_webrtc/video_encoder_factory.cpp b/sdk/ohos/src/ohos_webrtc/video_encoder_factory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b44c647acc4e850763cc81be7667f9407962f9e8 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_encoder_factory.cpp @@ -0,0 +1,142 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "video_encoder_factory.h" +#include "render/egl_env.h" +#include "utils/marcos.h" +#include "video_codec/hardware_video_encoder_factory.h" +#include "video_codec/software_video_encoder_factory.h" +#include "video_codec/default_video_encoder_factory.h" + +#include + +namespace webrtc { + +using namespace Napi; + +FunctionReference NapiHardwareVideoEncoderFactory::constructor_; + +void NapiHardwareVideoEncoderFactory::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceAccessor<&NapiHardwareVideoEncoderFactory::GetEnableH264HighProfile>( + kAttributeNameEnableH264HighProfile), + InstanceAccessor<&NapiHardwareVideoEncoderFactory::GetSharedContext>(kAttributeNameSharedContext), + InstanceMethod<&NapiHardwareVideoEncoderFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiHardwareVideoEncoderFactory::~NapiHardwareVideoEncoderFactory() = default; + +NapiHardwareVideoEncoderFactory::NapiHardwareVideoEncoderFactory(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + if (info.Length() > 0) { + if (info[0].IsBoolean()) { + enableH264HighProfile_ = info[0].As().Value(); + } + } + + if (info.Length() > 1) { + if (info[1].IsObject()) { + sharedContext_ = NapiEglContext::Unwrap(info[1].As())->Get(); + } + } else { + sharedContext_ = EglEnv::GetDefault().GetContext(); + } + + info.This().As().TypeTag(&kTypeTag); +} + +Napi::Value NapiHardwareVideoEncoderFactory::GetEnableH264HighProfile(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return Boolean::New(info.Env(), enableH264HighProfile_); +} + +Napi::Value NapiHardwareVideoEncoderFactory::GetSharedContext(const Napi::CallbackInfo& info) +{ + RTC_DLOG(LS_VERBOSE) << __FUNCTION__; + return NapiEglContext::NewInstance(info.Env(), sharedContext_); +} + +Napi::Value NapiHardwareVideoEncoderFactory::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiHardwareVideoEncoderFactory"); +#endif + json.Set(kAttributeNameEnableH264HighProfile, Boolean::New(info.Env(), enableH264HighProfile_)); + return json; +} + +FunctionReference NapiSoftwareVideoEncoderFactory::constructor_; + +void NapiSoftwareVideoEncoderFactory::Init(Napi::Env env, Napi::Object exports) +{ + RTC_LOG(LS_VERBOSE) << __FUNCTION__; + + Function func = DefineClass( + env, kClassName, + { + InstanceMethod<&NapiSoftwareVideoEncoderFactory::ToJson>(kMethodNameToJson), + }); + exports.Set(kClassName, func); + + constructor_ = Persistent(func); +} + +NapiSoftwareVideoEncoderFactory::~NapiSoftwareVideoEncoderFactory() = default; + +NapiSoftwareVideoEncoderFactory::NapiSoftwareVideoEncoderFactory(const Napi::CallbackInfo& info) : ObjectWrap(info) +{ + info.This().As().TypeTag(&kTypeTag); +} + +Napi::Value NapiSoftwareVideoEncoderFactory::ToJson(const Napi::CallbackInfo& info) +{ + auto json = Object::New(info.Env()); +#ifndef NDEBUG + json.Set("__native_class__", "NapiSoftwareVideoEncoderFactory"); +#endif + return json; +} + +std::unique_ptr CreateVideoEncoderFactory(const Napi::Object& jsVideoEncoderFactory) +{ + if (NAPI_CHECK_TYPE_TAG(jsVideoEncoderFactory, NapiHardwareVideoEncoderFactory)) { + auto napiFactory = NapiHardwareVideoEncoderFactory::Unwrap(jsVideoEncoderFactory); + auto sharedContext = napiFactory->GetSharedContext(); + auto enableH264HighProfile = napiFactory->GetEnableH264HighProfile(); + return std::make_unique(sharedContext, enableH264HighProfile); + } else if (NAPI_CHECK_TYPE_TAG(jsVideoEncoderFactory, NapiSoftwareVideoEncoderFactory)) { + return std::make_unique(); + } + + return nullptr; +} + +std::unique_ptr CreateDefaultVideoEncoderFactory() +{ + return std::make_unique(EglEnv::GetDefault().GetContext(), false); +} + +} // namespace webrtc diff --git a/sdk/ohos/src/ohos_webrtc/video_encoder_factory.h b/sdk/ohos/src/ohos_webrtc/video_encoder_factory.h new file mode 100644 index 0000000000000000000000000000000000000000..f9bb70b70f10eb23df14a0b39711d4d63cafe850 --- /dev/null +++ b/sdk/ohos/src/ohos_webrtc/video_encoder_factory.h @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2024 Archermind Technology (Nanjing) Co. Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WEBRTC_VIDEO_CODEC_VIDEO_ENCODER_FACTORY_H +#define WEBRTC_VIDEO_CODEC_VIDEO_ENCODER_FACTORY_H + +#include "render/egl_context.h" +#include "utils/marcos.h" + +#include + +#include + +namespace webrtc { + +class NapiHardwareVideoEncoderFactory : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(HardwareVideoEncoderFactory); + NAPI_ATTRIBUTE_NAME_DECLARE(SharedContext, sharedContext); + NAPI_ATTRIBUTE_NAME_DECLARE(EnableH264HighProfile, enableH264HighProfile); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + NAPI_TYPE_TAG_DECLARE(0x0d9878cc5e534620, 0xb005829df9cc4eb5); + + static void Init(Napi::Env env, Napi::Object exports); + + ~NapiHardwareVideoEncoderFactory(); + + std::shared_ptr GetSharedContext() + { + return sharedContext_; + } + bool GetEnableH264HighProfile() const + { + return enableH264HighProfile_; + } + +protected: + friend class ObjectWrap; + + explicit NapiHardwareVideoEncoderFactory(const Napi::CallbackInfo& info); + + Napi::Value GetEnableH264HighProfile(const Napi::CallbackInfo& info); + Napi::Value GetSharedContext(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; + + std::shared_ptr sharedContext_; + bool enableH264HighProfile_{false}; +}; + +class NapiSoftwareVideoEncoderFactory : public Napi::ObjectWrap { +public: + NAPI_CLASS_NAME_DECLARE(SoftwareVideoEncoderFactory); + NAPI_METHOD_NAME_DECLARE(ToJson, toJSON); + NAPI_TYPE_TAG_DECLARE(0x54d352bb27e3497a, 0xa7a147d57b7fce62); + + static void Init(Napi::Env env, Napi::Object exports); + + ~NapiSoftwareVideoEncoderFactory(); + +protected: + friend class ObjectWrap; + + explicit NapiSoftwareVideoEncoderFactory(const Napi::CallbackInfo& info); + Napi::Value ToJson(const Napi::CallbackInfo& info); + +private: + static Napi::FunctionReference constructor_; +}; + +std::unique_ptr CreateVideoEncoderFactory(const Napi::Object& jsVideoEncoderFactory); + +std::unique_ptr CreateDefaultVideoEncoderFactory(); + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_CODEC_VIDEO_ENCODER_FACTORY_H diff --git a/system_wrappers/BUILD.gn b/system_wrappers/BUILD.gn index 2576d4e3f3d0159799f104ccc4c0ec9c666ac0f4..2318647c4380bef5a495442e7f45b6076fb951bc 100644 --- a/system_wrappers/BUILD.gn +++ b/system_wrappers/BUILD.gn @@ -60,7 +60,7 @@ rtc_library("system_wrappers") { libs += [ "log" ] } - if (is_linux || is_chromeos) { + if (is_linux || is_chromeos || is_ohos) { if (!build_with_chromium) { sources += [ "source/cpu_features_linux.cc" ] }