From 3a10e31b694959c2777d4fca8c98ce3312a16950 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:58:59 +0100 Subject: [PATCH 1/8] Overwrite Track, PeerWithTracks and usePeers --- .../fishjam-chat/app/livestream/streamer.tsx | 7 +--- .../fishjam-chat/components/VideosGrid.tsx | 7 +--- packages/mobile-client/src/index.ts | 37 +++++++++++++++++-- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/examples/mobile-client/fishjam-chat/app/livestream/streamer.tsx b/examples/mobile-client/fishjam-chat/app/livestream/streamer.tsx index f207816a..83c62992 100644 --- a/examples/mobile-client/fishjam-chat/app/livestream/streamer.tsx +++ b/examples/mobile-client/fishjam-chat/app/livestream/streamer.tsx @@ -15,11 +15,6 @@ import { changeFishjamId } from "../../utils/fishjamIdStore"; import { Button } from "../../components"; import { BrandColors } from "../../utils/Colors"; -// Helper type for MediaStream with toURL method from react-native-webrtc -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} - export default function LivestreamStreamerScreen() { const { roomName } = useLocalSearchParams<{ roomName: string; @@ -120,7 +115,7 @@ export default function LivestreamStreamerScreen() { {cameraStream ? ( diff --git a/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx b/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx index 29c0a93e..6b4eceea 100644 --- a/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx +++ b/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx @@ -68,14 +68,9 @@ export const parsePeersToTracks = ( ...remotePeers.flatMap((peer) => createGridTracksFromPeer(peer, false)), ]; -//TODO: FCE-2487 remove it when MediaStream will be updated -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} const GridTrackItem = ({ peer, index }: { peer: GridTrack; index: number }) => { - //TODO: FCE-2487 overwrite Track to include MediaStream from react-native-webrtc - const streamURL = peer.track?.stream ? (peer.track.stream as MediaStreamWithURL).toURL() : null; + const streamURL = peer.track?.stream ? peer.track.stream.toURL() : null; return ( diff --git a/packages/mobile-client/src/index.ts b/packages/mobile-client/src/index.ts index ae284937..e0afc4ca 100644 --- a/packages/mobile-client/src/index.ts +++ b/packages/mobile-client/src/index.ts @@ -8,8 +8,12 @@ import React from 'react'; import { FishjamProvider as ReactClientFishjamProvider, type FishjamProviderProps as ReactClientFishjamProviderProps, + type Track as ReactClientTrack, + type PeerWithTracks as ReactClientPeerWithTracks, } from '@fishjam-cloud/react-client'; +import type { MediaStream } from '@fishjam-cloud/react-native-webrtc'; + export { RTCView, ScreenCapturePickerView, @@ -28,7 +32,6 @@ export { useCustomSource, useLivestreamStreamer, useLivestreamViewer, - usePeers, useSandbox, useScreenShare, useUpdatePeerMetadata, @@ -36,6 +39,21 @@ export { Variant, } from '@fishjam-cloud/react-client'; +import { usePeers as usePeersReactClient } from '@fishjam-cloud/react-client'; + +export function usePeers, ServerMetadata = Record>(): { + localPeer: PeerWithTracks | null; + remotePeers: PeerWithTracks[]; + peers: PeerWithTracks[]; +} { + const result = usePeersReactClient(); + return { + localPeer: result.localPeer as PeerWithTracks | null, + remotePeers: result.remotePeers as PeerWithTracks[], + peers: result.peers as PeerWithTracks[], + }; +} + export type { UseInitializeDevicesParams, JoinRoomConfig, @@ -44,7 +62,6 @@ export type { UseLivestreamStreamerResult, ConnectViewerConfig, UseLivestreamViewerResult, - PeerWithTracks, RoomType, UseSandboxProps, BandwidthLimits, @@ -60,7 +77,6 @@ export type { PersistLastDeviceHandlers, SimulcastBandwidthLimits, StreamConfig, - Track, TrackId, TrackMiddleware, TracksMiddleware, @@ -83,3 +99,18 @@ export function FishjamProvider(props: FishjamProviderProps) { persistLastDevice: false, }); } + +export type Track = Omit & { stream: MediaStream | null }; + +export type PeerWithTracks = Omit< + ReactClientPeerWithTracks, + 'tracks' | 'cameraTrack' | 'microphoneTrack' | 'screenShareVideoTrack' | 'screenShareAudioTrack' | 'customVideoTracks' | 'customAudioTracks' +> & { + tracks: Track[]; + cameraTrack?: Track; + microphoneTrack?: Track; + screenShareVideoTrack?: Track; + screenShareAudioTrack?: Track; + customVideoTracks: Track[]; + customAudioTracks: Track[]; +}; From a476b7508b66029d3a7fde9d90136a149c170ebe Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:11:34 +0100 Subject: [PATCH 2/8] Overwrite streams in useCamera and useMicrophone --- packages/mobile-client/src/index.ts | 32 ++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/mobile-client/src/index.ts b/packages/mobile-client/src/index.ts index e0afc4ca..802a0024 100644 --- a/packages/mobile-client/src/index.ts +++ b/packages/mobile-client/src/index.ts @@ -12,7 +12,7 @@ import { type PeerWithTracks as ReactClientPeerWithTracks, } from '@fishjam-cloud/react-client'; -import type { MediaStream } from '@fishjam-cloud/react-native-webrtc'; +import type { MediaStream as RNMediaStream } from '@fishjam-cloud/react-native-webrtc'; export { RTCView, @@ -24,9 +24,7 @@ export { } from '@fishjam-cloud/react-native-webrtc'; export { - useCamera, useInitializeDevices, - useMicrophone, InitializeDevicesSettings, useConnection, useCustomSource, @@ -39,7 +37,11 @@ export { Variant, } from '@fishjam-cloud/react-client'; -import { usePeers as usePeersReactClient } from '@fishjam-cloud/react-client'; +import { + usePeers as usePeersReactClient, + useCamera as useCameraReactClient, + useMicrophone as useMicrophoneReactClient, +} from '@fishjam-cloud/react-client'; export function usePeers, ServerMetadata = Record>(): { localPeer: PeerWithTracks | null; @@ -54,6 +56,26 @@ export function usePeers, ServerMetadata }; } +export function useCamera(): Omit, 'cameraStream'> & { + cameraStream: RNMediaStream | null; +} { + const result = useCameraReactClient(); + return { + ...result, + cameraStream: result.cameraStream as RNMediaStream | null, + }; +} + +export function useMicrophone(): Omit, 'microphoneStream'> & { + microphoneStream: RNMediaStream | null; +} { + const result = useMicrophoneReactClient(); + return { + ...result, + microphoneStream: result.microphoneStream as RNMediaStream | null, + }; +} + export type { UseInitializeDevicesParams, JoinRoomConfig, @@ -100,7 +122,7 @@ export function FishjamProvider(props: FishjamProviderProps) { }); } -export type Track = Omit & { stream: MediaStream | null }; +export type Track = Omit & { stream: RNMediaStream | null }; export type PeerWithTracks = Omit< ReactClientPeerWithTracks, From 639901e1fb4b1ff4d3dc9cac13a829db24c12e8d Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:26:43 +0100 Subject: [PATCH 3/8] Overwrite functions fro livestreaming --- .../fishjam-chat/app/livestream/viewer.tsx | 7 +-- packages/mobile-client/src/index.ts | 54 +++++++++++++++++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx b/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx index e0965cf5..cb35821c 100644 --- a/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx +++ b/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx @@ -9,11 +9,6 @@ import { } from "@fishjam-cloud/mobile-client"; import { BrandColors } from "../../utils/Colors"; -// Helper type for MediaStream with toURL method from react-native-webrtc -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} - export default function LivestreamViewerScreen() { const { roomName } = useLocalSearchParams<{ roomName: string; @@ -52,7 +47,7 @@ export default function LivestreamViewerScreen() { {stream ? ( ) : ( diff --git a/packages/mobile-client/src/index.ts b/packages/mobile-client/src/index.ts index 802a0024..3e66b522 100644 --- a/packages/mobile-client/src/index.ts +++ b/packages/mobile-client/src/index.ts @@ -28,8 +28,6 @@ export { InitializeDevicesSettings, useConnection, useCustomSource, - useLivestreamStreamer, - useLivestreamViewer, useSandbox, useScreenShare, useUpdatePeerMetadata, @@ -41,6 +39,8 @@ import { usePeers as usePeersReactClient, useCamera as useCameraReactClient, useMicrophone as useMicrophoneReactClient, + useLivestreamViewer as useLivestreamViewerReactClient, + useLivestreamStreamer as useLivestreamStreamerReactClient, } from '@fishjam-cloud/react-client'; export function usePeers, ServerMetadata = Record>(): { @@ -76,14 +76,33 @@ export function useMicrophone(): Omit, 'stream'> & { + stream: RNMediaStream | null; +} { + const result = useLivestreamViewerReactClient(); + return { + ...result, + stream: result.stream as RNMediaStream | null, + }; +} + +export function useLivestreamStreamer(): Omit, 'connect'> & { + connect: (config: ConnectStreamerConfig, urlOverride?: string) => Promise; +} { + const result = useLivestreamStreamerReactClient(); + return { + ...result, + connect: (config: ConnectStreamerConfig, urlOverride?: string) => { + return result.connect(config as unknown as ReactClientConnectStreamerConfig, urlOverride); + }, + }; +} + export type { UseInitializeDevicesParams, JoinRoomConfig, - ConnectStreamerConfig, - StreamerInputs, UseLivestreamStreamerResult, ConnectViewerConfig, - UseLivestreamViewerResult, RoomType, UseSandboxProps, BandwidthLimits, @@ -113,6 +132,31 @@ export type { TrackBandwidthLimit, } from '@fishjam-cloud/react-client'; +import type { + UseLivestreamViewerResult as ReactClientUseLivestreamViewerResult, + StreamerInputs as ReactClientStreamerInputs, + ConnectStreamerConfig as ReactClientConnectStreamerConfig, +} from '@fishjam-cloud/react-client'; + +export type UseLivestreamViewerResult = Omit & { + stream: RNMediaStream | null; +}; + +export type StreamerInputs = + | { + video: RNMediaStream; + audio?: RNMediaStream | null; + } + | { + video?: null; + audio: RNMediaStream; + }; + +export type ConnectStreamerConfig = { + inputs: StreamerInputs; + token: string; +}; + // persistLastDevice is not supported on mobile export type FishjamProviderProps = Omit; export function FishjamProvider(props: FishjamProviderProps) { From 269a29a3b3b59c1cd44fd038928c0d941bf7a318 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:37:24 +0100 Subject: [PATCH 4/8] Remove remaining MediaStreamWithURL --- .../fishjam-chat/app/room/preview.tsx | 16 +++------------- .../components/VideosGridItem.tsx | 7 +------ .../components/FishjamPlayerStreamer.tsx | 7 +------ .../components/FishjamPlayerViewer.tsx | 6 +----- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/examples/mobile-client/fishjam-chat/app/room/preview.tsx b/examples/mobile-client/fishjam-chat/app/room/preview.tsx index b43e1119..b6d8646d 100644 --- a/examples/mobile-client/fishjam-chat/app/room/preview.tsx +++ b/examples/mobile-client/fishjam-chat/app/room/preview.tsx @@ -14,16 +14,10 @@ import { import { Button, InCallButton, NoCameraView } from "../../components"; import { BrandColors } from "../../utils/Colors"; -// Helper type for MediaStream with toURL method from react-native-webrtc -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} - export default function PreviewScreen() { - const { roomName, userName, videoRoomEnv } = useLocalSearchParams<{ + const { roomName, userName } = useLocalSearchParams<{ roomName: string; userName: string; - videoRoomEnv: string; }>(); const { getSandboxPeerToken } = useSandbox(); @@ -103,10 +97,6 @@ export default function PreviewScreen() { } }, [getSandboxPeerToken, roomName, joinRoom, userName]); - const streamURL = cameraStream - ? (cameraStream as MediaStreamWithURL).toURL() - : null; - return ( {error && {error}} @@ -119,9 +109,9 @@ export default function PreviewScreen() { Initializing camera... - ) : streamURL ? ( + ) : cameraStream ? ( { //TODO: FCE-2487 overwrite Track to include MediaStream from react-native-webrtc - const streamURL = peer.track?.stream ? (peer.track.stream as MediaStreamWithURL).toURL() : null; + const streamURL = peer.track?.stream ? peer.track.stream.toURL() : null; return ( diff --git a/examples/mobile-client/video-player/components/FishjamPlayerStreamer.tsx b/examples/mobile-client/video-player/components/FishjamPlayerStreamer.tsx index 2fd1fff1..ecc9418c 100644 --- a/examples/mobile-client/video-player/components/FishjamPlayerStreamer.tsx +++ b/examples/mobile-client/video-player/components/FishjamPlayerStreamer.tsx @@ -2,11 +2,6 @@ import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; import { useInitializeDevices, useCamera, useLivestreamStreamer, useMicrophone, useSandbox, RTCView } from "@fishjam-cloud/mobile-client" import { useEffect } from "react"; -// Helper type for MediaStream with toURL method from react-native-webrtc -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} - export const FishjamPlayerStreamer = ({ roomName }: { roomName: string }) => { const { getSandboxLivestream } = useSandbox(); @@ -68,7 +63,7 @@ export const FishjamPlayerStreamer = ({ roomName }: { roomName: string }) => { diff --git a/examples/mobile-client/video-player/components/FishjamPlayerViewer.tsx b/examples/mobile-client/video-player/components/FishjamPlayerViewer.tsx index a8e9bff2..72c397c2 100644 --- a/examples/mobile-client/video-player/components/FishjamPlayerViewer.tsx +++ b/examples/mobile-client/video-player/components/FishjamPlayerViewer.tsx @@ -2,10 +2,6 @@ import { View, StyleSheet, Text, ActivityIndicator } from "react-native"; import { useSandbox, useLivestreamViewer, RTCView } from '@fishjam-cloud/mobile-client'; import { useEffect } from "react"; -interface MediaStreamWithURL extends MediaStream { - toURL(): string; -} - export const FishjamPlayerViewer = ({ roomName }: { roomName: string }) => { const { getSandboxViewerToken } = useSandbox(); const { connect, disconnect, stream } = useLivestreamViewer(); @@ -31,7 +27,7 @@ export const FishjamPlayerViewer = ({ roomName }: { roomName: string }) => { <> From 7d185b5c75d7072741d20d29341a2b532e4add96 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:17:08 +0100 Subject: [PATCH 5/8] Fix video-player example --- examples/mobile-client/video-player/app.json | 1 + examples/mobile-client/video-player/index.js | 6 ++++++ examples/mobile-client/video-player/package.json | 1 + 3 files changed, 8 insertions(+) create mode 100644 examples/mobile-client/video-player/index.js diff --git a/examples/mobile-client/video-player/app.json b/examples/mobile-client/video-player/app.json index cfdf29b3..ab709c83 100644 --- a/examples/mobile-client/video-player/app.json +++ b/examples/mobile-client/video-player/app.json @@ -3,6 +3,7 @@ "name": "video-player", "slug": "video-player", "version": "1.0.0", + "main": "index.js", "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "videoplayer", diff --git a/examples/mobile-client/video-player/index.js b/examples/mobile-client/video-player/index.js new file mode 100644 index 00000000..4eede251 --- /dev/null +++ b/examples/mobile-client/video-player/index.js @@ -0,0 +1,6 @@ +import { registerRootComponent } from 'expo'; + +import App from './App'; + +registerRootComponent(App); + diff --git a/examples/mobile-client/video-player/package.json b/examples/mobile-client/video-player/package.json index ed78e715..dac5cdf6 100644 --- a/examples/mobile-client/video-player/package.json +++ b/examples/mobile-client/video-player/package.json @@ -1,5 +1,6 @@ { "name": "video-player", + "main": "index.js", "version": "1.0.0", "scripts": { "start": "expo start", From 03eb4cf681b88534d5a4421740db79c1338d49d4 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:25:24 +0100 Subject: [PATCH 6/8] Small fix from copilot review --- examples/mobile-client/fishjam-chat/app/(tabs)/room.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mobile-client/fishjam-chat/app/(tabs)/room.tsx b/examples/mobile-client/fishjam-chat/app/(tabs)/room.tsx index 1acd314f..854338c9 100644 --- a/examples/mobile-client/fishjam-chat/app/(tabs)/room.tsx +++ b/examples/mobile-client/fishjam-chat/app/(tabs)/room.tsx @@ -97,7 +97,7 @@ export default function RoomScreen() { Keyboard.dismiss(); router.push({ pathname: "/room/preview", - params: { roomName, userName: displayName, videoRoomEnv }, + params: { roomName, userName: displayName}, }); } catch (e) { const message = From e005128460c509e7637117454dbe91305156f451 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:56:46 +0100 Subject: [PATCH 7/8] Improve types override --- packages/mobile-client/src/index.ts | 66 +++++++++-------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/packages/mobile-client/src/index.ts b/packages/mobile-client/src/index.ts index 3e66b522..957beaa2 100644 --- a/packages/mobile-client/src/index.ts +++ b/packages/mobile-client/src/index.ts @@ -56,47 +56,23 @@ export function usePeers, ServerMetadata }; } -export function useCamera(): Omit, 'cameraStream'> & { - cameraStream: RNMediaStream | null; -} { - const result = useCameraReactClient(); - return { - ...result, - cameraStream: result.cameraStream as RNMediaStream | null, - }; -} +export const useCamera = useCameraReactClient as () => Omit< ReturnType, 'cameraStream' > & { cameraStream: RNMediaStream | null; }; -export function useMicrophone(): Omit, 'microphoneStream'> & { +export const useMicrophone = useMicrophoneReactClient as () => Omit< ReturnType, 'microphoneStream' > & { microphoneStream: RNMediaStream | null; -} { - const result = useMicrophoneReactClient(); - return { - ...result, - microphoneStream: result.microphoneStream as RNMediaStream | null, - }; -} +}; -export function useLivestreamViewer(): Omit, 'stream'> & { +export const useLivestreamViewer = useLivestreamViewerReactClient as () => Omit< ReturnType, 'stream' > & { stream: RNMediaStream | null; -} { - const result = useLivestreamViewerReactClient(); - return { - ...result, - stream: result.stream as RNMediaStream | null, - }; -} +}; -export function useLivestreamStreamer(): Omit, 'connect'> & { - connect: (config: ConnectStreamerConfig, urlOverride?: string) => Promise; -} { - const result = useLivestreamStreamerReactClient(); - return { - ...result, - connect: (config: ConnectStreamerConfig, urlOverride?: string) => { - return result.connect(config as unknown as ReactClientConnectStreamerConfig, urlOverride); - }, +export const useLivestreamStreamer = + useLivestreamStreamerReactClient as () => Omit< + ReturnType, + 'connect' + > & { + connect: (config: ConnectStreamerConfig, urlOverride?: string) => Promise; }; -} export type { UseInitializeDevicesParams, @@ -142,19 +118,15 @@ export type UseLivestreamViewerResult = Omit & { + audio?: RNMediaStream; +} | Omit & { + video: RNMediaStream; + audio?: RNMediaStream | null; +}; + +export type ConnectStreamerConfig = Omit & { inputs: StreamerInputs; - token: string; }; // persistLastDevice is not supported on mobile From e3a2609e020bd61488cd694e9329be4eb314f880 Mon Sep 17 00:00:00 2001 From: Anna Olak <15946812+anna1901@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:00:29 +0100 Subject: [PATCH 8/8] Add mirror to RTCView in rooms and livestream --- examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx | 1 + examples/mobile-client/fishjam-chat/components/VideosGrid.tsx | 1 + .../minimal-react-native/components/VideosGridItem.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx b/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx index cb35821c..f11f221d 100644 --- a/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx +++ b/examples/mobile-client/fishjam-chat/app/livestream/viewer.tsx @@ -49,6 +49,7 @@ export default function LivestreamViewerScreen() { style={styles.rtcView} streamURL={stream.toURL()} objectFit="contain" + mirror={true} /> ) : ( diff --git a/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx b/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx index 4c22e5e0..3110337f 100644 --- a/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx +++ b/examples/mobile-client/fishjam-chat/components/VideosGrid.tsx @@ -94,6 +94,7 @@ const GridTrackItem = ({ peer, index }: { peer: GridTrack; index: number }) => { streamURL={streamURL} objectFit="cover" style={styles.video} + mirror={true} /> ) : ( diff --git a/examples/mobile-client/minimal-react-native/components/VideosGridItem.tsx b/examples/mobile-client/minimal-react-native/components/VideosGridItem.tsx index 94d7ac3c..d83f0c2c 100644 --- a/examples/mobile-client/minimal-react-native/components/VideosGridItem.tsx +++ b/examples/mobile-client/minimal-react-native/components/VideosGridItem.tsx @@ -4,7 +4,6 @@ import { GridTrack } from '../types'; import React from 'react'; export const VideosGridItem = ({ peer }: { peer: GridTrack }) => { - //TODO: FCE-2487 overwrite Track to include MediaStream from react-native-webrtc const streamURL = peer.track?.stream ? peer.track.stream.toURL() : null; return ( @@ -19,6 +18,7 @@ export const VideosGridItem = ({ peer }: { peer: GridTrack }) => { streamURL={streamURL} objectFit="cover" style={styles.videoContent} + mirror={true} /> ) : (