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.
- package/ios/ExpoSettingsModule.swift +81 -99
- package/ios/ExpoSettingsView.swift +26 -32
- package/package.json +1 -1
|
@@ -11,10 +11,7 @@ public class ExpoSettingsModule: Module {
|
|
|
11
11
|
public func definition() -> ModuleDefinition {
|
|
12
12
|
Name("ExpoSettings")
|
|
13
13
|
|
|
14
|
-
|
|
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
|
-
|
|
27
|
-
self.setStatus("previewInitializing")
|
|
23
|
+
self.setStatus("previewInitializing")
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
self.configureAudioSession()
|
|
25
|
+
self.configureAudioSession()
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self.rtmpConnection = connection
|
|
27
|
+
let connection = RTMPConnection()
|
|
28
|
+
self.rtmpConnection = connection
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
46
|
-
|
|
33
|
+
self.configureStream(stream)
|
|
34
|
+
self.attachAudioIfAvailable(stream)
|
|
35
|
+
self.attachFrontCamera(stream)
|
|
36
|
+
self.attachPreviewIfAvailable(stream)
|
|
47
37
|
|
|
48
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
self.setStatus("connecting")
|
|
42
|
+
self.setStatus("connecting")
|
|
43
|
+
print("[ExpoSettings] Publishing to: \(url) key: \(streamKey)")
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
50
|
+
let stream = RTMPStream(connection: connection)
|
|
51
|
+
self.rtmpStream = stream
|
|
69
52
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
self.configureStream(stream)
|
|
54
|
+
self.attachAudioIfAvailable(stream)
|
|
55
|
+
self.attachFrontCamera(stream)
|
|
56
|
+
self.attachPreviewIfAvailable(stream)
|
|
57
|
+
}
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.setStatus("connected")
|
|
59
|
+
self.rtmpConnection?.connect(url)
|
|
60
|
+
self.setStatus("connected")
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self.rtmpStream?.publish(streamKey)
|
|
83
|
-
print("[ExpoSettings] Stream published successfully")
|
|
62
|
+
self.setStatus("publishing")
|
|
63
|
+
self.rtmpStream?.publish(streamKey)
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
}
|
|
65
|
+
self.setStatus("started")
|
|
87
66
|
}
|
|
88
67
|
|
|
89
68
|
Function("stopStream") { () -> Void in
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
71
|
+
if let stream = self.rtmpStream {
|
|
72
|
+
stream.close()
|
|
73
|
+
stream.attachCamera(nil)
|
|
74
|
+
stream.attachAudio(nil)
|
|
75
|
+
}
|
|
101
76
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
77
|
+
if let connection = self.rtmpConnection {
|
|
78
|
+
connection.close()
|
|
79
|
+
}
|
|
106
80
|
|
|
107
|
-
|
|
108
|
-
|
|
81
|
+
self.rtmpStream = nil
|
|
82
|
+
self.rtmpConnection = nil
|
|
109
83
|
|
|
110
|
-
|
|
111
|
-
self.setStatus("stopped")
|
|
112
|
-
}
|
|
84
|
+
self.setStatus("stopped")
|
|
113
85
|
}
|
|
114
86
|
}
|
|
115
87
|
|
|
116
|
-
// MARK: -
|
|
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(
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
//
|
|
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 (
|
|
116
|
+
// 2) Orientação no nível do stream (governa pipeline)
|
|
144
117
|
stream.videoOrientation = .portrait
|
|
145
118
|
|
|
146
|
-
//
|
|
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 (
|
|
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
|
|
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
|
|
174
|
-
print("[ExpoSettings]
|
|
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
|
|
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)
|
|
187
|
-
guard let camera = AVCaptureDevice.default(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|