expo-nodemediaclient 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/CLAUDE.md +55 -0
  3. package/README.md +203 -19
  4. package/android/build.gradle +6 -7
  5. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodeMediaClientModule.kt +1 -1
  6. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodePlayerView.kt +15 -20
  7. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodePlayerViewModule.kt +58 -0
  8. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodePublisherView.kt +175 -0
  9. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodePublisherViewModule.kt +111 -0
  10. package/build/ExpoNodeMediaClientModule.js.map +1 -1
  11. package/build/ExpoNodePlayerView.d.ts +9 -3
  12. package/build/ExpoNodePlayerView.d.ts.map +1 -1
  13. package/build/ExpoNodePlayerView.js +3 -18
  14. package/build/ExpoNodePlayerView.js.map +1 -1
  15. package/build/ExpoNodePublisherView.d.ts +91 -0
  16. package/build/ExpoNodePublisherView.d.ts.map +1 -0
  17. package/build/ExpoNodePublisherView.js +44 -0
  18. package/build/ExpoNodePublisherView.js.map +1 -0
  19. package/build/index.d.ts +2 -1
  20. package/build/index.d.ts.map +1 -1
  21. package/build/index.js +3 -5
  22. package/build/index.js.map +1 -1
  23. package/expo-module.config.json +4 -2
  24. package/ios/ExpoNodeMediaClientModule.swift +1 -1
  25. package/ios/ExpoNodePlayerView.swift +76 -70
  26. package/ios/{ExpoNodePlayerModule.swift → ExpoNodePlayerViewModule.swift} +8 -6
  27. package/ios/ExpoNodePublisherView.swift +174 -0
  28. package/ios/ExpoNodePublisherViewModule.swift +114 -0
  29. package/ios/ExpoNodemediaclient.podspec +2 -3
  30. package/package.json +1 -1
  31. package/src/ExpoNodeMediaClientModule.tsx +1 -1
  32. package/src/ExpoNodePlayerView.tsx +12 -25
  33. package/src/ExpoNodePublisherView.tsx +99 -0
  34. package/src/index.ts +4 -7
  35. package/android/src/main/java/expo/modules/nodemediaclient/ExpoNodePlayerModule.kt +0 -60
package/CHANGELOG.md CHANGED
@@ -1,2 +1,12 @@
1
+ ## [0.2.1] 2026-01-05
2
+ - SDK更新到4.1
3
+ - 添加推流静音,镜头缩放,补光灯开关api
4
+
5
+ ## [0.2.0] 2025-12-31
6
+ - SDK更新到4.0
7
+ - 添加推流实现
8
+
9
+ ## [0.1.2] 2025-10-13
10
+
1
11
  ## [0.1.1] 2025-07-25
2
12
  - 实现播放功能
package/CLAUDE.md ADDED
@@ -0,0 +1,55 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is **expo-nodemediaclient**, an Expo native module providing live streaming capabilities using the NodeMedia SDK 4.0. It enables RTMP/RTSP/HLS/HTTP-FLV video playback and RTMP live streaming (publishing) for React Native applications using Expo's Continuous Native Generation (CNG) workflow.
8
+
9
+ ## Architecture
10
+
11
+ The module follows Expo's custom native module pattern with three main components:
12
+
13
+ - **NodeMediaClient** (`ExpoNodeMediaClientModule.tsx`) - SDK license management, requires separate license keys for iOS and Android
14
+ - **NodePlayer** (`ExpoNodePlayerView.tsx`) - Video playback component using `requireNativeViewManager`
15
+ - **NodePublisher** (`ExpoNodePublisherView.tsx`) - Camera capture and streaming component using `requireNativeViewManager`
16
+
17
+ The native implementations are in:
18
+ - `ios/ExpoNodemediaclient/` - iOS native code (Objective-C/Swift)
19
+ - `android/src/main/java/` - Android native code (Java)
20
+
21
+ Module configuration is in `expo-module.config.json` which maps platform-specific module names.
22
+
23
+ ## Development Commands
24
+
25
+ ```bash
26
+ npm run build # Build the module (TypeScript -> build/)
27
+ npm run clean # Clean build artifacts
28
+ npm run lint # Run linting
29
+ npm run test # Run tests
30
+ npm run prepare # Prepare module for development
31
+ npm run prepublishOnly # Prepare for publishing
32
+ npm run open:ios # Open example iOS project in Xcode
33
+ npm run open:android # Open example Android project in Android Studio
34
+ ```
35
+
36
+ The example app in `example/` demonstrates both Player and Publisher functionality with navigation between screens.
37
+
38
+ ## Key Constants
39
+
40
+ NodePublisher exposes these important constants:
41
+ - `NMC_CODEC_ID_AAC`, `NMC_CODEC_ID_H264`, `NMC_CODEC_ID_H265` - Codec identifiers
42
+ - `NMC_PROFILE_AUTO` - Auto profile selection
43
+ - `VIDEO_ORIENTATION_PORTRAIT`, `VIDEO_ORIENTATION_LANDSCAPE` - Video orientation
44
+ - `EFFECTOR_STYLE_ID_FAIRSKIN` - Beauty filter style
45
+
46
+ ## Event Callbacks
47
+
48
+ Both NodePlayer and NodePublisher support `onEventCallback` that receives `{ nativeEvent: { event: number, msg: string } }`. The `event` code indicates status changes (connection, buffering, error, etc.).
49
+
50
+ ## Important Notes
51
+
52
+ - **License Required**: NodeMedia SDK requires separate license keys for iOS and Android from https://www.nodemedia.cn
53
+ - **CNG Required**: Projects must use Expo's Continuous Native Generation - run `npx expo prebuild` if using managed workflow
54
+ - **Permissions**: Publisher requires camera and microphone permissions; use `expo-camera` plugin for permission handling
55
+ - **SDK Version**: Currently on NodeMedia SDK 4.0 (updated in v0.2.0)
package/README.md CHANGED
@@ -39,6 +39,7 @@ if (Platform.OS === 'ios') {
39
39
  ```
40
40
 
41
41
  ### 播放
42
+
42
43
  ```js
43
44
  import { NodePlayer, NodePlayerRef } from 'expo-nodemediaclient';
44
45
  import { useRef, useState } from 'react';
@@ -71,36 +72,219 @@ export default function App() {
71
72
  </SafeAreaView>
72
73
  );
73
74
  }
75
+ ```
76
+
77
+ ### 组件属性
78
+
79
+ | 属性 | 类型 | 说明 |
80
+ |------|------|------|
81
+ | `url` | `string` | 播放地址 (支持 RTMP、RTSP、HLS、HTTP-FLV) |
82
+ | `bufferTime` | `number` | 缓冲时间 (毫秒),默认 1000 |
83
+ | `scaleMode` | `number` | 缩放模式,`0`=填充,`1`=适应,`2`=拉伸 |
84
+ | `volume` | `number` | 音量 (0.0 - 1.0),默认 1.0 |
85
+ | `onEventCallback` | `(event) => void` | 事件回调 |
86
+
87
+ ### 常用方法
74
88
 
89
+ ```js
90
+ // 开始播放
91
+ playerRef.current?.start(url);
92
+
93
+ // 停止播放
94
+ playerRef.current?.stop();
75
95
  ```
76
96
 
77
- ### 进入页面自动播放
97
+ ### 事件回调
98
+
78
99
  ```js
79
- export default function App() {
80
- const [url, setUrl] = useState('rtmp://192.168.0.2/live/bbb');
81
- const [bufferTime, setBufferTime] = useState(0);
82
- const [scaleMode, setScaleMode] = useState(2);
83
- const playerRef = useRef<NodePlayerRef>(null);
84
-
85
- useEffect(() => {
86
- playerRef.current?.start(url);
87
- return () => {
88
- playerRef.current?.stop();
89
- };
90
- }, []);
100
+ const handleEvent = (event: { nativeEvent: NodePlayerEventCallback }) => {
101
+ console.log('事件码:', event.nativeEvent.event);
102
+ console.log('消息:', event.nativeEvent.msg);
103
+ };
104
+
105
+ <NodePlayer
106
+ onEventCallback={handleEvent}
107
+ // ...其他属性
108
+ />
109
+ ```
110
+
111
+
112
+ ## 推流
113
+
114
+ ### 基本用法
115
+
116
+ ```js
117
+ import { NodePublisher, NodePublisherRef } from 'expo-nodemediaclient';
118
+ import { useRef, useState } from 'react';
119
+
120
+ export default function PublisherScreen() {
121
+ const [url, setUrl] = useState('rtmp://192.168.0.2/live/stream');
122
+ const [isPublishing, setIsPublishing] = useState(false);
123
+ const publisherRef = useRef<NodePublisherRef>(null);
124
+
125
+ const handleTogglePublish = () => {
126
+ if (!isPublishing) {
127
+ setIsPublishing(true);
128
+ publisherRef.current?.start(url);
129
+ } else {
130
+ setIsPublishing(false);
131
+ publisherRef.current?.stop();
132
+ }
133
+ };
91
134
 
92
135
  return (
93
136
  <SafeAreaView style={{ flex: 1 }}>
94
- <NodePlayer
95
- ref={playerRef}
137
+ <NodePublisher
138
+ ref={publisherRef}
139
+ style={{ flex: 1, backgroundColor: '#000' }}
96
140
  url={url}
97
- bufferTime={bufferTime}
98
- scaleMode={scaleMode}
99
- style={{ backgroundColor: '#000000', height: 300 }}
141
+ audioParam={{
142
+ codecid: NodePublisher.NMC_CODEC_ID_AAC,
143
+ profile: NodePublisher.NMC_PROFILE_AUTO,
144
+ channels: 2,
145
+ samplingRate: 44100,
146
+ bitrate: 64_000,
147
+ }}
148
+ videoParam={{
149
+ codecid: NodePublisher.NMC_CODEC_ID_H264,
150
+ profile: NodePublisher.NMC_PROFILE_AUTO,
151
+ width: 720,
152
+ height: 1280,
153
+ fps: 30,
154
+ bitrate: 2000_000,
155
+ }}
156
+ />
157
+ <Button
158
+ title={isPublishing ? "停止" : "推流"}
159
+ onPress={handleTogglePublish}
100
160
  />
101
161
  </SafeAreaView>
102
162
  );
103
163
  }
104
164
  ```
105
165
 
106
- ## 推流
166
+ ### 组件属性
167
+
168
+ | 属性 | 类型 | 说明 |
169
+ |------|------|------|
170
+ | `url` | `string` | 推流地址 (RTMP) |
171
+ | `audioParam` | `AudioParam` | 音频编码参数 |
172
+ | `videoParam` | `VideoParam` | 视频编码参数 |
173
+ | `videoOrientation` | `number` | 视频方向,`NodePublisher.VIDEO_ORIENTATION_PORTRAIT` 或 `NodePublisher.VIDEO_ORIENTATION_LANDSCAPE` |
174
+ | `keyFrameInterval` | `number` | 关键帧间隔 (秒),默认 2 |
175
+ | `frontCamera` | `boolean` | 是否使用前置摄像头,默认 false |
176
+ | `cameraFrontMirror` | `boolean` | 前置摄像头是否镜像,默认 true |
177
+ | `HWAccelEnable` | `boolean` | 是否启用硬件加速,默认 true |
178
+ | `denoiseEnable` | `boolean` | 是否启用降噪,默认 true |
179
+ | `colorStyleId` | `number` | 色彩风格ID,如 `NodePublisher.EFFECTOR_STYLE_ID_FAIRSKIN` |
180
+ | `colorStyleIntensity` | `number` | 色彩强度 (0.0 - 1.0) |
181
+ | `smoothskinIntensity` | `number` | 磨皮强度 (0.0 - 1.0) |
182
+ | `onEventCallback` | `(event) => void` | 事件回调 |
183
+
184
+ ### AudioParam
185
+
186
+ | 属性 | 类型 | 说明 |
187
+ |------|------|------|
188
+ | `codecid` | `number` | 编码ID,`NodePublisher.NMC_CODEC_ID_AAC` |
189
+ | `profile` | `number` | 编码profile,`NodePublisher.NMC_PROFILE_AUTO` |
190
+ | `channels` | `number` | 声道数,1 或 2 |
191
+ | `samplingRate` | `number` | 采样率,如 44100 |
192
+ | `bitrate` | `number` | 比特率,如 64000 |
193
+
194
+ ### VideoParam
195
+
196
+ | 属性 | 类型 | 说明 |
197
+ |------|------|------|
198
+ | `codecid` | `number` | 编码ID,`NodePublisher.NMC_CODEC_ID_H264` 或 `NodePublisher.NMC_CODEC_ID_H265` |
199
+ | `profile` | `number` | 编码profile,`NodePublisher.NMC_PROFILE_AUTO` |
200
+ | `width` | `number` | 视频宽度 |
201
+ | `height` | `number` | 视频高度 |
202
+ | `fps` | `number` | 帧率 |
203
+ | `bitrate` | `number` | 比特率,如 2000000 |
204
+
205
+ ### 常用方法
206
+
207
+ ```js
208
+ // 开始推流
209
+ publisherRef.current?.start(url);
210
+
211
+ // 停止推流
212
+ publisherRef.current?.stop();
213
+ ```
214
+
215
+ ### 事件回调
216
+
217
+ ```js
218
+ const handleEvent = (event: { nativeEvent: NodePlayerEventCallback }) => {
219
+ console.log('事件码:', event.nativeEvent.event);
220
+ console.log('消息:', event.nativeEvent.msg);
221
+ };
222
+
223
+ <NodePublisher
224
+ onEventCallback={handleEvent}
225
+ // ...其他属性
226
+ />
227
+ ```
228
+
229
+ ### 权限说明
230
+
231
+ 使用推流功能需要摄像头和麦克风权限,建议使用 `expo-camera` 来申请权限。
232
+
233
+ 首先安装依赖:
234
+
235
+ ```bash
236
+ npm install expo-camera
237
+ ```
238
+
239
+ 在 `app.json` 中配置 `expo-camera` 插件:
240
+
241
+ ```json
242
+ {
243
+ "expo": {
244
+ "plugins": [
245
+ [
246
+ "expo-camera",
247
+ {
248
+ "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera",
249
+ "microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone",
250
+ "recordAudioAndroid": true
251
+ }
252
+ ]
253
+ ],
254
+ "ios": {
255
+ "infoPlist": {
256
+ "NSCameraUsageDescription": "需要访问摄像头以进行直播推流",
257
+ "NSMicrophoneUsageDescription": "需要访问麦克风以进行直播推流"
258
+ }
259
+ },
260
+ "android": {
261
+ "permissions": [
262
+ "android.permission.CAMERA",
263
+ "android.permission.RECORD_AUDIO",
264
+ "android.permission.INTERNET"
265
+ ]
266
+ }
267
+ }
268
+ }
269
+ ```
270
+
271
+ 在代码中申请权限:
272
+
273
+ ```js
274
+ import { useCameraPermissions, useMicrophonePermissions } from 'expo-camera';
275
+ import { useEffect } from 'react';
276
+
277
+ export default function App() {
278
+ const [cameraPermission, requestCameraPermission] = useCameraPermissions();
279
+ const [microphonePermission, requestMicrophonePermission] = useMicrophonePermissions();
280
+
281
+ useEffect(() => {
282
+ requestCameraPermission();
283
+ requestMicrophonePermission();
284
+ }, []);
285
+
286
+ const hasPermission = cameraPermission?.granted && microphonePermission?.granted;
287
+
288
+ // ...
289
+ }
290
+ ```
@@ -1,7 +1,6 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'expo.modules.nodemediaclient'
4
- version = '0.1.1'
5
4
 
6
5
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
6
  apply from: expoModulesCorePlugin
@@ -23,10 +22,10 @@ if (useManagedAndroidSdkVersions) {
23
22
  }
24
23
  }
25
24
  project.android {
26
- compileSdkVersion safeExtGet("compileSdkVersion", 34)
25
+ compileSdkVersion safeExtGet("compileSdkVersion", 36)
27
26
  defaultConfig {
28
- minSdkVersion safeExtGet("minSdkVersion", 21)
29
- targetSdkVersion safeExtGet("targetSdkVersion", 34)
27
+ minSdkVersion safeExtGet("minSdkVersion", 24)
28
+ targetSdkVersion safeExtGet("targetSdkVersion", 36)
30
29
  }
31
30
  }
32
31
  }
@@ -34,13 +33,13 @@ if (useManagedAndroidSdkVersions) {
34
33
  android {
35
34
  namespace "expo.modules.nodemediaclient"
36
35
  defaultConfig {
37
- versionCode 1
38
- versionName "0.1.1"
36
+ versionCode 201
37
+ versionName "0.2.1"
39
38
  }
40
39
  lintOptions {
41
40
  abortOnError false
42
41
  }
43
42
  dependencies {
44
- implementation 'com.github.NodeMedia:NodeMediaClient-Android:3.2.12'
43
+ implementation 'com.github.NodeMedia:NodeMediaClient-Android:4.1.0'
45
44
  }
46
45
  }
@@ -20,4 +20,4 @@ class ExpoNodeMediaClientModule : Module() {
20
20
  LICENSE_KEY = license
21
21
  }
22
22
  }
23
- }
23
+ }
@@ -1,30 +1,22 @@
1
- //
2
- // Copyright (c) 2025 NodeMedia Technology Co., Ltd.
3
- // Created by Chen Mingliang on 2025-07-22.
4
- // All rights reserved.
5
- //
6
-
7
1
  package expo.modules.nodemediaclient
8
2
 
9
3
  import android.content.Context
10
- import android.util.Log
11
- import android.webkit.WebView
12
- import android.webkit.WebViewClient
13
- import android.widget.FrameLayout
14
4
  import expo.modules.kotlin.AppContext
15
5
  import expo.modules.kotlin.viewevent.EventDispatcher
16
6
  import expo.modules.kotlin.views.ExpoView
17
- import cn.nodemedia.NodePlayer;
7
+
8
+ import android.util.Log
9
+ import android.widget.FrameLayout
10
+ import cn.nodemedia.NodePlayer
18
11
 
19
12
  class ExpoNodePlayerView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
20
- // Creates and initializes an event dispatcher for the `onLoad` event.
21
- // The name of the event is inferred from the value and needs to match the event name defined in the module.
22
- private val onLoad by EventDispatcher()
23
13
  private val TAG = "ExpoNodePlayerView"
24
14
  private var np: NodePlayer? = null
15
+ private val onEventCallback by EventDispatcher()
16
+
25
17
  var url = ""
26
18
  var cryptoKey = "" // Add this property
27
- set(value) {
19
+ set(value) {
28
20
  field = value
29
21
  np?.setCryptoKey(value)
30
22
  }
@@ -60,7 +52,7 @@ class ExpoNodePlayerView(context: Context, appContext: AppContext) : ExpoView(co
60
52
  np?.setRTSPTransport(value)
61
53
  }
62
54
 
63
- var HWAccelEnable= true
55
+ var HWAccelEnable = true
64
56
  set(value) {
65
57
  field = value
66
58
  np?.setHWAccelEnable(value)
@@ -76,6 +68,9 @@ class ExpoNodePlayerView(context: Context, appContext: AppContext) : ExpoView(co
76
68
  override fun onAttachedToWindow() {
77
69
  super.onAttachedToWindow()
78
70
  np = NodePlayer(context, LICENSE_KEY)
71
+ np?.setOnNodePlayerEventListener { obj, event, msg ->
72
+ onEventCallback(mapOf("event" to event, "msg" to msg))
73
+ }
79
74
  np?.attachView(videoView)
80
75
  }
81
76
 
@@ -86,8 +81,8 @@ class ExpoNodePlayerView(context: Context, appContext: AppContext) : ExpoView(co
86
81
  super.onDetachedFromWindow()
87
82
  }
88
83
 
89
- fun start(url: String?){
90
- if(!url.isNullOrEmpty()) {
84
+ fun start(url: String?) {
85
+ if (!url.isNullOrEmpty()) {
91
86
  this.url = url
92
87
  }
93
88
  np?.setVolume(this.volume)
@@ -101,7 +96,7 @@ class ExpoNodePlayerView(context: Context, appContext: AppContext) : ExpoView(co
101
96
  np?.start(this.url)
102
97
  }
103
98
 
104
- fun stop(){
99
+ fun stop() {
105
100
  np?.stop()
106
101
  }
107
- }
102
+ }
@@ -0,0 +1,58 @@
1
+ package expo.modules.nodemediaclient
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+
6
+ class ExpoNodePlayerViewModule : Module() {
7
+ override fun definition() = ModuleDefinition {
8
+ Name("ExpoNodePlayerView")
9
+
10
+ View(ExpoNodePlayerView::class) {
11
+ Events("onEventCallback")
12
+
13
+ Prop("url") { view: ExpoNodePlayerView, url: String ->
14
+ view.url = url
15
+ }
16
+
17
+ Prop("volume") { view: ExpoNodePlayerView, volume: Float ->
18
+ view.volume = volume
19
+ }
20
+
21
+ Prop("cryptoKey") { view: ExpoNodePlayerView, cryptoKey: String ->
22
+ view.cryptoKey = cryptoKey
23
+ }
24
+
25
+ Prop("scaleMode") { view: ExpoNodePlayerView, scaleMode: Int ->
26
+ view.scaleMode = scaleMode
27
+ }
28
+
29
+ Prop("bufferTime") { view: ExpoNodePlayerView, bufferTime: Int ->
30
+ view.bufferTime = bufferTime
31
+ }
32
+
33
+ Prop("HTTPReferer") { view: ExpoNodePlayerView, HTTPReferer: String ->
34
+ view.HTTPReferer = HTTPReferer
35
+ }
36
+
37
+ Prop("HTTPUserAgent") { view: ExpoNodePlayerView, HTTPUserAgent: String ->
38
+ view.HTTPUserAgent = HTTPUserAgent
39
+ }
40
+
41
+ Prop("RTSPTransport") { view: ExpoNodePlayerView, RTSPTransport: String ->
42
+ view.RTSPTransport = RTSPTransport
43
+ }
44
+
45
+ Prop("HWAccelEnable") { view: ExpoNodePlayerView, HWAccelEnable: Boolean ->
46
+ view.HWAccelEnable = HWAccelEnable
47
+ }
48
+
49
+ AsyncFunction("start") { view: ExpoNodePlayerView, url: String? ->
50
+ view.start(url)
51
+ }
52
+
53
+ AsyncFunction("stop") { view: ExpoNodePlayerView ->
54
+ view.stop()
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,175 @@
1
+ package expo.modules.nodemediaclient
2
+
3
+ import android.content.Context
4
+ import expo.modules.kotlin.AppContext
5
+ import expo.modules.kotlin.views.ExpoView
6
+
7
+ import android.widget.FrameLayout
8
+ import cn.nodemedia.NodePublisher
9
+ import expo.modules.kotlin.viewevent.EventDispatcher
10
+
11
+ class ExpoNodePublisherView(context: Context, appContext: AppContext) :
12
+ ExpoView(context, appContext) {
13
+ private val TAG = "ExpoNodePublisherView"
14
+ private var np: NodePublisher? = null
15
+ private val onEventCallback by EventDispatcher()
16
+
17
+ var url = ""
18
+ var cryptoKey = ""
19
+ var HWAccelEnable = true
20
+ var denoiseEnable = true
21
+
22
+ var zoomRatio: Float? = null
23
+ set(value) {
24
+ field = value
25
+ value?.let { np?.setZoomRatio(it) }
26
+ }
27
+
28
+ var volume: Float? = null
29
+ set(value) {
30
+ field = value
31
+ value?.let { np?.setVolume(it) }
32
+ }
33
+
34
+ var torchEnable: Boolean? = null
35
+ set(value) {
36
+ field = value
37
+ value?.let { np?.setTorchEnable(it) }
38
+ }
39
+
40
+ // Color and effect parameters
41
+ var colorStyleId: Int? = null
42
+ set(value) {
43
+ field = value
44
+ value?.let { np?.setEffectStyle(it) }
45
+ }
46
+
47
+ var colorStyleIntensity: Float? = null
48
+ set(value) {
49
+ field = value
50
+ value?.let { np?.setEffectParameter("style", it) }
51
+ }
52
+
53
+ var smoothskinIntensity: Float? = null
54
+ set(value) {
55
+ field = value
56
+ value?.let { np?.setEffectParameter("smoothskin", it) }
57
+ }
58
+
59
+ // Audio parameters
60
+ var audioCodecId = NodePublisher.NMC_CODEC_ID_AAC
61
+ var audioProfile = NodePublisher.NMC_PROFILE_AUTO
62
+ var audioChannels = 2
63
+ var audioSamplingRate = 44100
64
+ var audioBitrate = 64000
65
+
66
+ // Video parameters
67
+ var videoCodecId = NodePublisher.NMC_CODEC_ID_H264
68
+ var videoProfile = NodePublisher.NMC_PROFILE_AUTO
69
+ var videoWidth = 720
70
+ var videoHeight = 1280
71
+ var videoFps = 30
72
+ var videoBitrate = 2000000
73
+
74
+ var frontCamera: Boolean? = null
75
+ set(value) {
76
+ field = value
77
+ value?.let {
78
+ np?.closeCamera()
79
+ np?.openCamera(if (it) NodePublisher.NMC_CAMERA_FRONT else NodePublisher.NMC_CAMERA_BACK)
80
+ }
81
+ }
82
+
83
+ // Other parameters
84
+ var keyFrameInterval = 2
85
+ var videoOrientation = 1
86
+
87
+ var cameraFrontMirror: Boolean? = null
88
+ set(value) {
89
+ field = value
90
+ value?.let { np?.setCameraFrontMirror(it) }
91
+ }
92
+
93
+ private val videoView = FrameLayout(context).apply {
94
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
95
+ }
96
+
97
+ init {
98
+ addView(videoView)
99
+ }
100
+
101
+ override fun onAttachedToWindow() {
102
+ super.onAttachedToWindow()
103
+ np = NodePublisher(context, LICENSE_KEY)
104
+ np?.setOnNodePublisherEventListener { obj, event, msg ->
105
+ onEventCallback(mapOf("event" to event, "msg" to msg))
106
+ }
107
+ // Apply audio and video params
108
+ applyAudioParams()
109
+ applyVideoParams()
110
+
111
+ // Apply crypto key and HWAccelEnable
112
+ np?.setCryptoKey(this.cryptoKey)
113
+ np?.setHWAccelEnable(this.HWAccelEnable)
114
+ np?.setDenoiseEnable(this.denoiseEnable)
115
+
116
+ // Apply color and effect params if set
117
+ colorStyleId?.let { np?.setEffectStyle(it) }
118
+ colorStyleIntensity?.let { np?.setEffectParameter("style", it) }
119
+ smoothskinIntensity?.let { np?.setEffectParameter("smoothskin", it) }
120
+
121
+ // Apply volume if set
122
+ volume?.let { np?.setVolume(it) }
123
+
124
+ // opencamera and attachview
125
+ np?.openCamera(if (frontCamera == true) 0 else 1)
126
+ np?.attachView(videoView)
127
+
128
+ np?.setZoomRatio(zoomRatio ?: 1f)
129
+ }
130
+
131
+ override fun onDetachedFromWindow() {
132
+ np?.stop()
133
+ np?.closeCamera()
134
+ np?.detachView()
135
+ np = null
136
+ super.onDetachedFromWindow()
137
+ }
138
+
139
+ private fun applyAudioParams() {
140
+ np?.setAudioCodecParam(
141
+ audioCodecId, audioProfile,
142
+ audioSamplingRate, audioChannels, audioBitrate
143
+ )
144
+ }
145
+
146
+ private fun applyVideoParams() {
147
+ np?.setVideoCodecParam(
148
+ videoCodecId,
149
+ videoProfile, videoWidth, videoHeight, videoFps, videoBitrate
150
+ )
151
+ }
152
+
153
+ fun start(url: String?) {
154
+ if (!url.isNullOrEmpty()) {
155
+ this.url = url
156
+ }
157
+ np?.start(this.url)
158
+ }
159
+
160
+ fun stop() {
161
+ np?.stop()
162
+ }
163
+
164
+ fun setEffectParameter(key: String, value: Float) {
165
+ np?.setEffectParameter(key, value)
166
+ }
167
+
168
+ fun setEffectStyle(style: Int) {
169
+ np?.setEffectStyle(style)
170
+ }
171
+
172
+ fun startFocusAndMeteringCenter() {
173
+ np?.startFocusAndMeteringCenter()
174
+ }
175
+ }