Skip to content
This repository was archived by the owner on Feb 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions internal/fishjam-chat/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@
"@fishjam-cloud/react-native-client",
{
"android": {
"enableForegroundService": true
"enableForegroundService": true,
"supportsPictureInPicture": true
},
"ios": {
"enableScreensharing": true,
"enableVoIPBackgroundMode": true
"enableVoIPBackgroundMode": true,
"supportsPictureInPicture": true
}
}
],
Expand Down
22 changes: 14 additions & 8 deletions internal/fishjam-chat/components/VideosGrid/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PeerMetadata } from '../../types/metadata';
import NoCameraView from '../NoCameraView';
import { GridTrack, GridTrackItem } from './GridTrackItem';
import { parsePeersToTracks } from './parsePeersToTracks';
import { PipContainerView } from '@fishjam-cloud/react-native-client';

const ListFooterComponent = () => <View style={{ height: 60 }} />;

Expand Down Expand Up @@ -39,14 +40,19 @@ export default function VideosGrid({
);

return (
<FlatList<GridTrack>
data={videoTracks}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={styles.contentContainerStyle}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
<PipContainerView
allowsCameraInBackground
startAutomatically
stopAutomatically>
<FlatList<GridTrack>
data={videoTracks}
keyExtractor={keyExtractor}
renderItem={renderItem}
contentContainerStyle={styles.contentContainerStyle}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
</PipContainerView>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.fishjamcloud.client
import com.fishjamcloud.client.models.ReconnectConfig
import timber.log.Timber
import java.util.Timer
import java.util.concurrent.CopyOnWriteArrayList
import kotlin.concurrent.schedule

interface ReconnectionManagerListener {
Expand Down Expand Up @@ -31,7 +32,7 @@ internal class ReconnectionManager(
private var reconnectConfig: ReconnectConfig = ReconnectConfig(),
private val connect: () -> Unit
) {
private val listeners = mutableListOf<ReconnectionManagerListener>()
private val listeners = CopyOnWriteArrayList<ReconnectionManagerListener>()
private var reconnectAttempts = 0
private var reconnectionStatus = ReconnectionStatus.Idle

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import kotlinx.coroutines.sync.withLock
import org.webrtc.*
import timber.log.Timber
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList

internal class PeerConnectionManager(
private val peerConnectionFactory: PeerConnectionFactoryWrapper
) : PeerConnection.Observer {
private val listeners = mutableListOf<PeerConnectionListener>()
private val listeners = CopyOnWriteArrayList<PeerConnectionListener>()

fun addListener(listener: PeerConnectionListener) {
listeners.add(listener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import com.fishjamcloud.client.utils.gson
import com.fishjamcloud.client.utils.serializeToMap
import fishjam.media_events.Shared
import timber.log.Timber
import java.util.concurrent.CopyOnWriteArrayList

internal class RTCEngineCommunication {
private val listeners = mutableListOf<RTCEngineListener>()
private val listeners = CopyOnWriteArrayList<RTCEngineListener>()

fun addListener(listener: RTCEngineListener) {
listeners.add(listener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,7 @@ extension FishjamClientInternal: RTCEngineListener {
}

func onTrackUpdated(endpointId: String, trackId: String, metadata: Metadata) {
guard let track = getTrack(trackId: trackId) else {
guard let track = getTrackWithRtcEngineId(trackId: trackId) else {
sdkLogger.error("Failed to process TrackUpdated event: Track context not found: \(trackId)")
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public protocol CameraCapturerDeviceChangedListener: AnyObject {
class CameraCapturer: VideoCapturer {
private let videoParameters: VideoParameters
private let capturer: RTCCameraVideoCapturer
private let captureSession: AVCaptureSession
internal var isFront: Bool = true
private(set) var device: AVCaptureDevice? {
didSet {
Expand All @@ -19,7 +20,8 @@ class CameraCapturer: VideoCapturer {

init(videoParameters: VideoParameters, delegate: RTCVideoCapturerDelegate, deviceId: String? = nil) {
self.videoParameters = videoParameters
self.capturer = RTCCameraVideoCapturer(delegate: delegate, captureSession: AVCaptureSession())
self.captureSession = AVCaptureSession()
self.capturer = RTCCameraVideoCapturer(delegate: delegate, captureSession: captureSession)
let devices = RTCCameraVideoCapturer.captureDevices()

if let newDevice = devices.first(where: { $0.uniqueID == deviceId }) {
Expand Down Expand Up @@ -113,4 +115,29 @@ class CameraCapturer: VideoCapturer {
}
)
}

public func isMultitaskingCameraAccessSupported() -> Bool {
if #available(iOS 16.0, *) {
return captureSession.isMultitaskingCameraAccessSupported
}
return false
}

public func setMultitaskingCameraAccessEnabled(_ enabled: Bool) -> Bool {
if #available(iOS 16.0, *) {
guard captureSession.isMultitaskingCameraAccessSupported else {
return false
}
captureSession.isMultitaskingCameraAccessEnabled = enabled
return true
}
return false
}

public func isMultitaskingCameraAccessEnabled() -> Bool {
if #available(iOS 16.0, *) {
return captureSession.isMultitaskingCameraAccessEnabled
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,16 @@ public class LocalCameraTrack: LocalVideoTrack, LocalTrack {
public static func getCaptureDevices() -> [AVCaptureDevice] {
return RTCCameraVideoCapturer.captureDevices()
}

public func isMultitaskingCameraAccessSupported() -> Bool {
return capturer.isMultitaskingCameraAccessSupported()
}

public func setMultitaskingCameraAccessEnabled(_ enabled: Bool) -> Bool {
return capturer.setMultitaskingCameraAccessEnabled(enabled)
}

public func isMultitaskingCameraAccessEnabled() -> Bool {
return capturer.isMultitaskingCameraAccessEnabled()
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import WebRTC

open class Track: Equatable {
internal var mediaTrack: RTCMediaStreamTrack?
public internal(set) var mediaTrack: RTCMediaStreamTrack?
internal var sendEncodings: [RTCRtpEncodingParameters] = []
let endpointId: String
internal var rtcEngineId: String?
Expand Down
Loading
Loading