Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Expo Example #2

Open
wants to merge 2 commits into
base: v1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions react-native-expo-app/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
[<img src="https://avatars.githubusercontent.com/u/42463376" alt="React Native WebRTC Examples" style="height: 6em;" />](https://github.com/react-native-webrtc/examples)

# React Native Expo App

This is a demo app of using WebRTC with React Native. It's built with Expo and uses a [custom development build](https://docs.expo.dev/develop/development-builds/introduction/).

<!-- insert table of screenshots -->

| Join | Outgoing call | Incoming call | On call |
| -------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| <img width="200px" src="./docs//join.png" alt="Join screen" /> | <img width="200px" src="./docs//outgoing-call.png" alt="Outgoing screen" /> | <img width="200px" src="./docs//incoming-call.png" atl="Incoming screen" /> | <img width="200px" src="./docs//on-call.png" alt="On call screen" /> |

## How to build

The easy way to build this app is to use Expo's build service (EAS). Although you can [build it locally](https://docs.expo.dev/build-reference/local-builds/) if you prefer.

Before you can build the app you need a few things:

1. An Expo account
2. Create a new project on EAS
3. Update `extra.eas.projectId` in `client/app.json` with your project ID

After you linked your project to EAS, you can run `yarn build:dev` on the `client` directory to start a dev build.

This will generate a custom Expo client that contains `react-native-webrtc` and other packages that are not available on the Expo Go app.

Take a look at `client/package.json` to see other build scripts.

## How to run

1. Clone this repo
2. Run `yarn install` on both `client` and `server` directories (in separate terminals)
3. Update `EXPO_PUBLIC_SIGNALING_SERVER_URL` in `client/.env` with your signaling server URL (this can be your computer's IP address)
4. Run `yarn start` on both `client` and `server` directories
5. Install the custom dev client on your mobile devices
6. Make sure the server and both devices are on the same network
7. Open the app on two devices and start a video call

## How it works

For the client, there are three files that you want to take a look at (the rest can be ignored):

1. `client/App.tsx` - This is the main entry point of the app. It contains the UI and links everything together.
2. `client/src/signaling.ts` - This contains a class that lets you create [signaling channels](https://webrtc.org/getting-started/peer-connections#signaling).
3. `client/src/useWebRTC.ts` - This is the most interesting file. It contains all the logic for creating and managing WebRTC connections.

The server is only used for signling. It's a simple [Socket.IO](https://socket.io/) server that relays messages between clients.
1 change: 1 addition & 0 deletions react-native-expo-app/client/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXPO_PUBLIC_SIGNALING_SERVER_URL=http://192.168.1.200:3000
68 changes: 68 additions & 0 deletions react-native-expo-app/client/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"env": {
"es2021": true,
"react-native/react-native": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/jsx-runtime",
// "plugin:import/recommended",
"plugin:import/typescript"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"react-hooks",
"react-native",
"import"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/no-explicit-any": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react-native/no-unused-styles": "off",
"react-native/no-single-element-style-arrays": "warn",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-var-requires": "off",
"react/prop-types": "off",
"no-undef": "off",
"no-useless-escape": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"react-native/no-raw-text": "warn",
"react/display-name": "off",
"react/no-unescaped-entities": "off",
"no-case-declarations": "off",
"import/no-unused-modules": [
1,
{
"unusedExports": true,
"ignoreExports": [
"./app",
"./redux",
"app.config.js"
]
}
]
},
"settings": {
"react": {
"version": "detect"
},
"import/resolver": {
"typescript": true,
"node": true
}
}
}
35 changes: 35 additions & 0 deletions react-native-expo-app/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/

# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo
7 changes: 7 additions & 0 deletions react-native-expo-app/client/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "es5",
"semi": false
}
52 changes: 52 additions & 0 deletions react-native-expo-app/client/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useRef, useState } from "react"
import IncomingView from "src/IncomingView"
import JoinView from "src/JoinView"
import MeetingView from "src/MeetingView"
import OutgoingView from "src/OutgoingView"
import { useWebRTC } from "src/useWebRTC"

const App = () => {
const localClientId = useRef(Math.floor(100000 + Math.random() * 900000).toString())
const [remoteClientId, setRemoteClientId] = useState("")
const {
callState,
localStream,
remoteStream,
makeCall,
acceptCall,
endCall,
isMicEnabled,
isCameraEnabled,
isFrontCamera,
toggleMute,
toggleCamera,
toggleCameraMode,
} = useWebRTC(localClientId.current, setRemoteClientId)

return callState === "idle" ? (
<JoinView
localClientId={localClientId.current}
onCall={(remoteClientIdArg) => {
setRemoteClientId(remoteClientIdArg)
makeCall(remoteClientIdArg)
}}
/>
) : callState === "incoming" ? (
<IncomingView remoteClientId={remoteClientId} onAnswer={acceptCall} />
) : callState === "outgoing" ? (
<OutgoingView remoteClientId={remoteClientId} onCancel={() => null} />
) : callState === "connected" ? (
<MeetingView
localStream={localStream.current}
remoteStream={remoteStream.current}
onLeave={endCall}
isCameraOn={isCameraEnabled}
isMuted={!isMicEnabled}
onSwitchCamera={toggleCameraMode}
onToggleCamera={toggleCamera}
onToggleMic={toggleMute}
isFrontCamera={isFrontCamera}
/>
) : null
}
export default App
32 changes: 32 additions & 0 deletions react-native-expo-app/client/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"expo": {
"name": "WebRTC Expo Example",
"slug": "webrtc-expo-example",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"plugins": [
"@config-plugins/react-native-webrtc"
],
"extra": {
"eas": {
"projectId": "298b5afc-c9dd-42ff-9188-bac1105c6a0b"
}
},
"experiments": {
"tsconfigPaths": true
},
"ios": {
"bundleIdentifier": "com.example.webrtcexpoexample"
},
"android": {
"package": "com.example.webrtcexpoexample"
}
}
}
Binary file added react-native-expo-app/client/assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react-native-expo-app/client/assets/splash.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions react-native-expo-app/client/assets/svgs/CallAnswer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react';
import Svg, {Path} from 'react-native-svg';

const SvgComponent = props => (
<Svg
xmlns="http://www.w3.org/2000/svg"
width={66.667}
height={66.667}
viewBox="0 0 50 50"
{...props}>
<Path d="M6.4 6.4C4.5 8.3 3 10.6 3 11.6 3.1 21.9 28.1 46.9 38.3 47c2.3 0 8.7-6.2 8.7-8.4 0-.9-2.5-3.2-5.5-5.3-5.5-3.6-5.6-3.6-8.9-2l-3.3 1.5-6.1-6.1-6-6.2 1.6-3.2c1.5-3.2 1.5-3.3-2.1-8.8-2.1-3-4.4-5.5-5.3-5.5-.9 0-3.1 1.5-5 3.4zM25 5.7c0 .5 2.1 1.4 4.6 2.1 6.1 1.6 11 6.5 12.6 12.6 1.5 5.5 3.3 6.2 2.3.8-.9-5.1-4.1-9.9-8.4-12.7C32.3 6 25 4.2 25 5.7z" />
<Path d="M25 12c0 .5 1.2 1 2.6 1 1.6 0 3.9 1.3 6 3.4 2.1 2.1 3.4 4.4 3.4 6 0 1.4.5 2.6 1 2.6 1.7 0 1.1-4.3-1-7.7-2-3.4-6.6-6.3-10-6.3-1.1 0-2 .4-2 1zM25 17c0 .5.6 1 1.3 1 1.6 0 5.7 4.2 5.7 5.8 0 .7.5 1.2 1 1.2 2 0 1-3.2-1.9-6.1C28.2 16 25 15 25 17z" />
</Svg>
);

export default SvgComponent;
15 changes: 15 additions & 0 deletions react-native-expo-app/client/assets/svgs/CallEnd.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as React from "react"
import Svg, { Path } from "react-native-svg"

function SvgComponent(props) {
return (
<Svg width={22.882} height={7.844} viewBox="0 0 22.882 7.844" {...props}>
<Path
d="M11.441 1.771a15.2 15.2 0 00-4.386.637v2.749a.889.889 0 01-.534.8A11.116 11.116 0 003.98 7.595a.991.991 0 01-.667.252.969.969 0 01-.672-.261L.281 5.392a.843.843 0 010-1.257A16.829 16.829 0 0111.441 0 16.829 16.829 0 0122.6 4.134a.846.846 0 01.281.629.836.836 0 01-.281.624l-2.36 2.191a.989.989 0 01-.672.261 1 1 0 01-.667-.252 11.117 11.117 0 00-2.541-1.638.889.889 0 01-.534-.8V2.4a15.349 15.349 0 00-4.385-.629z"
fill="#fff"
/>
</Svg>
)
}

export default SvgComponent
22 changes: 22 additions & 0 deletions react-native-expo-app/client/assets/svgs/CameraSwitch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from "react";
import Svg, { Defs, Path } from "react-native-svg";

function CameraSwitch(props) {
return (
<Svg viewBox="0 0 19.046 16.357" {...props}>
<Defs></Defs>
<Path
className="a"
d="M21.045 11.238a.682.682 0 00-.627-.423h-.8a7.708 7.708 0 00-.443-1.608 8.178 8.178 0 00-15.239 0 .682.682 0 101.27.493 6.815 6.815 0 0112.7 0 6.134 6.134 0 01.314 1.118h-.532a.686.686 0 00-.484 1.165l1.363 1.363.109.068h.034a.634.634 0 00.341.129.682.682 0 00.484-.2l1.365-1.362a.682.682 0 00.145-.743zm-2.256 3.033a.682.682 0 00-.879.388 6.815 6.815 0 01-12.7 0 6.134 6.134 0 01-.314-1.118h.532a.686.686 0 00.484-1.165l-1.367-1.363-.109-.068a.682.682 0 00-.825.089L2.248 12.4a.669.669 0 00.45 1.145h.8a7.708 7.708 0 00.443 1.608 8.178 8.178 0 0015.239 0 .682.682 0 00-.391-.882z"
transform="translate(-2.052 -4)"
/>
<Path
className="a"
d="M13.602 12.179a2.045 2.045 0 10-2.045 2.045 2.045 2.045 0 002.045-2.045zm-2.726 0a.682.682 0 11.682.682.682.682 0 01-.682-.682z"
transform="translate(-2.052 -4)"
/>
</Svg>
);
}

export default CameraSwitch;
27 changes: 27 additions & 0 deletions react-native-expo-app/client/assets/svgs/Leave.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";
import Svg, { G, Path, Defs, ClipPath } from "react-native-svg";

function Leave(props) {
return (
<Svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<G clipPath="url(#clip0_226_104)" fill="#fff">
<Path d="M3 0h11c1.7 0 3 1.3 3 3v6H8c-1.7 0-3 1.3-3 3s1.3 3 3 3h9v6c0 1.7-1.3 3-3 3H3c-1.7 0-3-1.3-3-3V3c0-1.7 1.4-3 3-3z" />
<Path d="M20.6 13H8c-.6 0-1-.4-1-1s.4-1 1-1h12.6l-1.3-1.3c-.4-.4-.4-1 0-1.4.4-.4 1-.4 1.4 0l3 3c.4.4.4 1 0 1.4l-3 3c-.4.4-1 .4-1.4 0-.4-.4-.4-1 0-1.4l1.3-1.3z" />
</G>
<Defs>
<ClipPath id="clip0_226_104">
<Path fill="#fff" d="M0 0H24V24H0z" />
</ClipPath>
</Defs>
</Svg>
);
}

export default Leave;
16 changes: 16 additions & 0 deletions react-native-expo-app/client/assets/svgs/MicOff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as React from 'react';
import Svg, {Path} from 'react-native-svg';

function MicOff(props) {
return (
<Svg viewBox="0 0 12.691 12.691" {...props}>
<Path d="M0 0h12.691v12.691H0zm0 0h12.691v12.691H0z" fill="none" />
<Path
d="M10.062 5.819h-.9a2.607 2.607 0 01-.231 1.087l.654.652a3.431 3.431 0 00.477-1.739zm-2.131.087c0-.029.008-.058.008-.087V2.637a1.593 1.593 0 00-3.185 0v.1zm-5.686-4.33l-.677.673 3.19 3.191v.382a1.589 1.589 0 001.587 1.591 1.638 1.638 0 00.345-.04l.881.88a2.928 2.928 0 01-1.226.273 2.753 2.753 0 01-2.813-2.704h-.9a3.7 3.7 0 003.185 3.564v1.737h1.062V9.384a3.765 3.765 0 001.348-.48l2.222 2.22.677-.674z"
fill={props.fill}
/>
</Svg>
);
}

export default MicOff;
32 changes: 32 additions & 0 deletions react-native-expo-app/client/assets/svgs/MicOn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from 'react';
import Svg, {Defs, ClipPath, Path, G} from 'react-native-svg';

function MicOn(props) {
return (
<Svg fill="#FFF" viewBox="0 0 15.238 16" {...props}>
<Defs>
<ClipPath id="a">
<Path
className="a"
transform="translate(.235 .255)"
d="M0 0H15.238V16H0z"
/>
</ClipPath>
</Defs>
<G clipPath="url(#a)">
<Path
className="a"
d="M6.765 8.909a2.225 2.225 0 002.229-2.224l.007-4.45a2.236 2.236 0 00-4.472 0v4.448a2.232 2.232 0 002.236 2.226zm3.95-2.224a3.856 3.856 0 01-3.95 3.781 3.856 3.856 0 01-3.95-3.781H1.548a5.182 5.182 0 004.472 4.982v2.431h1.491v-2.431a5.182 5.182 0 004.472-4.982z"
transform="translate(-.235 -.255) translate(1.174 .899)"
/>
<Path
d="M0 .567h13.531v13.531H0z"
transform="translate(-.235 -.255) translate(1.174 .899)"
fill="none"
/>
</G>
</Svg>
);
}

export default MicOn;
Loading