diff --git a/packages/noise-cancellation-react-native/package.json b/packages/noise-cancellation-react-native/package.json
index aaf8706bc1..6a32382765 100644
--- a/packages/noise-cancellation-react-native/package.json
+++ b/packages/noise-cancellation-react-native/package.json
@@ -50,7 +50,7 @@
"@stream-io/react-native-webrtc": "125.4.4",
"react": "19.1.0",
"react-native": "^0.81.4",
- "react-native-builder-bob": "^0.37.0",
+ "react-native-builder-bob": "^0.40.13",
"rimraf": "^6.0.1",
"typescript": "^5.9.3"
},
diff --git a/packages/react-native-broadcast/.watchmanconfig b/packages/react-native-broadcast/.watchmanconfig
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/packages/react-native-broadcast/.watchmanconfig
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/packages/react-native-broadcast/README.md b/packages/react-native-broadcast/README.md
new file mode 100644
index 0000000000..784f012f08
--- /dev/null
+++ b/packages/react-native-broadcast/README.md
@@ -0,0 +1,70 @@
+# react-native-broadcast
+
+a
+
+## Installation
+
+```sh
+npm install react-native-broadcast
+```
+
+## Usage
+
+### BroadcastVideoView Component
+
+Display the local video preview from the broadcast mixer:
+
+```tsx
+import { BroadcastVideoView, multiply } from 'react-native-broadcast';
+import { View, StyleSheet, Button } from 'react-native';
+
+function App() {
+ const startBroadcast = async () => {
+ // This will initialize the mixer and start the RTMP broadcast
+ await multiply(3, 7);
+ };
+
+ return (
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ video: {
+ flex: 1,
+ backgroundColor: 'black',
+ },
+});
+```
+
+### API
+
+#### `BroadcastVideoView`
+
+A native view component that displays the local video from the broadcast mixer.
+
+**Props:**
+
+- `style?: ViewStyle` - Standard React Native style prop
+
+The view automatically connects to the mixer when it becomes available after calling `multiply()` to start the broadcast.
+
+## Contributing
+
+- [Development workflow](CONTRIBUTING.md#development-workflow)
+- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
+- [Code of conduct](CODE_OF_CONDUCT.md)
+
+## License
+
+MIT
+
+---
+
+Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
diff --git a/packages/react-native-broadcast/StreamReactNativeBroadcast.podspec b/packages/react-native-broadcast/StreamReactNativeBroadcast.podspec
new file mode 100644
index 0000000000..04aed4136d
--- /dev/null
+++ b/packages/react-native-broadcast/StreamReactNativeBroadcast.podspec
@@ -0,0 +1,26 @@
+require "json"
+
+package = JSON.parse(File.read(File.join(__dir__, "package.json")))
+
+Pod::Spec.new do |s|
+ s.name = "StreamReactNativeBroadcast"
+ s.version = package["version"]
+ s.summary = package["description"]
+ s.homepage = package["homepage"]
+ s.license = package["license"]
+ s.authors = package["author"]
+
+ s.platforms = { :ios => min_ios_version_supported }
+ s.source = { :git => "https://github.com/GetStream/stream-video-js.git", :tag => "#{s.version}" }
+
+ s.source_files = "ios/**/*.{h,m,mm,cpp,swift}"
+ s.private_header_files = "ios/**/*.h"
+
+ install_modules_dependencies(s)
+
+ spm_dependency(s,
+ url: 'https://github.com/HaishinKit/HaishinKit.swift',
+ requirement: { kind: 'upToNextMajorVersion', minimumVersion: '2.2.0' },
+ products: ['HaishinKit', 'RTMPHaishinKit', 'SRTHaishinKit', 'MoQTHaishinKit', 'RTCHaishinKit']
+ )
+end
diff --git a/packages/react-native-broadcast/android/build.gradle b/packages/react-native-broadcast/android/build.gradle
new file mode 100644
index 0000000000..0884bc0252
--- /dev/null
+++ b/packages/react-native-broadcast/android/build.gradle
@@ -0,0 +1,77 @@
+buildscript {
+ ext.getExtOrDefault = {name ->
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['Broadcast_' + name]
+ }
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath "com.android.tools.build:gradle:8.7.2"
+ // noinspection DifferentKotlinGradleVersion
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
+ }
+}
+
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+apply plugin: "com.facebook.react"
+
+def getExtOrIntegerDefault(name) {
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Broadcast_" + name]).toInteger()
+}
+
+android {
+ namespace "com.broadcast"
+
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
+
+ defaultConfig {
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
+ }
+
+ buildFeatures {
+ buildConfig true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ lintOptions {
+ disable "GradleCompatible"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main {
+ java.srcDirs += [
+ "generated/java",
+ "generated/jni"
+ ]
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+ google()
+}
+
+def kotlin_version = getExtOrDefault("kotlinVersion")
+
+dependencies {
+ implementation "com.facebook.react:react-android"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
diff --git a/packages/react-native-broadcast/android/gradle.properties b/packages/react-native-broadcast/android/gradle.properties
new file mode 100644
index 0000000000..0e3d333563
--- /dev/null
+++ b/packages/react-native-broadcast/android/gradle.properties
@@ -0,0 +1,5 @@
+Broadcast_kotlinVersion=2.0.21
+Broadcast_minSdkVersion=24
+Broadcast_targetSdkVersion=34
+Broadcast_compileSdkVersion=35
+Broadcast_ndkVersion=27.1.12297006
diff --git a/packages/react-native-broadcast/android/src/main/AndroidManifest.xml b/packages/react-native-broadcast/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..bdae66c8f5
--- /dev/null
+++ b/packages/react-native-broadcast/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastModule.kt b/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastModule.kt
new file mode 100644
index 0000000000..ee3965fd4e
--- /dev/null
+++ b/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastModule.kt
@@ -0,0 +1,27 @@
+package com.broadcast
+
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.module.annotations.ReactModule
+
+@ReactModule(name = BroadcastModule.NAME)
+class BroadcastModule(reactContext: ReactApplicationContext) :
+ NativeBroadcastSpec(reactContext) {
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ // Promise-based method to match TurboModule spec
+ override fun multiply(a: Double, b: Double, promise: Promise) {
+ try {
+ promise.resolve(a * b)
+ } catch (e: Exception) {
+ promise.reject("multiply_error", e)
+ }
+ }
+
+ companion object {
+ const val NAME = "Broadcast"
+ }
+}
diff --git a/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastPackage.kt b/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastPackage.kt
new file mode 100644
index 0000000000..c8ef319ce4
--- /dev/null
+++ b/packages/react-native-broadcast/android/src/main/java/com/broadcast/BroadcastPackage.kt
@@ -0,0 +1,33 @@
+package com.broadcast
+
+import com.facebook.react.BaseReactPackage
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.module.model.ReactModuleInfo
+import com.facebook.react.module.model.ReactModuleInfoProvider
+import java.util.HashMap
+
+class BroadcastPackage : BaseReactPackage() {
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
+ return if (name == BroadcastModule.NAME) {
+ BroadcastModule(reactContext)
+ } else {
+ null
+ }
+ }
+
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
+ return ReactModuleInfoProvider {
+ val moduleInfos: MutableMap = HashMap()
+ moduleInfos[BroadcastModule.NAME] = ReactModuleInfo(
+ BroadcastModule.NAME,
+ BroadcastModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ false, // isCxxModule
+ true // isTurboModule
+ )
+ moduleInfos
+ }
+ }
+}
diff --git a/packages/react-native-broadcast/babel.config.js b/packages/react-native-broadcast/babel.config.js
new file mode 100644
index 0000000000..0c05fd6963
--- /dev/null
+++ b/packages/react-native-broadcast/babel.config.js
@@ -0,0 +1,12 @@
+module.exports = {
+ overrides: [
+ {
+ exclude: /\/node_modules\//,
+ presets: ['module:react-native-builder-bob/babel-preset'],
+ },
+ {
+ include: /\/node_modules\//,
+ presets: ['module:@react-native/babel-preset'],
+ },
+ ],
+};
diff --git a/packages/react-native-broadcast/ios/AudioSourceService.swift b/packages/react-native-broadcast/ios/AudioSourceService.swift
new file mode 100644
index 0000000000..be60a71dce
--- /dev/null
+++ b/packages/react-native-broadcast/ios/AudioSourceService.swift
@@ -0,0 +1,128 @@
+@preconcurrency import AVFoundation
+import Combine
+
+struct AudioSource: Sendable, Hashable, Equatable, CustomStringConvertible {
+ static let empty = AudioSource(portName: "", dataSourceName: "", isSupportedStereo: false)
+
+ let portName: String
+ let dataSourceName: String
+ let isSupportedStereo: Bool
+
+ var description: String {
+ if isSupportedStereo {
+ return "\(portName)(\(dataSourceName))(Stereo)"
+ }
+ return "\(portName)(\(dataSourceName))(Mono)"
+ }
+}
+
+actor AudioSourceService {
+ enum Error: Swift.Error {
+ case missingDataSource(_ source: AudioSource)
+ }
+
+ private(set) var sources: [AudioSource] = [] {
+ didSet {
+ guard sources != oldValue else {
+ return
+ }
+ continuation?.yield(sources)
+ }
+ }
+ private let session = AVAudioSession.sharedInstance()
+ private var continuation: AsyncStream<[AudioSource]>.Continuation? {
+ didSet {
+ oldValue?.finish()
+ }
+ }
+
+ init() {
+ Task { await _init() }
+ }
+
+ private func _init() async {
+ sources = makeAudioSources()
+ Task {
+ for await _ in NotificationCenter.default.notifications(named: AVAudioSession.routeChangeNotification)
+ .compactMap({ $0.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt })
+ .compactMap({ AVAudioSession.RouteChangeReason(rawValue: $0) }) {
+ sources = makeAudioSources()
+ }
+ }
+ }
+
+ func setUp() {
+ let session = AVAudioSession.sharedInstance()
+ do {
+ // If you set the "mode" parameter, stereo capture is not possible, so it is left unspecified.
+ try session.setCategory(.playAndRecord, mode: .videoRecording, options: [.defaultToSpeaker, .allowBluetoothHFP])
+ // It looks like this setting is required on iOS 18.5.
+ try session.setPreferredInputNumberOfChannels(2)
+ try session.setActive(true)
+ } catch {
+ print(error)
+ }
+ }
+
+ func sourcesUpdates() -> AsyncStream<[AudioSource]> {
+ AsyncStream { continuation in
+ self.continuation = continuation
+ continuation.yield(sources)
+ }
+ }
+
+ func selectAudioSource(_ audioSource: AudioSource) throws {
+ setPreferredInputBuiltInMic(true)
+ guard let preferredInput = AVAudioSession.sharedInstance().preferredInput,
+ let dataSources = preferredInput.dataSources,
+ let newDataSource = dataSources.first(where: { $0.dataSourceName == audioSource.dataSourceName }),
+ let supportedPolarPatterns = newDataSource.supportedPolarPatterns else {
+ throw Error.missingDataSource(audioSource)
+ }
+ do {
+ let isStereoSupported = supportedPolarPatterns.contains(.stereo)
+ if isStereoSupported {
+ try newDataSource.setPreferredPolarPattern(.stereo)
+ }
+ try preferredInput.setPreferredDataSource(newDataSource)
+ } catch {
+ print(error)
+ }
+ }
+
+ private func makeAudioSources() -> [AudioSource] {
+ if session.inputDataSources?.isEmpty == true {
+ setPreferredInputBuiltInMic(false)
+ } else {
+ setPreferredInputBuiltInMic(true)
+ }
+ guard let preferredInput = session.preferredInput else {
+ return []
+ }
+ var sources: [AudioSource] = []
+ for dataSource in session.preferredInput?.dataSources ?? [] {
+ sources.append(.init(
+ portName: preferredInput.portName,
+ dataSourceName: dataSource.dataSourceName,
+ isSupportedStereo: dataSource.supportedPolarPatterns?.contains(.stereo) ?? false
+ ))
+ }
+ return sources
+ }
+
+ private func setPreferredInputBuiltInMic(_ isEnabled: Bool) {
+ do {
+ if isEnabled {
+ guard let availableInputs = session.availableInputs,
+ let builtInMicInput = availableInputs.first(where: { $0.portType == .builtInMic }) else {
+ return
+ }
+ try session.setPreferredInput(builtInMicInput)
+ } else {
+ try session.setPreferredInput(nil)
+ }
+ } catch {
+ print(error)
+ }
+ }
+}
diff --git a/packages/react-native-broadcast/ios/Broadcast.h b/packages/react-native-broadcast/ios/Broadcast.h
new file mode 100644
index 0000000000..ab7295b2ab
--- /dev/null
+++ b/packages/react-native-broadcast/ios/Broadcast.h
@@ -0,0 +1,5 @@
+#import
+
+@interface Broadcast : NSObject
+
+@end
diff --git a/packages/react-native-broadcast/ios/Broadcast.mm b/packages/react-native-broadcast/ios/Broadcast.mm
new file mode 100644
index 0000000000..959caecc0f
--- /dev/null
+++ b/packages/react-native-broadcast/ios/Broadcast.mm
@@ -0,0 +1,74 @@
+#import "Broadcast.h"
+#import
+#import
+
+@implementation Broadcast
+
+
+- (NSString *)createInstance:(JS::NativeBroadcast::Preset &)preset
+{
+ BroadcastPreset *p = [BroadcastPreset alloc];
+ p = [p initWithWidth:(NSInteger)preset.width()
+ height:(NSInteger)preset.height()
+ frameRate:(double)preset.frameRate()
+ videoBitrate:(NSInteger)preset.videoBitrate()
+ audioBitrate:(NSInteger)preset.audioBitrate()];
+
+ return [BroadcastManager createInstanceWithPreset:p];
+}
+
+- (void)destroyInstance:(NSString *)instanceId
+{
+ [BroadcastManager destroyInstanceWithInstanceId:instanceId];
+}
+
+- (void)start:(NSString *)instanceId endpoint:(NSString *)endpoint streamName:(NSString *)streamName resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
+{
+ [BroadcastManager startWithInstanceId:instanceId endpoint:endpoint streamName:streamName completion:^(NSError * _Nullable error) {
+ if (error != nil) {
+ reject(@"start_error", error.localizedDescription, error);
+ } else {
+ resolve(nil);
+ }
+ }];
+}
+
+- (void)stop:(NSString *)instanceId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
+{
+ [BroadcastManager stopWithInstanceId:instanceId completion:^(NSError * _Nullable error) {
+ if (error != nil) {
+ reject(@"stop_error", error.localizedDescription, error);
+ } else {
+ resolve(nil);
+ }
+ }];
+}
+
+- (void)setCameraDirection:(NSString *)instanceId direction:(NSString *)direction
+{
+ [BroadcastManager setCameraDirectionWithInstanceId:instanceId direction:direction];
+}
+
+- (void)setCameraEnabled:(NSString *)instanceId enabled:(BOOL)enabled
+{
+ [BroadcastManager setCameraEnabledWithInstanceId:instanceId enabled:enabled];
+}
+
+- (void)setMicrophoneEnabled:(NSString *)instanceId enabled:(BOOL)enabled
+{
+ [BroadcastManager setMicrophoneEnabledWithInstanceId:instanceId enabled:enabled];
+}
+
+- (std::shared_ptr)getTurboModule:
+ (const facebook::react::ObjCTurboModule::InitParams &)params
+{
+ return std::make_shared(params);
+}
+
++ (NSString *)moduleName
+{
+ return @"Broadcast";
+}
+
+@end
+
diff --git a/packages/react-native-broadcast/ios/BroadcastEventEmitter.m b/packages/react-native-broadcast/ios/BroadcastEventEmitter.m
new file mode 100644
index 0000000000..628669bc7e
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastEventEmitter.m
@@ -0,0 +1,5 @@
+#import
+#import
+
+@interface RCT_EXTERN_MODULE(BroadcastEventEmitter, RCTEventEmitter)
+@end
diff --git a/packages/react-native-broadcast/ios/BroadcastEventEmitter.swift b/packages/react-native-broadcast/ios/BroadcastEventEmitter.swift
new file mode 100644
index 0000000000..37f6857070
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastEventEmitter.swift
@@ -0,0 +1,29 @@
+import Foundation
+import React
+
+@objc(BroadcastEventEmitter)
+class BroadcastEventEmitter: RCTEventEmitter {
+ static var shared: BroadcastEventEmitter?
+
+ override init() {
+ super.init()
+ BroadcastEventEmitter.shared = self
+ }
+
+ override static func requiresMainQueueSetup() -> Bool {
+ return true
+ }
+
+ override func supportedEvents() -> [String]! {
+ return [
+ "broadcast.started",
+ "broadcast.mediaStateUpdated"
+ ]
+ }
+
+ @objc public static func emit(_ name: String, body: Any) {
+ DispatchQueue.main.async {
+ BroadcastEventEmitter.shared?.sendEvent(withName: name, body: body)
+ }
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastManager.swift b/packages/react-native-broadcast/ios/BroadcastManager.swift
new file mode 100644
index 0000000000..1e1be764d4
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastManager.swift
@@ -0,0 +1,320 @@
+import AVFoundation
+import Foundation
+import HaishinKit
+import RTMPHaishinKit
+
+@objc
+public class BroadcastManager: NSObject {
+
+ // MARK: - Multi-instance APIs
+
+ @objc(createInstanceWithPreset:)
+ public static func createInstance(preset: BroadcastPreset) -> String {
+ let id = UUID().uuidString
+ let state = BroadcastRegistry.shared.state(for: id)
+ state.withPreset(preset: preset)
+ return id
+ }
+
+ @objc(destroyInstanceWithInstanceId:)
+ public static func destroyInstance(instanceId: String) {
+ BroadcastRegistry.shared.remove(instanceId)
+ }
+
+ @objc(startWithInstanceId:endpoint:streamName:completion:)
+ public static func start(
+ instanceId: String,
+ endpoint: String,
+ streamName: String,
+ completion: @escaping (NSError?) -> Void
+ ) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+
+ print("[Broadcast][\(instanceId)] start called")
+ if state.isRunning {
+ print("[Broadcast][\(instanceId)] already running, ignoring start")
+ completion(nil)
+ return
+ }
+
+ // Compose URL
+ let fullURLString: String
+ if streamName.isEmpty {
+ fullURLString = endpoint
+ } else if endpoint.hasSuffix("/") {
+ fullURLString = endpoint + streamName
+ } else {
+ fullURLString = endpoint + "/" + streamName
+ }
+ guard let url = URL(string: fullURLString), let scheme = url.scheme,
+ scheme == "rtmp" || scheme == "rtmps"
+ else {
+ completion(
+ NSError(
+ domain: "Broadcast",
+ code: -1,
+ userInfo: [
+ NSLocalizedDescriptionKey:
+ "Invalid RTMP endpoint or stream name"
+ ]
+ )
+ )
+ return
+ }
+
+ state.lastURL = url
+ state.shouldReconnect = true
+ state.reconnectAttempts = 0
+
+ Task {
+ do {
+ let audioSourceService = AudioSourceService()
+ await audioSourceService.setUp()
+
+ let mixer = MediaMixer(captureSessionMode: .single)
+ try await mixer.setFrameRate(state.preset.frameRate)
+
+ await mixer.configuration { session in
+ session.automaticallyConfiguresApplicationAudioSession =
+ false
+ // Choose a capture preset based on desired dimensions (best-effort)
+ if max(state.preset.width, state.preset.height) >= 1920 {
+ session.sessionPreset = .hd1920x1080
+ } else {
+ session.sessionPreset = .hd1280x720
+ }
+ }
+ await mixer.setMonitoringEnabled(
+ DeviceUtil.isHeadphoneConnected()
+ )
+
+ var videoMixerSettings = await mixer.videoMixerSettings
+ videoMixerSettings.mode = .offscreen
+ await mixer.setVideoMixerSettings(videoMixerSettings)
+
+ // Attach devices based on current state
+ if state.cameraEnabled {
+ let position = state.cameraPosition
+ let camera = AVCaptureDevice.default(
+ .builtInWideAngleCamera,
+ for: .video,
+ position: position
+ )
+ try? await mixer.attachVideo(camera, track: 0) { unit in
+ unit.isVideoMirrored = position == .front
+ }
+ }
+
+ if state.micEnabled {
+ let mic = AVCaptureDevice.default(for: .audio)
+ try? await mixer.attachAudio(mic)
+ }
+
+ await mixer.startCapturing()
+ await mixer.startRunning()
+
+ let factory = RTMPSessionFactory()
+ let session = factory.make(
+ url,
+ mode: .publish,
+ configuration: nil
+ )
+
+ await mixer.addOutput(session.stream)
+
+ var audioSettings = await session.stream.audioSettings
+ audioSettings.format = .aac
+ audioSettings.bitRate = state.preset.audioBitrate
+ try await session.stream.setAudioSettings(audioSettings)
+
+ var videoSettings = await session.stream.videoSettings
+ videoSettings.isLowLatencyRateControlEnabled = false
+ videoSettings.bitRateMode = .average
+ videoSettings.bitRate = state.preset.videoBitrate
+ videoSettings.videoSize = CGSize(
+ width: state.preset.width,
+ height: state.preset.height
+ )
+ try await session.stream.setVideoSettings(videoSettings)
+
+ try await session.connect {
+ print(
+ "[Broadcast][\(instanceId)] RTMP connection closed unexpectedly"
+ )
+ BroadcastManager.scheduleReconnect(instanceId: instanceId)
+ }
+
+ print("[Broadcast][\(instanceId)] RTMP connected")
+
+ // Save state
+ state.mixer = mixer
+ state.audioSourceService = audioSourceService
+ state.session = session
+ state.isRunning = true
+
+ completion(nil)
+ } catch {
+ print("[Broadcast][\(instanceId)] start error: \(error)")
+ await BroadcastManager.cleanupInstance(instanceId: instanceId)
+ completion(error as NSError)
+ }
+ }
+ }
+
+ @objc(stopWithInstanceId:completion:)
+ public static func stop(
+ instanceId: String,
+ completion: @escaping (NSError?) -> Void
+ ) {
+ print("[Broadcast][\(instanceId)] stop called")
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ guard state.isRunning else {
+ completion(nil)
+ return
+ }
+ Task {
+ // Disable any auto-reconnects and cancel pending tasks
+ state.shouldReconnect = false
+ state.reconnectTask?.cancel()
+ state.reconnectTask = nil
+
+ if let session = state.session {
+ try? await session.close()
+ }
+ if let mixer = state.mixer {
+ await mixer.stopRunning()
+ await mixer.stopCapturing()
+ try? await mixer.attachAudio(nil)
+ try? await mixer.attachVideo(nil, track: 0)
+ if let session = state.session {
+ await mixer.removeOutput(session.stream)
+ }
+ }
+ // Reactive emission via didSet
+ state.isRunning = false
+ await BroadcastManager.cleanupInstance(instanceId: instanceId)
+ completion(nil)
+ }
+ }
+
+ @objc(setCameraDirectionWithInstanceId:direction:)
+ public static func setCameraDirection(
+ instanceId: String,
+ direction: String
+ ) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ let position: AVCaptureDevice.Position =
+ (direction.lowercased() == "back") ? .back : .front
+ state.cameraPosition = position
+ guard let mixer = state.mixer else { return }
+ Task {
+ let camera = AVCaptureDevice.default(
+ .builtInWideAngleCamera,
+ for: .video,
+ position: position
+ )
+ try? await mixer.attachVideo(camera, track: 0) { unit in
+ unit.isVideoMirrored = position == .front
+ }
+ }
+ }
+
+ @objc(setCameraEnabledWithInstanceId:enabled:)
+ public static func setCameraEnabled(
+ instanceId: String,
+ enabled: Bool
+ ) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ state.cameraEnabled = enabled
+ guard let mixer = state.mixer else { return }
+ Task {
+ if enabled {
+ let position = state.cameraPosition
+ let camera = AVCaptureDevice.default(
+ .builtInWideAngleCamera,
+ for: .video,
+ position: position
+ )
+ try? await mixer.attachVideo(camera, track: 0) { unit in
+ unit.isVideoMirrored = position == .front
+ }
+ } else {
+ try? await mixer.attachVideo(nil, track: 0)
+ }
+ }
+ }
+
+ @objc(setMicrophoneEnabledWithInstanceId:enabled:)
+ public static func setMicrophoneEnabled(
+ instanceId: String,
+ enabled: Bool
+ ) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ state.micEnabled = enabled
+ guard let mixer = state.mixer else { return }
+ Task {
+ if enabled {
+ let mic = AVCaptureDevice.default(for: .audio)
+ try? await mixer.attachAudio(mic)
+ } else {
+ try? await mixer.attachAudio(nil)
+ }
+ }
+ }
+
+ // Schedule a reconnect with exponential backoff using the current session.
+ private static func scheduleReconnect(instanceId: String) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ guard state.shouldReconnect, state.isRunning else { return }
+
+ let nextAttempt = state.reconnectAttempts + 1
+ if nextAttempt > state.maxReconnectAttempts {
+ print(
+ "[Broadcast][\(instanceId)] Reconnect: max attempts reached, giving up"
+ )
+ Task { @MainActor in
+ state.isRunning = false
+ BroadcastManager.cleanupInstance(instanceId: instanceId)
+ }
+ return
+ }
+ state.reconnectAttempts = nextAttempt
+
+ let delay = min(2.5, pow(2.0, Double(nextAttempt - 1)))
+ print(
+ "[Broadcast][\(instanceId)] Reconnect scheduled in \(delay)s (attempt \(nextAttempt)/\(state.maxReconnectAttempts))"
+ )
+
+ state.reconnectTask?.cancel()
+ state.reconnectTask = Task { [instanceId] in
+ do {
+ try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
+
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ guard state.shouldReconnect, state.isRunning,let session = state.session else { return }
+
+ try await session.close()
+ try await session.connect {
+ print(
+ "[Broadcast][\(instanceId)] RTMP connection closed unexpectedly"
+ )
+ BroadcastManager.scheduleReconnect(instanceId: instanceId)
+ }
+
+ print("[Broadcast][\(instanceId)] RTMP reconnected")
+ state.reconnectAttempts = 0
+ } catch {
+ print(
+ "[Broadcast][\(instanceId)] Reconnect task error: \(error)"
+ )
+ }
+ }
+ }
+
+ // Per-instance cleanup
+ @MainActor
+ private static func cleanupInstance(instanceId: String) {
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ state.reset()
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastPreset.swift b/packages/react-native-broadcast/ios/BroadcastPreset.swift
new file mode 100644
index 0000000000..81c90ad477
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastPreset.swift
@@ -0,0 +1,31 @@
+//
+// BroadcastPreset.swift
+//
+// Created by Oliver Lazoroski on 23.10.25.
+//
+
+@objcMembers
+public class BroadcastPreset: NSObject {
+ var width: Int = 720
+ var height: Int = 1280
+ var frameRate: Double = 30
+ var videoBitrate: Int = 3_000_000
+ var audioBitrate: Int = 128_000
+
+ override init() { super.init() }
+
+ @objc public init(
+ width: Int,
+ height: Int,
+ frameRate: Double,
+ videoBitrate: Int,
+ audioBitrate: Int
+ ) {
+ self.width = width
+ self.height = height
+ self.frameRate = frameRate
+ self.videoBitrate = videoBitrate
+ self.audioBitrate = audioBitrate
+ super.init()
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastRegistry.swift b/packages/react-native-broadcast/ios/BroadcastRegistry.swift
new file mode 100644
index 0000000000..59ee9fa2a5
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastRegistry.swift
@@ -0,0 +1,26 @@
+@objc
+public class BroadcastRegistry: NSObject {
+ @objc public static let shared = BroadcastRegistry()
+
+ private var states: [String: BroadcastState] = [:]
+
+ private override init() { super.init() }
+
+ @objc
+ public func state(for instanceId: String) -> BroadcastState {
+ if let existing = states[instanceId] { return existing }
+ let state = BroadcastState()
+ state.instanceId = instanceId
+ states[instanceId] = state
+ return state
+ }
+
+ @objc
+ public func remove(_ instanceId: String) {
+ Task { @MainActor in
+ if let state = states.removeValue(forKey: instanceId) {
+ state.reset()
+ }
+ }
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastState.swift b/packages/react-native-broadcast/ios/BroadcastState.swift
new file mode 100644
index 0000000000..b3446665f0
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastState.swift
@@ -0,0 +1,103 @@
+//
+// BroadcastState.swift
+//
+// Created by Oliver Lazoroski on 23.10.25.
+//
+import HaishinKit
+import AVFoundation
+
+@objc
+public class BroadcastState: NSObject {
+ // Identifier for correlating events with JS instances
+ var instanceId: String = ""
+
+ var suppressEvents: Bool = false
+
+ var preset: BroadcastPreset = BroadcastPreset()
+
+ var mixer: MediaMixer?
+ var audioSourceService: AudioSourceService?
+ var session: Session?
+
+ // Reconnection state
+ var lastURL: URL?
+ var shouldReconnect: Bool = false
+ var reconnectAttempts: Int = 0
+ var maxReconnectAttempts: Int = 5
+ var reconnectTask: Task?
+
+ var isRunning: Bool = false {
+ didSet {
+ guard !suppressEvents else { return }
+ BroadcastEventEmitter.emit(
+ "broadcast.started",
+ body: [
+ "instanceId": instanceId,
+ "running": isRunning,
+ ]
+ )
+ }
+ }
+
+ var cameraPosition: AVCaptureDevice.Position = .front {
+ didSet {
+ emitMediaStateUpdated()
+ }
+ }
+ var cameraEnabled: Bool = true {
+ didSet {
+ emitMediaStateUpdated()
+ }
+ }
+ var micEnabled: Bool = true {
+ didSet {
+ emitMediaStateUpdated()
+ }
+ }
+
+ override public init() {
+ super.init()
+ }
+
+ func withPreset(preset: BroadcastPreset) {
+ self.preset = preset
+ }
+
+ private func emitMediaStateUpdated() {
+ guard !suppressEvents else { return }
+ let direction = cameraPosition == .back ? "back" : "front"
+ BroadcastEventEmitter.emit(
+ "broadcast.mediaStateUpdated",
+ body: [
+ "instanceId": instanceId,
+ "cameraEnabled": cameraEnabled,
+ "microphoneEnabled": micEnabled,
+ "cameraDirection": direction,
+ ]
+ )
+ }
+
+ @MainActor
+ func reset() {
+ suppressEvents = true
+
+ // cancel any pending reconnect
+ reconnectTask?.cancel()
+ reconnectTask = nil
+ shouldReconnect = false
+ reconnectAttempts = 0
+ lastURL = nil
+
+ session = nil
+ mixer = nil
+ audioSourceService = nil
+ isRunning = false
+ cameraPosition = .front
+ cameraEnabled = true
+ micEnabled = true
+
+ // send an update
+ suppressEvents = false
+ emitMediaStateUpdated()
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastVideoView.swift b/packages/react-native-broadcast/ios/BroadcastVideoView.swift
new file mode 100644
index 0000000000..06e94a1725
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastVideoView.swift
@@ -0,0 +1,82 @@
+import AVFoundation
+import Foundation
+import HaishinKit
+import UIKit
+
+@objc(BroadcastVideoView)
+public class BroadcastVideoView: UIView {
+
+ private var hkView: MTHKView?
+ private var isAttached = false
+
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ setupView()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ setupView()
+ }
+
+ private func setupView() {
+ backgroundColor = .black
+
+ // Create HKView for displaying HaishinKit video
+ let hkView = MTHKView(frame: bounds)
+ hkView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+ hkView.videoGravity = .resizeAspectFill
+ addSubview(hkView)
+ self.hkView = hkView
+
+ print("[BroadcastVideoView] View initialized")
+
+ // Try to attach mixer if available
+ tryAttachMixer()
+ }
+
+ @objc public var instanceId: String? {
+ didSet {
+ isAttached = false
+ tryAttachMixer()
+ }
+ }
+
+ private func tryAttachMixer() {
+ guard let instanceId = instanceId else {
+ // wait for instance id to be set
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
+ [weak self] in
+ self?.tryAttachMixer()
+ }
+ return
+ }
+ let state = BroadcastRegistry.shared.state(for: instanceId)
+ guard !isAttached, let mixer = state.mixer else {
+ // Schedule retry if mixer not ready yet or already attached
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ [weak self] in
+ self?.tryAttachMixer()
+ }
+ return
+ }
+
+ Task { @MainActor in
+ guard let hkView = self.hkView else { return }
+ await mixer.addOutput(hkView)
+ self.isAttached = true
+ print(
+ "[BroadcastVideoView] Mixer attached to view for instanceId=\(instanceId)"
+ )
+ }
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+ hkView?.frame = bounds
+ }
+
+ deinit {
+ print("[BroadcastVideoView] View deinitialized")
+ }
+}
diff --git a/packages/react-native-broadcast/ios/BroadcastVideoViewManager.m b/packages/react-native-broadcast/ios/BroadcastVideoViewManager.m
new file mode 100644
index 0000000000..4fb07c0f67
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastVideoViewManager.m
@@ -0,0 +1,8 @@
+#import
+
+@interface RCT_EXTERN_MODULE(BroadcastVideoViewManager, RCTViewManager)
+
+RCT_EXPORT_VIEW_PROPERTY(instanceId, NSString)
+
+@end
+
diff --git a/packages/react-native-broadcast/ios/BroadcastVideoViewManager.swift b/packages/react-native-broadcast/ios/BroadcastVideoViewManager.swift
new file mode 100644
index 0000000000..4ef567c03b
--- /dev/null
+++ b/packages/react-native-broadcast/ios/BroadcastVideoViewManager.swift
@@ -0,0 +1,17 @@
+import Foundation
+import React
+
+@objc(BroadcastVideoViewManager)
+class BroadcastVideoViewManager: RCTViewManager {
+
+ override func view() -> UIView! {
+ let view = BroadcastVideoView()
+ print("[BroadcastVideoViewManager] View created")
+ return view
+ }
+
+ override static func requiresMainQueueSetup() -> Bool {
+ return true
+ }
+}
+
diff --git a/packages/react-native-broadcast/package.json b/packages/react-native-broadcast/package.json
new file mode 100644
index 0000000000..655e78ce08
--- /dev/null
+++ b/packages/react-native-broadcast/package.json
@@ -0,0 +1,95 @@
+{
+ "name": "@stream-io/video-react-native-broadcast",
+ "version": "0.1.0",
+ "description": "a",
+ "main": "./dist/module/index.js",
+ "types": "./dist/typescript/src/index.d.ts",
+ "exports": {
+ ".": {
+ "source": "./src/index.tsx",
+ "types": "./dist/typescript/src/index.d.ts",
+ "default": "./dist/module/index.js"
+ },
+ "./package.json": "./package.json"
+ },
+ "files": [
+ "src",
+ "lib",
+ "android",
+ "ios",
+ "cpp",
+ "*.podspec",
+ "react-native.config.js",
+ "!ios/build",
+ "!android/build",
+ "!android/gradle",
+ "!android/gradlew",
+ "!android/gradlew.bat",
+ "!android/local.properties",
+ "!**/__tests__",
+ "!**/__fixtures__",
+ "!**/__mocks__",
+ "!**/.*"
+ ],
+ "scripts": {
+ "prepare": "bob build",
+ "build": "bob build"
+ },
+ "keywords": [
+ "react-native",
+ "ios",
+ "android"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/GetStream/stream-video-js.git"
+ },
+ "author": "GetStream (https://github.com/GetStream)",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/GetStream/stream-video-js/issues"
+ },
+ "homepage": "https://github.com/GetStream/stream-video-js",
+ "dependencies": {
+ "rxjs": "^7.8.2"
+ },
+ "devDependencies": {
+ "@react-native-community/cli": "20.0.0",
+ "@react-native/babel-preset": "^0.81.4",
+ "@types/react": "~19.1.17",
+ "react": "19.1.0",
+ "react-native": "^0.81.4",
+ "react-native-builder-bob": "^0.40.13",
+ "typescript": "^5.9.3"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": ">=0.79.0"
+ },
+ "react-native-builder-bob": {
+ "source": "src",
+ "output": "dist",
+ "targets": [
+ [
+ "module",
+ {
+ "esm": true
+ }
+ ],
+ [
+ "typescript",
+ {
+ "project": "tsconfig.json"
+ }
+ ]
+ ]
+ },
+ "codegenConfig": {
+ "name": "BroadcastSpec",
+ "type": "modules",
+ "jsSrcsDir": "src",
+ "android": {
+ "javaPackageName": "io.getstream.rn.broadcast"
+ }
+ }
+}
diff --git a/packages/react-native-broadcast/src/Broadcast.ts b/packages/react-native-broadcast/src/Broadcast.ts
new file mode 100644
index 0000000000..91b85b40f4
--- /dev/null
+++ b/packages/react-native-broadcast/src/Broadcast.ts
@@ -0,0 +1,93 @@
+import NativeBroadcast, { type Preset } from './NativeBroadcast';
+import { BehaviorSubject } from 'rxjs';
+import {
+ type EventSubscription,
+ NativeEventEmitter,
+ NativeModules,
+} from 'react-native';
+import { Presets } from './Presets';
+import {
+ type CameraDirection,
+ type MediaState,
+ TypedNativeEventEmitter,
+} from './events';
+
+const BroadcastEvents = new TypedNativeEventEmitter(
+ new NativeEventEmitter(NativeModules.BroadcastEventEmitter),
+);
+
+export class Broadcast {
+ private readonly instanceId: string;
+
+ // Subjects holding native-synchronized state
+ readonly running$ = new BehaviorSubject(false);
+ readonly mediaState$ = new BehaviorSubject({
+ cameraEnabled: true,
+ microphoneEnabled: true,
+ cameraDirection: 'front',
+ });
+
+ private subscriptions: EventSubscription[] = [];
+
+ private constructor(instanceId: string) {
+ this.instanceId = instanceId;
+
+ // Subscribe to native events for this instance
+ this.subscriptions.push(
+ BroadcastEvents.addListener('broadcast.started', (payload) => {
+ if (payload.instanceId !== this.instanceId) return;
+ this.running$.next(payload.running);
+ }),
+ );
+
+ this.subscriptions.push(
+ BroadcastEvents.addListener('broadcast.mediaStateUpdated', (payload) => {
+ const { instanceId: id, ...next } = payload;
+ if (id !== this.instanceId) return;
+ this.mediaState$.next(next);
+ }),
+ );
+ }
+
+ static create(preset: Preset = Presets.PORTRAIT_HD): Broadcast {
+ const instanceId = NativeBroadcast.createInstance(preset);
+ return new Broadcast(instanceId);
+ }
+
+ destroy = () => {
+ // Remove listeners first to avoid receiving post-destroy events
+ this.subscriptions.forEach((s) => s.remove());
+ this.subscriptions = [];
+
+ NativeBroadcast.destroyInstance(this.instanceId);
+
+ // Complete subjects to signal teardown to subscribers
+ this.running$.complete();
+ this.mediaState$.complete();
+ };
+
+ get id() {
+ return this.instanceId;
+ }
+
+ // Control methods
+ start = async (endpoint: string, streamName: string) => {
+ return NativeBroadcast.start(this.instanceId, endpoint, streamName);
+ };
+
+ stop = async () => {
+ return NativeBroadcast.stop(this.instanceId);
+ };
+
+ setCameraDirection = (direction: CameraDirection) => {
+ NativeBroadcast.setCameraDirection(this.instanceId, direction);
+ };
+
+ setCameraEnabled = (enabled: boolean) => {
+ NativeBroadcast.setCameraEnabled(this.instanceId, enabled);
+ };
+
+ setMicrophoneEnabled = (enabled: boolean) => {
+ NativeBroadcast.setMicrophoneEnabled(this.instanceId, enabled);
+ };
+}
diff --git a/packages/react-native-broadcast/src/BroadcastVideoView.tsx b/packages/react-native-broadcast/src/BroadcastVideoView.tsx
new file mode 100644
index 0000000000..74808dd757
--- /dev/null
+++ b/packages/react-native-broadcast/src/BroadcastVideoView.tsx
@@ -0,0 +1,27 @@
+import { requireNativeComponent, type ViewStyle } from 'react-native';
+import type { Broadcast } from './Broadcast';
+
+export interface BroadcastVideoViewProps {
+ style?: ViewStyle;
+ broadcast: Broadcast;
+}
+
+interface NativeBroadcastVideoViewProps {
+ style?: ViewStyle;
+ instanceId: string;
+}
+
+const NativeBroadcastVideoView =
+ requireNativeComponent('BroadcastVideoView');
+
+export const BroadcastVideoView = ({
+ style,
+ broadcast,
+}: BroadcastVideoViewProps) => {
+ return (
+
+ );
+};
diff --git a/packages/react-native-broadcast/src/NativeBroadcast.ts b/packages/react-native-broadcast/src/NativeBroadcast.ts
new file mode 100644
index 0000000000..8f790d5082
--- /dev/null
+++ b/packages/react-native-broadcast/src/NativeBroadcast.ts
@@ -0,0 +1,29 @@
+import { type TurboModule, TurboModuleRegistry } from 'react-native';
+
+export type Preset = {
+ width: number;
+ height: number;
+ frameRate: number;
+ videoBitrate: number;
+ audioBitrate: number;
+};
+
+export interface Spec extends TurboModule {
+ // Synchronous creation with preset
+ createInstance: (preset: Preset) => string;
+
+ destroyInstance: (instanceId: string) => void;
+
+ start: (
+ instanceId: string,
+ endpoint: string,
+ streamName: string,
+ ) => Promise;
+ stop: (instanceId: string) => Promise;
+
+ setCameraDirection: (instanceId: string, direction: string) => void;
+ setCameraEnabled: (instanceId: string, enabled: boolean) => void;
+ setMicrophoneEnabled: (instanceId: string, enabled: boolean) => void;
+}
+
+export default TurboModuleRegistry.getEnforcing('Broadcast');
diff --git a/packages/react-native-broadcast/src/Presets.ts b/packages/react-native-broadcast/src/Presets.ts
new file mode 100644
index 0000000000..c7108235a0
--- /dev/null
+++ b/packages/react-native-broadcast/src/Presets.ts
@@ -0,0 +1,26 @@
+import type { Preset } from './NativeBroadcast';
+
+export class Presets {
+ /**
+ * HD portrait mode preset. Recommended for most use cases.
+ */
+ static PORTRAIT_HD: Preset = {
+ width: 720,
+ height: 1280,
+ frameRate: 30,
+ videoBitrate: 3_000_000,
+ audioBitrate: 128_000,
+ };
+
+ /**
+ * Full HD portrait mode preset. Recommended for high-resolution broadcasts,
+ * but it can be slow on older devices.
+ */
+ static PORTRAIT_FULL_HD: Preset = {
+ width: 1080,
+ height: 1920,
+ frameRate: 30,
+ videoBitrate: 4_000_000,
+ audioBitrate: 128_000,
+ };
+}
diff --git a/packages/react-native-broadcast/src/events.ts b/packages/react-native-broadcast/src/events.ts
new file mode 100644
index 0000000000..6e50f77e8a
--- /dev/null
+++ b/packages/react-native-broadcast/src/events.ts
@@ -0,0 +1,38 @@
+import { type EventSubscription, NativeEventEmitter } from 'react-native';
+
+export type CameraDirection = 'front' | 'back';
+
+export type MediaState = {
+ cameraEnabled: boolean;
+ microphoneEnabled: boolean;
+ cameraDirection: CameraDirection;
+};
+
+// Event typings
+export type BroadcastStartedEvent = {
+ instanceId: string;
+ running: boolean;
+};
+
+export type BroadcastMediaStateUpdatedEvent = MediaState & {
+ instanceId: string;
+};
+
+type BroadcastEvents = {
+ 'broadcast.started': BroadcastStartedEvent;
+ 'broadcast.mediaStateUpdated': BroadcastMediaStateUpdatedEvent;
+};
+
+export class TypedNativeEventEmitter {
+ constructor(private readonly emitter: NativeEventEmitter) {}
+
+ addListener<
+ E extends Extract,
+ V = BroadcastEvents[E],
+ >(event: E, listener: (payload: V) => void): EventSubscription {
+ return this.emitter.addListener(
+ event,
+ listener as unknown as (...args: any[]) => any,
+ );
+ }
+}
diff --git a/packages/react-native-broadcast/src/index.tsx b/packages/react-native-broadcast/src/index.tsx
new file mode 100644
index 0000000000..fe5b059399
--- /dev/null
+++ b/packages/react-native-broadcast/src/index.tsx
@@ -0,0 +1,3 @@
+export { Broadcast } from './Broadcast';
+export * from './BroadcastVideoView';
+export * from './Presets';
diff --git a/packages/react-native-broadcast/src/native-module.d.ts b/packages/react-native-broadcast/src/native-module.d.ts
new file mode 100644
index 0000000000..2506fcbd7b
--- /dev/null
+++ b/packages/react-native-broadcast/src/native-module.d.ts
@@ -0,0 +1,13 @@
+import 'react-native';
+import type { Spec as BroadcastSpec } from './NativeBroadcast';
+
+declare module 'react-native' {
+ interface NativeModulesStatic {
+ /**
+ * Native Broadcast TurboModule, accessible via NativeModules.Broadcast.
+ * For direct (and preferred) access, use TurboModuleRegistry through
+ * the typed helper exported by this package (NativeBroadcast.ts).
+ */
+ Broadcast: BroadcastSpec;
+ }
+}
diff --git a/packages/react-native-broadcast/tsconfig.json b/packages/react-native-broadcast/tsconfig.json
new file mode 100644
index 0000000000..4618786b46
--- /dev/null
+++ b/packages/react-native-broadcast/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "rootDir": ".",
+ "paths": {
+ "react-native-broadcast": ["./src/index"]
+ },
+ "allowUnreachableCode": false,
+ "allowUnusedLabels": false,
+ "customConditions": ["react-native-strict-api"],
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "jsx": "react-jsx",
+ "lib": ["ESNext"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "noEmit": true,
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitReturns": true,
+ "noImplicitUseStrict": false,
+ "noStrictGenericChecks": false,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "ESNext",
+ "verbatimModuleSyntax": true
+ }
+}
diff --git a/packages/video-filters-react-native/package.json b/packages/video-filters-react-native/package.json
index b1011ecbaa..ef711375fb 100644
--- a/packages/video-filters-react-native/package.json
+++ b/packages/video-filters-react-native/package.json
@@ -50,7 +50,7 @@
"@stream-io/react-native-webrtc": "125.4.4",
"react": "19.1.0",
"react-native": "^0.81.4",
- "react-native-builder-bob": "^0.37.0",
+ "react-native-builder-bob": "^0.40.13",
"rimraf": "^6.0.1",
"typescript": "^5.9.3"
},
diff --git a/sample-apps/react-native/dogfood/App.tsx b/sample-apps/react-native/dogfood/App.tsx
index 3d0d46c9ab..06d360e3cb 100755
--- a/sample-apps/react-native/dogfood/App.tsx
+++ b/sample-apps/react-native/dogfood/App.tsx
@@ -34,6 +34,7 @@ import { NavigationHeader } from './src/components/NavigationHeader';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Alert, LogBox } from 'react-native';
import { LiveStream } from './src/navigators/Livestream';
+import { RTMP } from './src/navigators/RTMP';
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import {
defaultTheme,
@@ -124,6 +125,15 @@ const StackNavigator = () => {
/>
);
break;
+ case 'RTMP':
+ mode = (
+
+ );
+ break;
case 'None':
mode = (
();
+
+export const RTMP = () => {
+ return (
+
+
+
+ );
+};
diff --git a/sample-apps/react-native/dogfood/src/screens/ChooseAppModeScreen.tsx b/sample-apps/react-native/dogfood/src/screens/ChooseAppModeScreen.tsx
index 6f4043c3e4..00ac15eb17 100644
--- a/sample-apps/react-native/dogfood/src/screens/ChooseAppModeScreen.tsx
+++ b/sample-apps/react-native/dogfood/src/screens/ChooseAppModeScreen.tsx
@@ -38,6 +38,10 @@ export const ChooseAppModeScreen = () => {
setState({ appMode: 'Call' });
};
+ const onRTMPSelect = () => {
+ setState({ appMode: 'RTMP' });
+ };
+
const landscapeStyles: ViewStyle = {
flexDirection: orientation === 'landscape' ? 'row' : 'column',
};
@@ -71,6 +75,11 @@ export const ChooseAppModeScreen = () => {
onPress={onLiveStreamSelect}
buttonStyle={styles.callButton}
/>
+
);
diff --git a/sample-apps/react-native/dogfood/src/screens/RTMPBroadcastScreen.tsx b/sample-apps/react-native/dogfood/src/screens/RTMPBroadcastScreen.tsx
new file mode 100644
index 0000000000..c8a0d2b06d
--- /dev/null
+++ b/sample-apps/react-native/dogfood/src/screens/RTMPBroadcastScreen.tsx
@@ -0,0 +1,323 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import {
+ ActivityIndicator,
+ StatusBar,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import {
+ Broadcast,
+ BroadcastVideoView,
+} from '@stream-io/video-react-native-broadcast';
+import { useObservableValue } from '@stream-io/video-react-native-sdk';
+import {
+ useAppGlobalStoreSetState,
+ useAppGlobalStoreValue,
+} from '../contexts/AppContext';
+import { appTheme } from '../theme';
+import { Button } from '../components/Button';
+
+const RTMP_ENDPOINT =
+ 'rtmps://ingress.stream-io-video.com:443/hd8szvscpxvd.default.RdcC9Qr4j7pzr62FZbo8Q';
+// 'rtmps://ingress.stream-io-video.com:443/par8f5s3gn2j.default.RdcC9Qr4j7pzr62FZbo8Q';
+const RTMP_STREAM_NAME =
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL2phbmUiLCJ1c2VyX2lkIjoiamFuZSIsInZhbGlkaXR5X2luX3NlY29uZHMiOjYwNDgwMCwiZW52aXJvbm1lbnQiOiJwcm9udG8tc3RhZ2luZyIsImlhdCI6MTc2MTIxNDkzNywiZXhwIjoxNzYxODE5NzM3fQ.ZsrsSSpgDu9J3Npw3S3LCTJ5tiMgj8FsAOpVETkc0QI';
+// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL2phbmUiLCJ1c2VyX2lkIjoiamFuZSIsInZhbGlkaXR5X2luX3NlY29uZHMiOjYwNDgwMCwiZW52aXJvbm1lbnQiOiJwcm9udG8iLCJpYXQiOjE3NjA5Njc0MzMsImV4cCI6MTc2MTU3MjIzM30.pkBwlOMlo7wUJG4DSG7fk8QAxF912Y5UaErm4H6a59I';
+
+export const RTMPBroadcastScreen = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState();
+ const themeMode = useAppGlobalStoreValue((store) => store.themeMode);
+ const setAppStore = useAppGlobalStoreSetState();
+ const broadcast = useMemo(() => Broadcast.create(), []);
+ const running = useObservableValue(broadcast.running$);
+ const mediaState = useObservableValue(broadcast.mediaState$);
+ useEffect(() => {
+ return () => {
+ broadcast.stop().then(() => broadcast.destroy());
+ };
+ }, [broadcast]);
+
+ const startBroadcast = async () => {
+ try {
+ setIsLoading(true);
+ setError(undefined);
+ console.log('[RTMP] Starting broadcast...');
+
+ await broadcast.start(RTMP_ENDPOINT, RTMP_STREAM_NAME);
+ } catch (err) {
+ console.error('[RTMP] Failed to start broadcast:', err);
+ setError(
+ err instanceof Error ? err.message : 'Failed to start broadcast',
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const stopBroadcast = async () => {
+ try {
+ setIsLoading(true);
+ await broadcast.stop();
+ } catch (err) {
+ console.error('[RTMP] Failed to stop broadcast:', err);
+ setError(err instanceof Error ? err.message : 'Failed to stop broadcast');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const toggleCamera = async () => {
+ try {
+ const next = !mediaState.cameraEnabled;
+ broadcast.setCameraEnabled(next);
+ } catch (err) {
+ console.error('[RTMP] Failed to toggle camera:', err);
+ setError(err instanceof Error ? err.message : 'Failed to toggle camera');
+ }
+ };
+
+ const toggleMicrophone = async () => {
+ try {
+ const next = !mediaState.microphoneEnabled;
+ broadcast.setMicrophoneEnabled(next);
+ } catch (err) {
+ console.error('[RTMP] Failed to toggle microphone:', err);
+ setError(
+ err instanceof Error ? err.message : 'Failed to toggle microphone',
+ );
+ }
+ };
+
+ const switchCamera = async () => {
+ try {
+ const next = mediaState.cameraDirection === 'front' ? 'back' : 'front';
+ broadcast.setCameraDirection(next);
+ } catch (err) {
+ console.error('[RTMP] Failed to switch camera:', err);
+ setError(err instanceof Error ? err.message : 'Failed to switch camera');
+ }
+ };
+
+ const styles = useStyles(themeMode);
+
+ const onChangeMode = async () => {
+ try {
+ setIsLoading(true);
+ if (running) {
+ try {
+ await broadcast.stop();
+ } catch (e) {
+ console.warn('[RTMP] Error while stopping before changing mode', e);
+ }
+ }
+ setAppStore({ appMode: 'None' });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+ RTMP Broadcast
+
+ {running && (
+
+
+ LIVE
+
+ )}
+ {!running && (
+
+ )}
+
+
+
+
+ {running ? (
+
+ ) : (
+
+
+ {isLoading
+ ? 'Initializing broadcast...'
+ : 'Tap "Start Broadcast" to begin'}
+
+ {isLoading && (
+
+ )}
+
+ )}
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+ {!running && (
+
+ )}
+ {running && (
+ <>
+
+ Broadcasting to RTMP server
+ Stream is live
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+};
+
+const useStyles = (themeMode: 'light' | 'dark') => {
+ return StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor:
+ themeMode === 'light'
+ ? appTheme.colors.static_white
+ : appTheme.colors.static_grey,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ padding: 16,
+ paddingTop: 60,
+ },
+ headerActions: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ color:
+ themeMode === 'light'
+ ? appTheme.colors.dark_gray
+ : appTheme.colors.static_white,
+ },
+ liveIndicator: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#FF0000',
+ paddingHorizontal: 12,
+ paddingVertical: 6,
+ borderRadius: 4,
+ },
+ liveDot: {
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ backgroundColor: appTheme.colors.static_white,
+ marginRight: 6,
+ },
+ liveText: {
+ color: appTheme.colors.static_white,
+ fontWeight: 'bold',
+ fontSize: 14,
+ },
+ videoContainer: {
+ flex: 1,
+ margin: 16,
+ borderRadius: 12,
+ overflow: 'hidden',
+ backgroundColor: appTheme.colors.dark_gray,
+ },
+ video: {
+ flex: 1,
+ },
+ placeholder: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: 16,
+ },
+ placeholderText: {
+ color: appTheme.colors.static_white,
+ fontSize: 16,
+ textAlign: 'center',
+ paddingHorizontal: 32,
+ },
+ controls: {
+ padding: 16,
+ paddingBottom: 32,
+ },
+ errorContainer: {
+ backgroundColor: '#FF000020',
+ padding: 12,
+ marginHorizontal: 16,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#FF0000',
+ },
+ errorText: {
+ color: '#FF0000',
+ fontSize: 14,
+ textAlign: 'center',
+ },
+ infoContainer: {
+ alignItems: 'center',
+ gap: 4,
+ },
+ infoText: {
+ color:
+ themeMode === 'light'
+ ? appTheme.colors.dark_gray
+ : appTheme.colors.static_white,
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ infoSubText: {
+ color: themeMode === 'light' ? '#666' : '#999',
+ fontSize: 14,
+ },
+ });
+};
diff --git a/sample-apps/react-native/dogfood/types.ts b/sample-apps/react-native/dogfood/types.ts
index 3e62b9ed6f..27de6ec82a 100644
--- a/sample-apps/react-native/dogfood/types.ts
+++ b/sample-apps/react-native/dogfood/types.ts
@@ -28,6 +28,10 @@ export type LiveStreamParamList = {
QRScanner: { onScan: (callId: string) => void };
};
+export type RTMPParamList = {
+ RTMPBroadcast: undefined;
+};
+
export type AppModeParamList = {
ChooseAppModeScreen: undefined;
};
@@ -38,6 +42,7 @@ export type RootStackParamList = {
AudioRoom: undefined;
ChooseAppMode: undefined;
LiveStream: undefined;
+ RTMP: undefined;
};
export type ScreenTypes =
diff --git a/yarn.lock b/yarn.lock
index 75cbf825c3..f7cde15ea3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -59,6 +59,31 @@ __metadata:
languageName: node
linkType: hard
+"@ark/regex@npm:0.0.0":
+ version: 0.0.0
+ resolution: "@ark/regex@npm:0.0.0"
+ dependencies:
+ "@ark/util": "npm:0.50.0"
+ checksum: 10/ba2f539b865ae9bdcc97ef1272e322e68c5d3e27327e2cd90d414df588cdbf16dca2ccd0d99ce2869efcd76063b24532b5bcea83866b8798d07f6fce35ec5c96
+ languageName: node
+ linkType: hard
+
+"@ark/schema@npm:0.50.0":
+ version: 0.50.0
+ resolution: "@ark/schema@npm:0.50.0"
+ dependencies:
+ "@ark/util": "npm:0.50.0"
+ checksum: 10/12608d828f38661cf320f4e45228f10fab73750c6b115ec6b600cbd60ae76944d0816b68be59c36c7442da21862ce57a687264f8c224222e238e5410292719b6
+ languageName: node
+ linkType: hard
+
+"@ark/util@npm:0.50.0":
+ version: 0.50.0
+ resolution: "@ark/util@npm:0.50.0"
+ checksum: 10/aa253cca47365a169e5de02f09f8aeb5ea8887475870c5a815d62139ab592ca173189e518d0c4aec1508617e29af30048da8b2601ec04f9e91782f63c338a030
+ languageName: node
+ linkType: hard
+
"@babel/cli@npm:^7.23.4":
version: 7.26.4
resolution: "@babel/cli@npm:7.26.4"
@@ -136,7 +161,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/generator@npm:^7.20.0, @babel/generator@npm:^7.20.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.7, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2":
+"@babel/generator@npm:^7.20.5, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.7, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2":
version: 7.28.3
resolution: "@babel/generator@npm:7.28.3"
dependencies:
@@ -371,7 +396,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4":
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.25.4, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3, @babel/parser@npm:^7.28.4":
version: 7.28.4
resolution: "@babel/parser@npm:7.28.4"
dependencies:
@@ -554,14 +579,14 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-syntax-flow@npm:^7.12.1, @babel/plugin-syntax-flow@npm:^7.26.0":
- version: 7.26.0
- resolution: "@babel/plugin-syntax-flow@npm:7.26.0"
+"@babel/plugin-syntax-flow@npm:^7.12.1, @babel/plugin-syntax-flow@npm:^7.27.1":
+ version: 7.27.1
+ resolution: "@babel/plugin-syntax-flow@npm:7.27.1"
dependencies:
- "@babel/helper-plugin-utils": "npm:^7.25.9"
+ "@babel/helper-plugin-utils": "npm:^7.27.1"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/fdc0d0a7b512e00d933e12cf93c785ea4645a193f4b539230b7601cfaa8c704410199318ce9ea14e5fca7d13e9027822f7d81a7871d3e854df26b6af04cc3c6c
+ checksum: 10/7baca3171ed595d04c865b0ce46fca7f21900686df9d7fcd1017036ce78bb5483e33803de810831e68d39cf478953db69f49ae3f3de2e3207bc4ba49a96b6739
languageName: node
linkType: hard
@@ -923,15 +948,15 @@ __metadata:
languageName: node
linkType: hard
-"@babel/plugin-transform-flow-strip-types@npm:^7.25.2, @babel/plugin-transform-flow-strip-types@npm:^7.25.9":
- version: 7.26.5
- resolution: "@babel/plugin-transform-flow-strip-types@npm:7.26.5"
+"@babel/plugin-transform-flow-strip-types@npm:^7.25.2, @babel/plugin-transform-flow-strip-types@npm:^7.25.9, @babel/plugin-transform-flow-strip-types@npm:^7.26.5":
+ version: 7.27.1
+ resolution: "@babel/plugin-transform-flow-strip-types@npm:7.27.1"
dependencies:
- "@babel/helper-plugin-utils": "npm:^7.26.5"
- "@babel/plugin-syntax-flow": "npm:^7.26.0"
+ "@babel/helper-plugin-utils": "npm:^7.27.1"
+ "@babel/plugin-syntax-flow": "npm:^7.27.1"
peerDependencies:
"@babel/core": ^7.0.0-0
- checksum: 10/01ffdf56f0cbf26d222311cd69be4e5997182dbe6fee217f241c8d67f5e5b115b70efa4acd27d850f0a242b0d36b062d255d763984416155d0237c3ee9e9b8ea
+ checksum: 10/22e260866b122b7d0c35f2c55b2d422b175606b4d14c9ba116b1fbe88e08cc8b024c1c41bb62527cfc5f7ccc0ed06c752e5945cb1ee22465a30aa5623e617940
languageName: node
linkType: hard
@@ -1526,7 +1551,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/preset-flow@npm:^7.17.12, @babel/preset-flow@npm:^7.24.7":
+"@babel/preset-flow@npm:^7.17.12":
version: 7.25.9
resolution: "@babel/preset-flow@npm:7.25.9"
dependencies:
@@ -1590,7 +1615,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/template@npm:^7.0.0, @babel/template@npm:^7.25.0, @babel/template@npm:^7.25.7, @babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3":
+"@babel/template@npm:^7.25.0, @babel/template@npm:^7.25.7, @babel/template@npm:^7.27.1, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3":
version: 7.27.2
resolution: "@babel/template@npm:7.27.2"
dependencies:
@@ -1616,7 +1641,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4":
+"@babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.4":
version: 7.28.4
resolution: "@babel/traverse@npm:7.28.4"
dependencies:
@@ -1631,7 +1656,7 @@ __metadata:
languageName: node
linkType: hard
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
version: 7.28.4
resolution: "@babel/types@npm:7.28.4"
dependencies:
@@ -7254,7 +7279,7 @@ __metadata:
"@stream-io/react-native-webrtc": "npm:125.4.4"
react: "npm:19.1.0"
react-native: "npm:^0.81.4"
- react-native-builder-bob: "npm:^0.37.0"
+ react-native-builder-bob: "npm:^0.40.13"
rimraf: "npm:^6.0.1"
typescript: "npm:^5.9.3"
peerDependencies:
@@ -7353,7 +7378,7 @@ __metadata:
"@stream-io/react-native-webrtc": "npm:125.4.4"
react: "npm:19.1.0"
react-native: "npm:^0.81.4"
- react-native-builder-bob: "npm:^0.37.0"
+ react-native-builder-bob: "npm:^0.40.13"
rimraf: "npm:^6.0.1"
typescript: "npm:^5.9.3"
peerDependencies:
@@ -7462,6 +7487,24 @@ __metadata:
languageName: unknown
linkType: soft
+"@stream-io/video-react-native-broadcast@workspace:^, @stream-io/video-react-native-broadcast@workspace:packages/react-native-broadcast":
+ version: 0.0.0-use.local
+ resolution: "@stream-io/video-react-native-broadcast@workspace:packages/react-native-broadcast"
+ dependencies:
+ "@react-native-community/cli": "npm:20.0.0"
+ "@react-native/babel-preset": "npm:^0.81.4"
+ "@types/react": "npm:~19.1.17"
+ react: "npm:19.1.0"
+ react-native: "npm:^0.81.4"
+ react-native-builder-bob: "npm:^0.40.13"
+ rxjs: "npm:^7.8.2"
+ typescript: "npm:^5.9.3"
+ peerDependencies:
+ react: "*"
+ react-native: ">=0.79.0"
+ languageName: unknown
+ linkType: soft
+
"@stream-io/video-react-native-dogfood@workspace:sample-apps/react-native/dogfood":
version: 0.0.0-use.local
resolution: "@stream-io/video-react-native-dogfood@workspace:sample-apps/react-native/dogfood"
@@ -7490,6 +7533,7 @@ __metadata:
"@stream-io/noise-cancellation-react-native": "workspace:^"
"@stream-io/react-native-webrtc": "npm:125.4.4"
"@stream-io/video-filters-react-native": "workspace:^"
+ "@stream-io/video-react-native-broadcast": "workspace:^"
"@stream-io/video-react-native-sdk": "workspace:^"
"@types/react": "npm:~19.1.17"
axios: "npm:^1.12.2"
@@ -9019,6 +9063,17 @@ __metadata:
languageName: node
linkType: hard
+"arktype@npm:^2.1.15":
+ version: 2.1.23
+ resolution: "arktype@npm:2.1.23"
+ dependencies:
+ "@ark/regex": "npm:0.0.0"
+ "@ark/schema": "npm:0.50.0"
+ "@ark/util": "npm:0.50.0"
+ checksum: 10/772d67c688015ae1aa4d853f4d1bf970d657054df26b83ba7d7437d8981c7bb74680f095d16aec2e4bfc7f4b2cec626bc6d562d9319d4a1f7f6bc955fe312558
+ languageName: node
+ linkType: hard
+
"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
version: 1.0.2
resolution: "array-buffer-byte-length@npm:1.0.2"
@@ -9342,19 +9397,6 @@ __metadata:
languageName: node
linkType: hard
-"babel-plugin-module-resolver@npm:^5.0.2":
- version: 5.0.2
- resolution: "babel-plugin-module-resolver@npm:5.0.2"
- dependencies:
- find-babel-config: "npm:^2.1.1"
- glob: "npm:^9.3.3"
- pkg-up: "npm:^3.1.0"
- reselect: "npm:^4.1.7"
- resolve: "npm:^1.22.8"
- checksum: 10/8084fa8a4cd96aaa861e5fe765a6cd03accef64d21d4108e314029bcd5f3a7fd96faf0c877c575a6a24d4fe0d87458d49748ca56faa4c77b2b812e4ed6023768
- languageName: node
- linkType: hard
-
"babel-plugin-polyfill-corejs2@npm:^0.4.10, babel-plugin-polyfill-corejs2@npm:^0.4.14":
version: 0.4.14
resolution: "babel-plugin-polyfill-corejs2@npm:0.4.14"
@@ -9428,6 +9470,15 @@ __metadata:
languageName: node
linkType: hard
+"babel-plugin-syntax-hermes-parser@npm:^0.28.0":
+ version: 0.28.1
+ resolution: "babel-plugin-syntax-hermes-parser@npm:0.28.1"
+ dependencies:
+ hermes-parser: "npm:0.28.1"
+ checksum: 10/2cbc921e663463480ead9ccc8bb229a5196032367ba2b5ccb18a44faa3afa84b4dc493297749983b9a837a3d76b0b123664aecc06f9122618c3246f03e076a9d
+ languageName: node
+ linkType: hard
+
"babel-plugin-transform-flow-enums@npm:^0.0.2":
version: 0.0.2
resolution: "babel-plugin-transform-flow-enums@npm:0.0.2"
@@ -10691,13 +10742,6 @@ __metadata:
languageName: node
linkType: hard
-"core-util-is@npm:~1.0.0":
- version: 1.0.3
- resolution: "core-util-is@npm:1.0.3"
- checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99
- languageName: node
- linkType: hard
-
"cosmiconfig@npm:^5.0.5":
version: 5.2.1
resolution: "cosmiconfig@npm:5.2.1"
@@ -10944,7 +10988,7 @@ __metadata:
languageName: node
linkType: hard
-"debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.6.9":
+"debug@npm:2.6.9, debug@npm:^2.6.9":
version: 2.6.9
resolution: "debug@npm:2.6.9"
dependencies:
@@ -11142,13 +11186,6 @@ __metadata:
languageName: node
linkType: hard
-"denodeify@npm:^1.2.1":
- version: 1.2.1
- resolution: "denodeify@npm:1.2.1"
- checksum: 10/f5371a93051a81b0d8f54ac2972de6ae7cd9ba272174dff58bbf28a545c5b38e1952b3e8860e6b31ead44981bb14e158720fa43501e86252315b25f3ca34a460
- languageName: node
- linkType: hard
-
"depd@npm:2.0.0, depd@npm:^2.0.0":
version: 2.0.0
resolution: "depd@npm:2.0.0"
@@ -12881,16 +12918,16 @@ __metadata:
languageName: node
linkType: hard
-"fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2":
- version: 3.3.2
- resolution: "fast-glob@npm:3.3.2"
+"fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3":
+ version: 3.3.3
+ resolution: "fast-glob@npm:3.3.3"
dependencies:
"@nodelib/fs.stat": "npm:^2.0.2"
"@nodelib/fs.walk": "npm:^1.2.3"
glob-parent: "npm:^5.1.2"
merge2: "npm:^1.3.0"
- micromatch: "npm:^4.0.4"
- checksum: 10/222512e9315a0efca1276af9adb2127f02105d7288fa746145bf45e2716383fb79eb983c89601a72a399a56b7c18d38ce70457c5466218c5f13fad957cee16df
+ micromatch: "npm:^4.0.8"
+ checksum: 10/dcc6432b269762dd47381d8b8358bf964d8f4f60286ac6aa41c01ade70bda459ff2001b516690b96d5365f68a49242966112b5d5cc9cd82395fa8f9d017c90ad
languageName: node
linkType: hard
@@ -13073,15 +13110,6 @@ __metadata:
languageName: node
linkType: hard
-"find-babel-config@npm:^2.1.1":
- version: 2.1.2
- resolution: "find-babel-config@npm:2.1.2"
- dependencies:
- json5: "npm:^2.2.3"
- checksum: 10/f0fae1a9125a379cf660fc1b5ca7c1fc1edac5f47e521a89e4c2b92865c8e57101a9152ee503eef9f33e16f196182f2cff03d7768b7caf5eef81c80f1c124a2f
- languageName: node
- linkType: hard
-
"find-root@npm:^1.1.0":
version: 1.1.0
resolution: "find-root@npm:1.1.0"
@@ -13089,15 +13117,6 @@ __metadata:
languageName: node
linkType: hard
-"find-up@npm:^3.0.0":
- version: 3.0.0
- resolution: "find-up@npm:3.0.0"
- dependencies:
- locate-path: "npm:^3.0.0"
- checksum: 10/38eba3fe7a66e4bc7f0f5a1366dc25508b7cfc349f852640e3678d26ad9a6d7e2c43eff0a472287de4a9753ef58f066a0ea892a256fa3636ad51b3fe1e17fae9
- languageName: node
- linkType: hard
-
"find-up@npm:^4.0.0, find-up@npm:^4.1.0":
version: 4.1.0
resolution: "find-up@npm:4.1.0"
@@ -13715,7 +13734,7 @@ __metadata:
languageName: node
linkType: hard
-"glob@npm:^9.3.2, glob@npm:^9.3.3":
+"glob@npm:^9.3.2":
version: 9.3.5
resolution: "glob@npm:9.3.5"
dependencies:
@@ -13976,13 +13995,6 @@ __metadata:
languageName: node
linkType: hard
-"hermes-estree@npm:0.23.1":
- version: 0.23.1
- resolution: "hermes-estree@npm:0.23.1"
- checksum: 10/b7ad78f53044d53ec1c77e93036c16e34f6f0985c895540876301e4791d4db08da828870977140f5cf1ae34532bbb9d9d013a0a1a4a5a0da05177225648d5295
- languageName: node
- linkType: hard
-
"hermes-estree@npm:0.25.1":
version: 0.25.1
resolution: "hermes-estree@npm:0.25.1"
@@ -13990,6 +14002,13 @@ __metadata:
languageName: node
linkType: hard
+"hermes-estree@npm:0.28.1":
+ version: 0.28.1
+ resolution: "hermes-estree@npm:0.28.1"
+ checksum: 10/3195a1aa7035d96b77839e6bfd6832b51830518aaf8dabfca11248b84d6fb6abd27e21c8caa84229954a76b4f8a1e346b65d421a4daecd3053bd2ea08fe6abc9
+ languageName: node
+ linkType: hard
+
"hermes-estree@npm:0.29.1":
version: 0.29.1
resolution: "hermes-estree@npm:0.29.1"
@@ -14004,12 +14023,12 @@ __metadata:
languageName: node
linkType: hard
-"hermes-parser@npm:0.23.1":
- version: 0.23.1
- resolution: "hermes-parser@npm:0.23.1"
+"hermes-parser@npm:0.28.1":
+ version: 0.28.1
+ resolution: "hermes-parser@npm:0.28.1"
dependencies:
- hermes-estree: "npm:0.23.1"
- checksum: 10/de88df4f23bd8dc2ffa89c8a317445320af8c7705a2aeeb05c4dd171f037a747982be153a0a237b1c9c7337b79bceaeb5052934cb8a25fe2e2473294a5343334
+ hermes-estree: "npm:0.28.1"
+ checksum: 10/cb2aa4d386929825c3bd8184eeb4e3dcf34892c1f850624d09a80aee0674bc2eb135eccaeb7ac33675552130229ee6160025c4e4f351d6a61b503bd8bfdf63f5
languageName: node
linkType: hard
@@ -15006,13 +15025,6 @@ __metadata:
languageName: node
linkType: hard
-"isarray@npm:~1.0.0":
- version: 1.0.0
- resolution: "isarray@npm:1.0.0"
- checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab
- languageName: node
- linkType: hard
-
"isexe@npm:^2.0.0":
version: 2.0.0
resolution: "isexe@npm:2.0.0"
@@ -15622,7 +15634,7 @@ __metadata:
languageName: node
linkType: hard
-"jest-validate@npm:^29.6.3, jest-validate@npm:^29.7.0":
+"jest-validate@npm:^29.7.0":
version: 29.7.0
resolution: "jest-validate@npm:29.7.0"
dependencies:
@@ -15680,7 +15692,7 @@ __metadata:
languageName: node
linkType: hard
-"jest-worker@npm:^29.6.3, jest-worker@npm:^29.7.0":
+"jest-worker@npm:^29.7.0":
version: 29.7.0
resolution: "jest-worker@npm:29.7.0"
dependencies:
@@ -16276,16 +16288,6 @@ __metadata:
languageName: node
linkType: hard
-"locate-path@npm:^3.0.0":
- version: 3.0.0
- resolution: "locate-path@npm:3.0.0"
- dependencies:
- p-locate: "npm:^3.0.0"
- path-exists: "npm:^3.0.0"
- checksum: 10/53db3996672f21f8b0bf2a2c645ae2c13ffdae1eeecfcd399a583bce8516c0b88dcb4222ca6efbbbeb6949df7e46860895be2c02e8d3219abd373ace3bfb4e11
- languageName: node
- linkType: hard
-
"locate-path@npm:^5.0.0":
version: 5.0.0
resolution: "locate-path@npm:5.0.0"
@@ -16962,18 +16964,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-babel-transformer@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-babel-transformer@npm:0.80.12"
- dependencies:
- "@babel/core": "npm:^7.20.0"
- flow-enums-runtime: "npm:^0.0.6"
- hermes-parser: "npm:0.23.1"
- nullthrows: "npm:^1.1.1"
- checksum: 10/3912367e269df3ac697d67541d56fed86ab6fc40ce1aa107b8f332402c7a84a3d0991e536897d4877bab2b1986dd21ec7fad0c76704a27c1c2edce0bcf9037a9
- languageName: node
- linkType: hard
-
"metro-babel-transformer@npm:0.83.1":
version: 0.83.1
resolution: "metro-babel-transformer@npm:0.83.1"
@@ -16998,15 +16988,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-cache-key@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-cache-key@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- checksum: 10/7a06601180604361339d19eb833d61b79cc188a4e6ebe73188cc10fbf3a33e711d74c81d1d19a14b6581bd9dfeebe1b253684360682d033ab55909c9995b6a18
- languageName: node
- linkType: hard
-
"metro-cache-key@npm:0.83.1":
version: 0.83.1
resolution: "metro-cache-key@npm:0.83.1"
@@ -17025,17 +17006,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-cache@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-cache@npm:0.80.12"
- dependencies:
- exponential-backoff: "npm:^3.1.1"
- flow-enums-runtime: "npm:^0.0.6"
- metro-core: "npm:0.80.12"
- checksum: 10/914b599ad4f8a2538e6f4788b3da722aa855688affef3002fe374a0a1cb7dd36ad9224d1ef83f7c17610ebb290cea3cb545bfd67100a216b7bbb3f26f8982c93
- languageName: node
- linkType: hard
-
"metro-cache@npm:0.83.1":
version: 0.83.1
resolution: "metro-cache@npm:0.83.1"
@@ -17060,22 +17030,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-config@npm:0.80.12, metro-config@npm:^0.80.9":
- version: 0.80.12
- resolution: "metro-config@npm:0.80.12"
- dependencies:
- connect: "npm:^3.6.5"
- cosmiconfig: "npm:^5.0.5"
- flow-enums-runtime: "npm:^0.0.6"
- jest-validate: "npm:^29.6.3"
- metro: "npm:0.80.12"
- metro-cache: "npm:0.80.12"
- metro-core: "npm:0.80.12"
- metro-runtime: "npm:0.80.12"
- checksum: 10/2d11745d32e8992b78159c275dc54b08bf258871f274634f9824540f1ec80a9b1a9d7eb5493b52078a5a68cccd4fd688cd846dd0802aea2f065b5588e98eb146
- languageName: node
- linkType: hard
-
"metro-config@npm:0.83.1":
version: 0.83.1
resolution: "metro-config@npm:0.83.1"
@@ -17108,17 +17062,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-core@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-core@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- lodash.throttle: "npm:^4.1.1"
- metro-resolver: "npm:0.80.12"
- checksum: 10/d29ab20df4d19c1d8c5178f7b182e050c659f022ab2adc669504c7ef7fd5d76cdde02936d1599e6d6137e353cbf4fef6b3cfa6aaf217bca954fc23cbf1b61f18
- languageName: node
- linkType: hard
-
"metro-core@npm:0.83.1":
version: 0.83.1
resolution: "metro-core@npm:0.83.1"
@@ -17141,29 +17084,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-file-map@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-file-map@npm:0.80.12"
- dependencies:
- anymatch: "npm:^3.0.3"
- debug: "npm:^2.2.0"
- fb-watchman: "npm:^2.0.0"
- flow-enums-runtime: "npm:^0.0.6"
- fsevents: "npm:^2.3.2"
- graceful-fs: "npm:^4.2.4"
- invariant: "npm:^2.2.4"
- jest-worker: "npm:^29.6.3"
- micromatch: "npm:^4.0.4"
- node-abort-controller: "npm:^3.1.1"
- nullthrows: "npm:^1.1.1"
- walker: "npm:^1.0.7"
- dependenciesMeta:
- fsevents:
- optional: true
- checksum: 10/a0c06da7c89bfbbe17adadb46470274e2e1ffee43126a08f665db230d7831c6195410ea7165f94182e18a27359e140fc8272d2271c04bf0286a1ea95106a3758
- languageName: node
- linkType: hard
-
"metro-file-map@npm:0.83.1":
version: 0.83.1
resolution: "metro-file-map@npm:0.83.1"
@@ -17198,16 +17118,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-minify-terser@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-minify-terser@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- terser: "npm:^5.15.0"
- checksum: 10/ff527b3f04c5814db139e55ceb7689aaaf0af5c7fbb0eb5d4a6f22044932dfb10bd385d388fa7b352acd03a2d078edaf43a6b5cd11cbc87a7c5502a34fc12735
- languageName: node
- linkType: hard
-
"metro-minify-terser@npm:0.83.1":
version: 0.83.1
resolution: "metro-minify-terser@npm:0.83.1"
@@ -17228,15 +17138,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-resolver@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-resolver@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- checksum: 10/e8609f1b93f1bbe7a9f97dd3fa2a6669c0a51f8360fea9a73e528fc25615f7ef61bd8ad9feb9a52fdbf4405a4065195f053183626f3ab56f54225ebefee574ac
- languageName: node
- linkType: hard
-
"metro-resolver@npm:0.83.1":
version: 0.83.1
resolution: "metro-resolver@npm:0.83.1"
@@ -17255,16 +17156,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-runtime@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-runtime@npm:0.80.12"
- dependencies:
- "@babel/runtime": "npm:^7.25.0"
- flow-enums-runtime: "npm:^0.0.6"
- checksum: 10/8a09e7001bd54331c50145d02e6a2b67589da4dd0da3ff1cdb83e6ce161b9079e2a52a4722db8f222b46f666e3dbfe1fc59ee7c277325763a162e3d27ba81d38
- languageName: node
- linkType: hard
-
"metro-runtime@npm:0.83.1":
version: 0.83.1
resolution: "metro-runtime@npm:0.83.1"
@@ -17285,23 +17176,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-source-map@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-source-map@npm:0.80.12"
- dependencies:
- "@babel/traverse": "npm:^7.20.0"
- "@babel/types": "npm:^7.20.0"
- flow-enums-runtime: "npm:^0.0.6"
- invariant: "npm:^2.2.4"
- metro-symbolicate: "npm:0.80.12"
- nullthrows: "npm:^1.1.1"
- ob1: "npm:0.80.12"
- source-map: "npm:^0.5.6"
- vlq: "npm:^1.0.0"
- checksum: 10/ad6e0cf7f4d2727ecb45a082b4ab92915df8c574de0a905023a53e501a32f619aaeb0f94645aca048ae322176600867f5f21119349261427a2de27cb27ef0ef1
- languageName: node
- linkType: hard
-
"metro-source-map@npm:0.83.1":
version: 0.83.1
resolution: "metro-source-map@npm:0.83.1"
@@ -17338,23 +17212,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-symbolicate@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-symbolicate@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- invariant: "npm:^2.2.4"
- metro-source-map: "npm:0.80.12"
- nullthrows: "npm:^1.1.1"
- source-map: "npm:^0.5.6"
- through2: "npm:^2.0.1"
- vlq: "npm:^1.0.0"
- bin:
- metro-symbolicate: src/index.js
- checksum: 10/0c1dd055691bd670fb73a146f7e2fa3235a5afa3135996e681384676a439e10c9efe398d5b07d588907adbfbf65228829ceb57dab2c19a61eb79dde60bb7dc31
- languageName: node
- linkType: hard
-
"metro-symbolicate@npm:0.83.1":
version: 0.83.1
resolution: "metro-symbolicate@npm:0.83.1"
@@ -17387,20 +17244,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-transform-plugins@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-transform-plugins@npm:0.80.12"
- dependencies:
- "@babel/core": "npm:^7.20.0"
- "@babel/generator": "npm:^7.20.0"
- "@babel/template": "npm:^7.0.0"
- "@babel/traverse": "npm:^7.20.0"
- flow-enums-runtime: "npm:^0.0.6"
- nullthrows: "npm:^1.1.1"
- checksum: 10/801510bde9cb70ba47572c3d5d42f98fc2ee173a48ca39893cbdeb689de54d5a1fea5383ba4fe388f334af06ecb651e5634b5e7611223e217668c98f8666c913
- languageName: node
- linkType: hard
-
"metro-transform-plugins@npm:0.83.1":
version: 0.83.1
resolution: "metro-transform-plugins@npm:0.83.1"
@@ -17429,27 +17272,6 @@ __metadata:
languageName: node
linkType: hard
-"metro-transform-worker@npm:0.80.12":
- version: 0.80.12
- resolution: "metro-transform-worker@npm:0.80.12"
- dependencies:
- "@babel/core": "npm:^7.20.0"
- "@babel/generator": "npm:^7.20.0"
- "@babel/parser": "npm:^7.20.0"
- "@babel/types": "npm:^7.20.0"
- flow-enums-runtime: "npm:^0.0.6"
- metro: "npm:0.80.12"
- metro-babel-transformer: "npm:0.80.12"
- metro-cache: "npm:0.80.12"
- metro-cache-key: "npm:0.80.12"
- metro-minify-terser: "npm:0.80.12"
- metro-source-map: "npm:0.80.12"
- metro-transform-plugins: "npm:0.80.12"
- nullthrows: "npm:^1.1.1"
- checksum: 10/a0802ebbc308a3bd6c81f9a1c640c62a8918f4d4e73da2184d24be10014ce6bc1cef53c0ef6a59568ecc0d0d44d43e38ec595d4abda043f93072613261074371
- languageName: node
- linkType: hard
-
"metro-transform-worker@npm:0.83.1":
version: 0.83.1
resolution: "metro-transform-worker@npm:0.83.1"
@@ -17492,58 +17314,6 @@ __metadata:
languageName: node
linkType: hard
-"metro@npm:0.80.12":
- version: 0.80.12
- resolution: "metro@npm:0.80.12"
- dependencies:
- "@babel/code-frame": "npm:^7.0.0"
- "@babel/core": "npm:^7.20.0"
- "@babel/generator": "npm:^7.20.0"
- "@babel/parser": "npm:^7.20.0"
- "@babel/template": "npm:^7.0.0"
- "@babel/traverse": "npm:^7.20.0"
- "@babel/types": "npm:^7.20.0"
- accepts: "npm:^1.3.7"
- chalk: "npm:^4.0.0"
- ci-info: "npm:^2.0.0"
- connect: "npm:^3.6.5"
- debug: "npm:^2.2.0"
- denodeify: "npm:^1.2.1"
- error-stack-parser: "npm:^2.0.6"
- flow-enums-runtime: "npm:^0.0.6"
- graceful-fs: "npm:^4.2.4"
- hermes-parser: "npm:0.23.1"
- image-size: "npm:^1.0.2"
- invariant: "npm:^2.2.4"
- jest-worker: "npm:^29.6.3"
- jsc-safe-url: "npm:^0.2.2"
- lodash.throttle: "npm:^4.1.1"
- metro-babel-transformer: "npm:0.80.12"
- metro-cache: "npm:0.80.12"
- metro-cache-key: "npm:0.80.12"
- metro-config: "npm:0.80.12"
- metro-core: "npm:0.80.12"
- metro-file-map: "npm:0.80.12"
- metro-resolver: "npm:0.80.12"
- metro-runtime: "npm:0.80.12"
- metro-source-map: "npm:0.80.12"
- metro-symbolicate: "npm:0.80.12"
- metro-transform-plugins: "npm:0.80.12"
- metro-transform-worker: "npm:0.80.12"
- mime-types: "npm:^2.1.27"
- nullthrows: "npm:^1.1.1"
- serialize-error: "npm:^2.1.0"
- source-map: "npm:^0.5.6"
- strip-ansi: "npm:^6.0.0"
- throat: "npm:^5.0.0"
- ws: "npm:^7.5.10"
- yargs: "npm:^17.6.2"
- bin:
- metro: src/cli.js
- checksum: 10/b44280b16d3671be97d11327a9fe0bb2db014a6dcedaab9e88d58696a8133246ef7f8290e9fac0841534872132bbc0d7132745b02f3584339c0999d9e7a58c10
- languageName: node
- linkType: hard
-
"metro@npm:0.83.1":
version: 0.83.1
resolution: "metro@npm:0.83.1"
@@ -18502,13 +18272,6 @@ __metadata:
languageName: node
linkType: hard
-"node-abort-controller@npm:^3.1.1":
- version: 3.1.1
- resolution: "node-abort-controller@npm:3.1.1"
- checksum: 10/0a2cdb7ec0aeaf3cb31e1ca0e192f5add48f1c5c9c9ed822129f9dddbd9432f69b7425982f94ce803c56a2104884530aa67cd57696e5774b2e5b8ec2f58de042
- languageName: node
- linkType: hard
-
"node-addon-api@npm:^7.0.0":
version: 7.1.1
resolution: "node-addon-api@npm:7.1.1"
@@ -18771,15 +18534,6 @@ __metadata:
languageName: node
linkType: hard
-"ob1@npm:0.80.12":
- version: 0.80.12
- resolution: "ob1@npm:0.80.12"
- dependencies:
- flow-enums-runtime: "npm:^0.0.6"
- checksum: 10/c78af51d6ecf47ba5198bc7eb27d0456a287589533f1445e6d595e2d067f6f8038da02a98e5faa4a6c3d0c04f77c570bc9b29c652fec55518884c40c73212f17
- languageName: node
- linkType: hard
-
"ob1@npm:0.83.1":
version: 0.83.1
resolution: "ob1@npm:0.83.1"
@@ -19093,7 +18847,7 @@ __metadata:
languageName: node
linkType: hard
-"p-limit@npm:^2.0.0, p-limit@npm:^2.2.0":
+"p-limit@npm:^2.2.0":
version: 2.3.0
resolution: "p-limit@npm:2.3.0"
dependencies:
@@ -19120,15 +18874,6 @@ __metadata:
languageName: node
linkType: hard
-"p-locate@npm:^3.0.0":
- version: 3.0.0
- resolution: "p-locate@npm:3.0.0"
- dependencies:
- p-limit: "npm:^2.0.0"
- checksum: 10/83991734a9854a05fe9dbb29f707ea8a0599391f52daac32b86f08e21415e857ffa60f0e120bfe7ce0cc4faf9274a50239c7895fc0d0579d08411e513b83a4ae
- languageName: node
- linkType: hard
-
"p-locate@npm:^4.1.0":
version: 4.1.0
resolution: "p-locate@npm:4.1.0"
@@ -19299,13 +19044,6 @@ __metadata:
languageName: node
linkType: hard
-"path-exists@npm:^3.0.0":
- version: 3.0.0
- resolution: "path-exists@npm:3.0.0"
- checksum: 10/96e92643aa34b4b28d0de1cd2eba52a1c5313a90c6542d03f62750d82480e20bfa62bc865d5cfc6165f5fcd5aeb0851043c40a39be5989646f223300021bae0a
- languageName: node
- linkType: hard
-
"path-exists@npm:^4.0.0":
version: 4.0.0
resolution: "path-exists@npm:4.0.0"
@@ -19505,15 +19243,6 @@ __metadata:
languageName: node
linkType: hard
-"pkg-up@npm:^3.1.0":
- version: 3.1.0
- resolution: "pkg-up@npm:3.1.0"
- dependencies:
- find-up: "npm:^3.0.0"
- checksum: 10/5bac346b7c7c903613c057ae3ab722f320716199d753f4a7d053d38f2b5955460f3e6ab73b4762c62fd3e947f58e04f1343e92089e7bb6091c90877406fcd8c8
- languageName: node
- linkType: hard
-
"playwright-core@npm:1.56.0":
version: 1.56.0
resolution: "playwright-core@npm:1.56.0"
@@ -19794,13 +19523,6 @@ __metadata:
languageName: node
linkType: hard
-"process-nextick-args@npm:~2.0.0":
- version: 2.0.1
- resolution: "process-nextick-args@npm:2.0.1"
- checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf
- languageName: node
- linkType: hard
-
"process@npm:^0.11.1":
version: 0.11.10
resolution: "process@npm:0.11.10"
@@ -20200,19 +19922,19 @@ __metadata:
languageName: node
linkType: hard
-"react-native-builder-bob@npm:^0.37.0":
- version: 0.37.0
- resolution: "react-native-builder-bob@npm:0.37.0"
+"react-native-builder-bob@npm:^0.40.13":
+ version: 0.40.13
+ resolution: "react-native-builder-bob@npm:0.40.13"
dependencies:
"@babel/core": "npm:^7.25.2"
+ "@babel/plugin-transform-flow-strip-types": "npm:^7.26.5"
"@babel/plugin-transform-strict-mode": "npm:^7.24.7"
"@babel/preset-env": "npm:^7.25.2"
- "@babel/preset-flow": "npm:^7.24.7"
"@babel/preset-react": "npm:^7.24.7"
"@babel/preset-typescript": "npm:^7.24.7"
- babel-plugin-module-resolver: "npm:^5.0.2"
+ arktype: "npm:^2.1.15"
+ babel-plugin-syntax-hermes-parser: "npm:^0.28.0"
browserslist: "npm:^4.20.4"
- cosmiconfig: "npm:^9.0.0"
cross-spawn: "npm:^7.0.3"
dedent: "npm:^0.7.0"
del: "npm:^6.1.1"
@@ -20222,13 +19944,13 @@ __metadata:
is-git-dirty: "npm:^2.0.1"
json5: "npm:^2.2.1"
kleur: "npm:^4.1.4"
- metro-config: "npm:^0.80.9"
prompts: "npm:^2.4.2"
+ react-native-monorepo-config: "npm:^0.1.8"
which: "npm:^2.0.2"
yargs: "npm:^17.5.1"
bin:
bob: bin/bob
- checksum: 10/194e91d1fdce14a495af23298759accd86b5c9f8eee094a45c2b77d12c1ce81f8e8ed5988c4418ab611f432fc704c9d985e688a7d91a7170824ffb9ab553105a
+ checksum: 10/7359398d32f237c5b3366dd6dc86862ac2059ec9878d21f4dccb10b432d154afffb68bcdfa3ce6781c1391ea08ca8d93bd2592b7d86c8d013d123736e7b5112a
languageName: node
linkType: hard
@@ -20363,6 +20085,16 @@ __metadata:
languageName: node
linkType: hard
+"react-native-monorepo-config@npm:^0.1.8":
+ version: 0.1.10
+ resolution: "react-native-monorepo-config@npm:0.1.10"
+ dependencies:
+ escape-string-regexp: "npm:^5.0.0"
+ fast-glob: "npm:^3.3.3"
+ checksum: 10/36611eca9cbda6647111e659d5c466fdba002c608172b9d25880b6e3ac95c51f41d15520e06d9d3188c096b0c9182caeba7b9340c64f6b45f1fee331c08b877b
+ languageName: node
+ linkType: hard
+
"react-native-permissions@npm:^5.4.2":
version: 5.4.2
resolution: "react-native-permissions@npm:5.4.2"
@@ -20852,21 +20584,6 @@ __metadata:
languageName: node
linkType: hard
-"readable-stream@npm:~2.3.6":
- version: 2.3.8
- resolution: "readable-stream@npm:2.3.8"
- dependencies:
- core-util-is: "npm:~1.0.0"
- inherits: "npm:~2.0.3"
- isarray: "npm:~1.0.0"
- process-nextick-args: "npm:~2.0.0"
- safe-buffer: "npm:~5.1.1"
- string_decoder: "npm:~1.1.1"
- util-deprecate: "npm:~1.0.1"
- checksum: 10/8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4
- languageName: node
- linkType: hard
-
"readdirp@npm:^4.0.1":
version: 4.1.2
resolution: "readdirp@npm:4.1.2"
@@ -21092,13 +20809,6 @@ __metadata:
languageName: node
linkType: hard
-"reselect@npm:^4.1.7":
- version: 4.1.8
- resolution: "reselect@npm:4.1.8"
- checksum: 10/199984d9872f71cd207f4aa6e6fd2bd48d95154f7aa9b3aee3398335f39f5491059e732f28c12e9031d5d434adab2c458dc8af5afb6564d0ad37e1644445e09c
- languageName: node
- linkType: hard
-
"resolve-cwd@npm:^3.0.0":
version: 3.0.0
resolution: "resolve-cwd@npm:3.0.0"
@@ -21476,7 +21186,7 @@ __metadata:
languageName: node
linkType: hard
-"rxjs@npm:7.8.2, rxjs@npm:^7.5.5, rxjs@npm:~7.8.2":
+"rxjs@npm:7.8.2, rxjs@npm:^7.5.5, rxjs@npm:^7.8.2, rxjs@npm:~7.8.2":
version: 7.8.2
resolution: "rxjs@npm:7.8.2"
dependencies:
@@ -21505,13 +21215,6 @@ __metadata:
languageName: node
linkType: hard
-"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1":
- version: 5.1.2
- resolution: "safe-buffer@npm:5.1.2"
- checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a
- languageName: node
- linkType: hard
-
"safe-push-apply@npm:^1.0.0":
version: 1.0.0
resolution: "safe-push-apply@npm:1.0.0"
@@ -22620,15 +22323,6 @@ __metadata:
languageName: node
linkType: hard
-"string_decoder@npm:~1.1.1":
- version: 1.1.1
- resolution: "string_decoder@npm:1.1.1"
- dependencies:
- safe-buffer: "npm:~5.1.0"
- checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4
- languageName: node
- linkType: hard
-
"stringify-entities@npm:^4.0.0":
version: 4.0.4
resolution: "stringify-entities@npm:4.0.4"
@@ -23083,16 +22777,6 @@ __metadata:
languageName: node
linkType: hard
-"through2@npm:^2.0.1":
- version: 2.0.5
- resolution: "through2@npm:2.0.5"
- dependencies:
- readable-stream: "npm:~2.3.6"
- xtend: "npm:~4.0.1"
- checksum: 10/cd71f7dcdc7a8204fea003a14a433ef99384b7d4e31f5497e1f9f622b3cf3be3691f908455f98723bdc80922a53af7fa10c3b7abbe51c6fd3d536dbc7850e2c4
- languageName: node
- linkType: hard
-
"through@npm:>=2.2.7 <3, through@npm:^2.3.6":
version: 2.3.8
resolution: "through@npm:2.3.8"
@@ -23891,7 +23575,7 @@ __metadata:
languageName: node
linkType: hard
-"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
+"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2
@@ -24612,7 +24296,7 @@ __metadata:
languageName: node
linkType: hard
-"xtend@npm:^4.0.0, xtend@npm:~4.0.1":
+"xtend@npm:^4.0.0":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a