diff --git a/.gitmodules b/.gitmodules index 6c7a308b..6bd7ad0c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "packages/web-client-sdk"] path = packages/web-client-sdk url = https://github.com/fishjam-cloud/web-client-sdk.git -[submodule "packages/mobile-client-sdk"] - path = packages/mobile-client-sdk - url = https://github.com/fishjam-cloud/mobile-client-sdk.git [submodule "packages/js-server-sdk"] path = packages/js-server-sdk url = https://github.com/fishjam-cloud/js-server-sdk.git diff --git a/docs/explanation/public-livestreams.mdx b/docs/explanation/public-livestreams.mdx index bb990e11..0fb00ba3 100644 --- a/docs/explanation/public-livestreams.mdx +++ b/docs/explanation/public-livestreams.mdx @@ -139,21 +139,23 @@ Once you've created a viewer token, you can connect to a room using the Fishjam const viewerToken = ''; // ---cut--- - import { - LivestreamViewer, - useLivestreamViewer, - } from '@fishjam-cloud/react-native-client/livestream'; + import { useLivestreamViewer, RTCView } from '@fishjam-cloud/mobile-client'; - // ... + //TODO: FCE-2487 remove it when MediaStream will be updated + interface MediaStreamWithURL extends MediaStream { + toURL(): string; + } - const { connect, whepClientRef } = useLivestreamViewer(); + // Inside your component: + const { connect, stream } = useLivestreamViewer(); // ... await connect({ token: viewerToken }); - // Use `LivestreamViewer` to render the stream - + // Render the stream + const streamURL = stream ? (stream as MediaStreamWithURL).toURL() : null; + {streamURL && } ``` @@ -203,21 +205,23 @@ Once you've created a room of type `livestream` with the `public` flag enabled, const roomId = ''; // ---cut--- - import { - LivestreamViewer, - useLivestreamViewer, - } from '@fishjam-cloud/react-native-client/livestream'; + import { useLivestreamViewer, RTCView } from '@fishjam-cloud/mobile-client'; - // ... + //TODO: FCE-2487 remove it when MediaStream will be updated + interface MediaStreamWithURL extends MediaStream { + toURL(): string; + } - const { connect, whepClientRef } = useLivestreamViewer(); + // Inside your component: + const { connect, stream } = useLivestreamViewer(); // ... await connect({ streamId: roomId }); - // Use `LivestreamViewer` to render the stream - + // Render the stream + const streamURL = stream ? (stream as MediaStreamWithURL).toURL() : null; + {streamURL && } ``` diff --git a/docs/how-to/backend/server-setup.mdx b/docs/how-to/backend/server-setup.mdx index 77a75a88..84903f39 100644 --- a/docs/how-to/backend/server-setup.mdx +++ b/docs/how-to/backend/server-setup.mdx @@ -164,8 +164,7 @@ At any time you can terminate user's access by deleting the peer. #### Metadata -When creating a peer, you can also assign metadata to that peer, which can be read later with the [mobile SDK](../../how-to/react-native/metadata) -or [web SDK](../../how-to/react/metadata). This metadata can be only set when creating the peer and can't be updated later. +When creating a peer, you can also assign metadata to that peer, which can be read later with the [client SDK](../../how-to/client/metadata). This metadata can be only set when creating the peer and can't be updated later. diff --git a/docs/how-to/client/_category_.json b/docs/how-to/client/_category_.json new file mode 100644 index 00000000..e93ff99d --- /dev/null +++ b/docs/how-to/client/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Web & Mobile", + "position": 1, + "link": { + "type": "generated-index", + "description": "Learn how to integrate Fishjam into your web and mobile applications." + } +} + diff --git a/docs/how-to/client/background-streaming.mdx b/docs/how-to/client/background-streaming.mdx new file mode 100644 index 00000000..890f9072 --- /dev/null +++ b/docs/how-to/client/background-streaming.mdx @@ -0,0 +1,128 @@ +--- +sidebar_position: 13 +sidebar_label: "Background calls πŸ“±" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Background calls Mobile + +:::note +This guide is exclusively for **Mobile** (React Native) applications. +::: + +Both Android and iOS support calls running in the background, but they use different approaches: + +- **Android**: Uses foreground services to keep the app active in the background +- **iOS**: Uses CallKit integration to maintain VoIP calls in the background + +Below is configuration required to make it work: + + + + + +You need to modify `app.json` file and add our plugin: + +```json +{ + "expo": { + ... + "plugins": { + ... + [ + "@fishjam-cloud/mobile-client", + { + "android": { + "enableForegroundService": true + }, + "ios": { + "enableVoIPBackgroundMode": true + } + } + ], + ... + } + } +} +``` + + + + +**Android Configuration** + +You need to add the following service to `AndroidManifest.xml`: + +```xml title='AndroidManifest.xml' + + ... + + ... + + + +``` + +**iOS Configuration** + +You need to add VoIP background mode in `Info.plist`: + +```xml title='Info.plist' +UIBackgroundModes + + voip + +``` + + + + +## Usage + + + + + +You can use [`useForegroundService`](../../api/mobile/variables/useForegroundService) hook to handle how foreground service behaves on Android. + +:::important[Permissions] + +If you want to use [`enableCamera`](../../api/mobile/type-aliases/ForegroundServiceConfig#enablecamera) or [`enableMicrophone`](../../api/mobile/type-aliases/ForegroundServiceConfig#enablemicrophone), +user must first grant permission for this resource. [`useForegroundService`](../../api/mobile/variables/useForegroundService) will check if permission is +granted and only then allow to start a service. + +::: + +```tsx +import { + useCamera, + useMicrophone, +} from "@fishjam-cloud/mobile-client"; + +const { isCameraOn } = useCamera(); +const { isMicrophoneOn } = useMicrophone(); + + +``` + + + + +On iOS, background calls are achieved through CallKit integration. To enable background streaming on iOS: + +1. Enable VoIP background mode by setting `enableVoIPBackgroundMode: true` in the plugin configuration or adding the VoIP background mode to your `Info.plist` +2. The SDK will automatically handle CallKit integration for maintaining background audio/video sessions + +:::note +CallKit integration is handled automatically by the SDK when VoIP background mode is enabled. The call will appear in the iOS call history and can be managed through the native phone interface. +::: + + + + +## See Also + +For an enhanced user experience when your app is in the background, consider enabling [Picture in Picture](./picture-in-picture), which allows users to see video content in a floating window while using other apps. + diff --git a/docs/how-to/client/connecting.mdx b/docs/how-to/client/connecting.mdx new file mode 100644 index 00000000..94b90659 --- /dev/null +++ b/docs/how-to/client/connecting.mdx @@ -0,0 +1,173 @@ +--- +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Connecting + +This article will guide you through the process of connecting to a Fishjam room. + +## Getting URL and token + +In order to connect, you need to obtain a **Peer Token** (the token that will authenticate the peer in +your Room). + + + + + +Once you create your account on [Fishjam](https://fishjam.io), you will have access to the Sandbox environment as part of the Mini Jar plan. +While using the Sandbox environment, [you can use the Sandbox API](../features/sandbox-api-testing) to generate peer tokens for testing or development purposes. +This is basically a service that will create a Room, add your app as +the Room's Peer, and return the token required to use that Room. + + + + +```ts +import { useSandbox } from "@fishjam-cloud/react-client"; +const roomName = "room"; +const peerName = "user"; +// ---cut--- + +// The `useSandbox` hook gets the fishjamId from FishjamProvider +// It will work ONLY with the FISHJAM_ID of the Sandbox environment +const { getSandboxPeerToken } = useSandbox(); +const peerToken = await getSandboxPeerToken(roomName, peerName); +``` + + + + +```ts +import { useSandbox } from "@fishjam-cloud/mobile-client"; +const roomName = "room"; +const peerName = "user"; +// ---cut--- + +// The `useSandbox` hook gets the fishjamId from FishjamProvider +// It will work ONLY with the FISHJAM_ID of the Sandbox environment +const { getSandboxPeerToken } = useSandbox(); +const peerToken = await getSandboxPeerToken(roomName, peerName); +``` + + + + + + + +For the production app, you need to implement your own backend service that will provide the user with a **Peer Token**. To do that, +follow our [server setup instructions](../backend/server-setup). + + + + +## Connecting + +Use the [`useConnection`](../../api/web/functions/useConnection) hook to get +the [`joinRoom`](../../api/web/functions/useConnection#joinroom) function. + + + + +```tsx +const PEER_TOKEN = "some-peer-token"; +// ---cut-before--- +import { useConnection, useSandbox } from "@fishjam-cloud/react-client"; +import React, { useCallback } from "react"; + +export function JoinRoomButton() { + const { joinRoom } = useConnection(); // [!code highlight] + // get the peer token from sandbox or your backend + const { getSandboxPeerToken } = useSandbox(); + + const onJoinRoomPress = useCallback(async () => { + // [!code highlight:5] + const peerToken = await getSandboxPeerToken("Room", "User"); + await joinRoom({ peerToken }); + }, [joinRoom]); + + return ; +} +``` + + + + +```tsx +import React, { useCallback } from "react"; +import { Button } from "react-native"; +import { useConnection, useSandbox } from "@fishjam-cloud/mobile-client"; + +export function JoinRoomButton() { + const { joinRoom } = useConnection(); // [!code highlight] + // fishjamId is provided through FishjamProvider + const { getSandboxPeerToken } = useSandbox(); + + const onPressJoin = useCallback(async () => { + // in production environment, get the peerToken from your backend + const peerToken = await getSandboxPeerToken("Room", "User"); + + await joinRoom({ peerToken }); // [!code highlight] + }, [joinRoom, getSandboxPeerToken]); + + return ; +} +``` + + + + +```tsx +import React, { useCallback } from "react"; +import { Button } from "react-native"; +import { useConnection } from "@fishjam-cloud/mobile-client"; + +export function LeaveRoomButton() { + const { leaveRoom } = useConnection(); // [!code highlight] + + const onPressLeave = useCallback(async () => { + await leaveRoom(); // [!code highlight] + }, [leaveRoom]); + + return + + ))} + + ); +} +``` + + + + +To select the desired camera, use the [`selectCamera`](../../api/mobile/functions/useCamera#selectcamera) method. +The list of the available camera devices is available via the [`cameraDevices`](../../api/mobile/functions/useCamera#cameradevices). + +```tsx +import React, { useCallback, useState } from "react"; +import { Button } from "react-native"; +import { useCamera } from "@fishjam-cloud/mobile-client"; + +export function FlipButton() { + const { cameraDevices, selectCamera } = useCamera(); // [!code highlight] + const [currentIndex, setCurrentIndex] = useState(0); + + const onPressFlipCamera = useCallback(() => { + if (cameraDevices.length === 0) return; + + // Cycle through available cameras + const nextIndex = (currentIndex + 1) % cameraDevices.length; + const nextCamera = cameraDevices[nextIndex]; + if (nextCamera) { + selectCamera(nextCamera.deviceId); // [!code highlight] + setCurrentIndex(nextIndex); + } + }, [cameraDevices, currentIndex, selectCamera]); + + return ; +} +``` + + + + +You can use [`toggleCamera`](../../api/mobile/functions/useCamera#togglecamera) to toggle the camera state, or use [`startCamera`](../../api/mobile/functions/useCamera#startcamera) and [`stopCamera`](../../api/mobile/functions/useCamera#stopcamera) for more explicit control. + +#### Using toggleCamera + +```tsx +import { Button } from "react-native"; +import React from "react"; +import { useCamera } from "@fishjam-cloud/mobile-client"; + +export function ToggleCameraButton() { + const { isCameraOn, toggleCamera } = useCamera(); // [!code highlight] + + return ( + + + + ); +} +``` diff --git a/docs/how-to/client/metadata.mdx b/docs/how-to/client/metadata.mdx new file mode 100644 index 00000000..0f96782b --- /dev/null +++ b/docs/how-to/client/metadata.mdx @@ -0,0 +1,212 @@ +--- +sidebar_position: 6 +title: "Metadata" +description: "How to use metadata" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +Alongside audio and video, it is possible to send additional metadata with each peer. Metadata is just +JSON that can contain arbitrary information. Its most common use is sending a user name associated with a peer. +However, it can be also used to send the peer's camera type, application information etc. + +:::info + +You can also set metadata on [the server side, when adding user to the room](../backend/server-setup#metadata). This metadata is persistent throughout its lifetime and is useful for attaching information that +can't be overwritten by the peer, like information about real user names or basic permission info. + +::: + +## Setting metadata when joining the room + +The `joinRoom` method from the `useConnection` hook has a `peerMetadata` parameter, that can be used for setting object metadata. + + + + +```tsx +const PEER_TOKEN = "some-peer-token"; +// ---cut--- +import { useConnection } from "@fishjam-cloud/react-client"; +import React, { useCallback } from "react"; + +type PeerMetadata = { + displayName: string; +}; + +export function JoinRoomButton() { + const { joinRoom } = useConnection(); // [!code highlight] + + const onJoinRoomPress = useCallback(async () => { + await joinRoom({ + peerToken: PEER_TOKEN, + peerMetadata: { displayName: "John Wick" }, // [!code highlight] + }); + }, [joinRoom]); + + return ; +} +``` + + + + +```tsx +const PEER_TOKEN = "some-peer-token"; +// ---cut--- +import React, { useCallback } from "react"; +import { Button } from "react-native"; +import { useConnection } from "@fishjam-cloud/mobile-client"; + +type PeerMetadata = { + displayName: string; +}; + +export function JoinRoomButton() { + const { joinRoom } = useConnection(); + + const onPressJoin = useCallback(async () => { + // Note: fishjamId is passed to FishjamProvider, not joinRoom + await joinRoom({ + peerToken: PEER_TOKEN, + peerMetadata: { displayName: "John Wick" }, // [!code highlight] + }); + }, [joinRoom]); + + return ; +} +``` + + + + +```tsx +import React, { useCallback } from "react"; +import { Button } from "react-native"; +import { useUpdatePeerMetadata } from "@fishjam-cloud/mobile-client"; + +type PeerMetadata = { + displayName: string; +}; + +export function UpdateNameButton() { + const { updatePeerMetadata } = useUpdatePeerMetadata(); // [!code highlight] + + const onPressUpdateName = useCallback(async () => { + await updatePeerMetadata({ displayName: "Thomas A. Anderson" }); // [!code highlight] + }, [updatePeerMetadata]); + + return ; -} -``` - -## Disconnecting - -In order to close connection, use the [`leaveRoom`](../../api/web/functions/useConnection#leaveroom) method -from [`useConnection`](../../api/web/functions/useConnection) hook. - -```tsx -import { useConnection } from "@fishjam-cloud/react-client"; -import React, { useCallback } from "react"; - -export function LeaveRoomButton() { - const { leaveRoom } = useConnection(); // [!code highlight] - - return ; -} -``` diff --git a/docs/how-to/react/installation.mdx b/docs/how-to/react/installation.mdx deleted file mode 100644 index ce903bb7..00000000 --- a/docs/how-to/react/installation.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -sidebar_position: 1 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Installation - -## 1. Install the package - -```bash npm2yarn -npm install @fishjam-cloud/react-client -``` - -## 2. Setup Fishjam context - -Wrap your app in our [`FishjamProvider`](../../api/web/functions/FishjamProvider) component. Get your Fishjam ID from [Fishjam Dashboard](https://fishjam.io/app) and pass it to the provider. - -```tsx -const App = () => { - return
Hello world
; -}; - -// ---cut--- -import React from "react"; -import ReactDOM from "react-dom/client"; -// import App from "./App"; -import { FishjamProvider } from "@fishjam-cloud/react-client"; - -// Check https://fishjam.io/app/ for your Fishjam ID -const FISHJAM_ID = "your-fishjam-id"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - // [!code highlight:5] - - - - - , -); -``` - -:::tip - -It's possible to have many independent Fishjam contexts in one app. -Just render many [`FishjamProvider`](../../api/web/functions/FishjamProvider) components and make sure they don't overlap. - -::: diff --git a/docs/how-to/react/list-other-peers.mdx b/docs/how-to/react/list-other-peers.mdx deleted file mode 100644 index ad5536df..00000000 --- a/docs/how-to/react/list-other-peers.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Display media of other peers - -To access data and media of other peers, use the [`usePeers`](../../api/web/functions/usePeers) hook. -It returns two properties, [`remotePeers`](../../api/web/functions/usePeers) and [`localPeer`](../../api/web/functions/usePeers). -They contain all the tracks of other peers and all the tracks of the local user, respectively. - -### Example of playing other peers' available media - -```tsx -import React, { FC } from "react"; - -const VideoRenderer: FC<{ stream?: MediaStream | null }> = (_) =>