blix-expo-settings 0.1.9 → 0.1.10

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.
@@ -11,10 +11,7 @@ public class ExpoSettingsModule: Module {
11
11
  public func definition() -> ModuleDefinition {
12
12
  Name("ExpoSettings")
13
13
 
14
- // View component (preview)
15
- View(ExpoSettingsView.self) {
16
- // sem props
17
- }
14
+ View(ExpoSettingsView.self) {}
18
15
 
19
16
  Events("onStreamStatus")
20
17
 
@@ -23,97 +20,72 @@ public class ExpoSettingsModule: Module {
23
20
  }
24
21
 
25
22
  Function("initializePreview") { () -> Void in
26
- Task {
27
- self.setStatus("previewInitializing")
23
+ self.setStatus("previewInitializing")
28
24
 
29
- // 0) Configura AVAudioSession
30
- self.configureAudioSession()
25
+ self.configureAudioSession()
31
26
 
32
- // 1) Cria connection + stream
33
- let connection = RTMPConnection()
34
- self.rtmpConnection = connection
27
+ let connection = RTMPConnection()
28
+ self.rtmpConnection = connection
35
29
 
36
- let stream = RTMPStream(connection: connection)
37
- self.rtmpStream = stream
38
-
39
- // 2) Configura stream (tudo que influencia proporção/encode)
40
- self.configureStream(stream)
41
-
42
- // 3) Attach áudio
43
- self.attachAudioIfAvailable(stream)
30
+ let stream = RTMPStream(connection: connection)
31
+ self.rtmpStream = stream
44
32
 
45
- // 4) Attach câmera frontal (portrait + mirror)
46
- await self.attachFrontCamera(stream)
33
+ self.configureStream(stream)
34
+ self.attachAudioIfAvailable(stream)
35
+ self.attachFrontCamera(stream)
36
+ self.attachPreviewIfAvailable(stream)
47
37
 
48
- // 5) Attach preview (MainActor)
49
- await self.attachPreviewIfAvailable(stream)
50
-
51
- self.setStatus("previewReady")
52
- }
38
+ self.setStatus("previewReady")
53
39
  }
54
40
 
55
41
  Function("publishStream") { (url: String, streamKey: String) -> Void in
56
- Task {
57
- print("[ExpoSettings] Publishing stream to URL: \(url) with key: \(streamKey)")
58
- self.setStatus("connecting")
42
+ self.setStatus("connecting")
43
+ print("[ExpoSettings] Publishing to: \(url) key: \(streamKey)")
59
44
 
60
- // Caso não tenha initializePreview, cria do zero com config completa
61
- if self.rtmpConnection == nil || self.rtmpStream == nil {
62
- print("[ExpoSettings] WARNING: Connection or stream not initialized. Creating new ones.")
63
-
64
- let connection = RTMPConnection()
65
- self.rtmpConnection = connection
45
+ // Se não existe stream/connection, cria e aplica TODA a config
46
+ if self.rtmpConnection == nil || self.rtmpStream == nil {
47
+ let connection = RTMPConnection()
48
+ self.rtmpConnection = connection
66
49
 
67
- let stream = RTMPStream(connection: connection)
68
- self.rtmpStream = stream
50
+ let stream = RTMPStream(connection: connection)
51
+ self.rtmpStream = stream
69
52
 
70
- self.configureStream(stream)
71
- self.attachAudioIfAvailable(stream)
72
- await self.attachFrontCamera(stream)
73
- await self.attachPreviewIfAvailable(stream)
74
- }
53
+ self.configureStream(stream)
54
+ self.attachAudioIfAvailable(stream)
55
+ self.attachFrontCamera(stream)
56
+ self.attachPreviewIfAvailable(stream)
57
+ }
75
58
 
76
- // Conecta
77
- self.rtmpConnection?.connect(url)
78
- self.setStatus("connected")
59
+ self.rtmpConnection?.connect(url)
60
+ self.setStatus("connected")
79
61
 
80
- // Publica
81
- self.setStatus("publishing")
82
- self.rtmpStream?.publish(streamKey)
83
- print("[ExpoSettings] Stream published successfully")
62
+ self.setStatus("publishing")
63
+ self.rtmpStream?.publish(streamKey)
84
64
 
85
- self.setStatus("started")
86
- }
65
+ self.setStatus("started")
87
66
  }
88
67
 
89
68
  Function("stopStream") { () -> Void in
90
- Task {
91
- print("[ExpoSettings] stopStream called")
92
-
93
- if let stream = self.rtmpStream {
94
- print("[ExpoSettings] Stopping stream publication")
95
- stream.close()
69
+ print("[ExpoSettings] stopStream called")
96
70
 
97
- // Libera recursos
98
- stream.attachCamera(nil)
99
- stream.attachAudio(nil)
100
- }
71
+ if let stream = self.rtmpStream {
72
+ stream.close()
73
+ stream.attachCamera(nil)
74
+ stream.attachAudio(nil)
75
+ }
101
76
 
102
- if let connection = self.rtmpConnection {
103
- print("[ExpoSettings] Closing RTMP connection")
104
- connection.close()
105
- }
77
+ if let connection = self.rtmpConnection {
78
+ connection.close()
79
+ }
106
80
 
107
- self.rtmpStream = nil
108
- self.rtmpConnection = nil
81
+ self.rtmpStream = nil
82
+ self.rtmpConnection = nil
109
83
 
110
- print("[ExpoSettings] Stream and connection closed and resources released")
111
- self.setStatus("stopped")
112
- }
84
+ self.setStatus("stopped")
113
85
  }
114
86
  }
115
87
 
116
- // MARK: - Helpers
88
+ // MARK: - Internals
117
89
 
118
90
  private func setStatus(_ status: String) {
119
91
  self.currentStreamStatus = status
@@ -123,46 +95,38 @@ public class ExpoSettingsModule: Module {
123
95
  private func configureAudioSession() {
124
96
  let session = AVAudioSession.sharedInstance()
125
97
  do {
126
- try session.setCategory(.playAndRecord,
127
- mode: .default,
128
- options: [.defaultToSpeaker, .allowBluetooth])
98
+ try session.setCategory(
99
+ .playAndRecord,
100
+ mode: .default,
101
+ options: [.defaultToSpeaker, .allowBluetooth]
102
+ )
129
103
  try session.setActive(true)
130
104
  } catch {
131
105
  print("[ExpoSettings] AVAudioSession error:", error)
132
106
  }
133
107
  }
134
108
 
135
- /// Configuração centralizada do stream (evita mismatch entre preview/publish).
136
109
  private func configureStream(_ stream: RTMPStream) {
137
110
  print("[ExpoSettings] Configuring stream...")
138
111
 
139
- // IMPORTANTE: preset primeiro, depois FPS (evita fallback estranho)
112
+ // 1) Captura previsível (16:9)
140
113
  stream.sessionPreset = .hd1280x720
141
114
  stream.frameRate = 30
142
115
 
143
- // Orientação no nível do stream (governança do pipeline)
116
+ // 2) Orientação no nível do stream (governa pipeline)
144
117
  stream.videoOrientation = .portrait
145
118
 
146
- // Sugestão para estabilidade de scaling/mix
147
- stream.videoMixerSettings.mode = .offscreen
148
-
149
- // Opcional: deixa o capture session gerenciar áudio automaticamente
150
- stream.configuration { captureSession in
151
- captureSession.automaticallyConfiguresApplicationAudioSession = true
152
- }
153
-
154
- // Áudio
119
+ // 3) Áudio
155
120
  var audioSettings = AudioCodecSettings()
156
121
  audioSettings.bitRate = 128 * 1000
157
122
  stream.audioSettings = audioSettings
158
123
 
159
- // Vídeo (vertical 9:16)
160
- // 720x1280 é um “sweet spot” pra compatibilidade e estabilidade
124
+ // 4) Vídeo (9:16). Use 720x1280 para estabilidade
161
125
  let videoSettings = VideoCodecSettings(
162
126
  videoSize: .init(width: 720, height: 1280),
163
127
  bitRate: 4_000 * 1000,
164
128
  profileLevel: kVTProfileLevel_H264_Baseline_3_1 as String,
165
- scalingMode: .trim, // sem distorção (corta excesso). Alternativa: .letterbox (barras)
129
+ scalingMode: .trim, // sem distorção (corta). Alternativa: .letterbox (barras)
166
130
  bitRateMode: .average,
167
131
  maxKeyFrameIntervalDuration: 2,
168
132
  allowFrameReordering: nil,
@@ -170,28 +134,30 @@ public class ExpoSettingsModule: Module {
170
134
  )
171
135
  stream.videoSettings = videoSettings
172
136
 
173
- print("[ExpoSettings] Stream configured: preset=\(stream.sessionPreset.rawValue), fps=\(stream.frameRate), orientation=\(stream.videoOrientation.rawValue)")
174
- print("[ExpoSettings] Encoder target: \(Int(videoSettings.videoSize.width))x\(Int(videoSettings.videoSize.height)) scaling=\(videoSettings.scalingMode.rawValue)")
137
+ print("[ExpoSettings] Stream configured preset=\(stream.sessionPreset.rawValue) fps=\(stream.frameRate)")
138
+ print("[ExpoSettings] Target=\(Int(videoSettings.videoSize.width))x\(Int(videoSettings.videoSize.height)) orientation=portrait")
175
139
  }
176
140
 
177
141
  private func attachAudioIfAvailable(_ stream: RTMPStream) {
178
142
  if let audioDevice = AVCaptureDevice.default(for: .audio) {
179
- print("[ExpoSettings] Attaching audio device")
143
+ print("[ExpoSettings] Attaching audio")
180
144
  stream.attachAudio(audioDevice)
181
145
  } else {
182
146
  print("[ExpoSettings] No audio device found")
183
147
  }
184
148
  }
185
149
 
186
- private func attachFrontCamera(_ stream: RTMPStream) async {
187
- guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera,
188
- for: .video,
189
- position: .front) else {
190
- print("[ExpoSettings] No front camera device found")
150
+ private func attachFrontCamera(_ stream: RTMPStream) {
151
+ guard let camera = AVCaptureDevice.default(
152
+ .builtInWideAngleCamera,
153
+ for: .video,
154
+ position: .front
155
+ ) else {
156
+ print("[ExpoSettings] No front camera found")
191
157
  return
192
158
  }
193
159
 
194
- print("[ExpoSettings] Attaching front camera device")
160
+ print("[ExpoSettings] Attaching front camera")
195
161
 
196
162
  stream.attachCamera(camera) { videoUnit, error in
197
163
  guard let unit = videoUnit else {
@@ -200,4 +166,20 @@ public class ExpoSettingsModule: Module {
200
166
  }
201
167
 
202
168
  unit.isVideoMirrored = true
203
- unit.videoOrientation = .po
169
+ unit.videoOrientation = .portrait // <-- GARANTA que está .portrait (não .po)
170
+ unit.preferredVideoStabilizationMode = .standard
171
+ unit.colorFormat = kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
172
+ }
173
+ }
174
+
175
+ private func attachPreviewIfAvailable(_ stream: RTMPStream) {
176
+ DispatchQueue.main.async {
177
+ if let preview = ExpoSettingsView.current {
178
+ print("[ExpoSettings] Attaching stream to preview")
179
+ preview.attachStream(stream)
180
+ } else {
181
+ print("[ExpoSettings] Preview not available yet")
182
+ }
183
+ }
184
+ }
185
+ }
@@ -3,40 +3,34 @@ import HaishinKit
3
3
  import AVFoundation
4
4
 
5
5
  public class ExpoSettingsView: ExpoView {
6
-
7
- public static weak var current: ExpoSettingsView?
8
-
9
- // A view de preview do HaishinKit
10
- private let hkView: MTHKView = {
11
- let view = MTHKView(frame: .zero)
12
- view.videoGravity = .resizeAspectFill
13
- return view
14
- }()
6
+ public static weak var current: ExpoSettingsView?
15
7
 
16
- required init(appContext: AppContext? = nil) {
17
- super.init(appContext: appContext)
18
- clipsToBounds = true
19
- addSubview(hkView)
20
- ExpoSettingsView.current = self
21
- print("[ExpoSettingsView] View initialized and set as current")
22
- }
8
+ private let hkView: MTHKView = {
9
+ let view = MTHKView(frame: .zero)
10
+ view.videoGravity = .resizeAspectFill
11
+ return view
12
+ }()
23
13
 
24
- override public func layoutSubviews() {
25
- super.layoutSubviews()
26
- hkView.frame = bounds
27
- print("[ExpoSettingsView] Layout updated, frame: \(bounds)")
28
- }
14
+ required init(appContext: AppContext? = nil) {
15
+ super.init(appContext: appContext)
16
+ clipsToBounds = true
17
+ addSubview(hkView)
18
+ ExpoSettingsView.current = self
19
+ print("[ExpoSettingsView] initialized")
20
+ }
29
21
 
30
- /// Conecta o RTMPStream a essa view (internamente já faz o attachCamera + renderização)
31
- public func attachStream(_ stream: RTMPStream) {
32
- print("[ExpoSettingsView] Attaching stream to view")
33
- hkView.attachStream(stream)
34
- }
35
-
36
- deinit {
37
- print("[ExpoSettingsView] View being deinitialized")
38
- if ExpoSettingsView.current === self {
39
- ExpoSettingsView.current = nil
40
- }
22
+ public override func layoutSubviews() {
23
+ super.layoutSubviews()
24
+ hkView.frame = bounds
25
+ }
26
+
27
+ public func attachStream(_ stream: RTMPStream) {
28
+ hkView.attachStream(stream)
29
+ }
30
+
31
+ deinit {
32
+ if ExpoSettingsView.current === self {
33
+ ExpoSettingsView.current = nil
41
34
  }
35
+ }
42
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blix-expo-settings",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "LiveStream",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",