├── .github
├── ISSUE_TEMPLATE
│ └── bug_report.md
└── dependabot.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── netlify.toml
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── sample.env
├── screenshot-react-demo.png
└── src
├── CallObjectContext.js
├── api.js
├── components
├── App
│ ├── App.css
│ └── App.js
├── BrowserUnsupported
│ ├── BrowserUnsupported.css
│ └── BrowserUnsupported.js
├── Call
│ ├── Call.css
│ ├── Call.js
│ └── callState.js
├── CallMessage
│ ├── CallMessage.css
│ └── CallMessage.js
├── Chat
│ ├── Chat.css
│ └── Chat.js
├── Icon
│ └── Icon.js
├── StartButton
│ ├── StartButton.css
│ └── StartButton.js
├── Tile
│ ├── Tile.css
│ └── Tile.js
├── Tray
│ ├── Tray.css
│ └── Tray.js
└── TrayButton
│ ├── TrayButton.css
│ └── TrayButton.js
├── index.css
├── index.js
├── logUtils.js
├── logo.svg
└── urlUtils.js
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: BUG
5 | labels: bug
6 | assignees: kimberleejohnson
7 | ---
8 |
9 |
10 |
11 | # Expected behavior
12 |
13 |
14 |
15 | # Describe the bug (unexpected behavior)
16 |
17 |
18 |
19 | # Steps to reproduce
20 |
21 |
22 |
23 |
24 |
25 |
26 | # Screenshots
27 |
28 |
29 |
30 | # System information
31 |
32 |
33 |
34 | - Device:
35 | - OS, version:
36 | - Browser, version:
37 |
38 | # Additional context
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "daily" # update frequency
7 | allow:
8 | - dependency-name: "@daily-co/daily-js"
9 | dependency-type: "direct"
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # Local Netlify folder
26 | .netlify
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | unsafe-perm = true
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # From .gitignore #
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Microbundle cache
58 | .rpt2_cache/
59 | .rts2_cache_cjs/
60 | .rts2_cache_es/
61 | .rts2_cache_umd/
62 |
63 | # Optional REPL history
64 | .node_repl_history
65 |
66 | # Output of 'npm pack'
67 | *.tgz
68 |
69 | # Yarn Integrity file
70 | .yarn-integrity
71 |
72 | # dotenv environment variables file
73 | .env
74 | .env.test
75 |
76 | # parcel-bundler cache (https://parceljs.org/)
77 | .cache
78 |
79 | # Next.js build output
80 | .next
81 |
82 | # Nuxt.js build / generate output
83 | .nuxt
84 | dist
85 |
86 | # Gatsby files
87 | .cache/
88 | # Comment in the public line in if your project uses Gatsby and not Next.js
89 | # https://nextjs.org/blog/next-9-1#public-directory-support
90 | # public
91 |
92 | # vuepress build output
93 | .vuepress/dist
94 |
95 | # Serverless directories
96 | .serverless/
97 |
98 | # FuseBox cache
99 | .fusebox/
100 |
101 | # DynamoDB Local files
102 | .dynamodb/
103 |
104 | # TernJS port file
105 | .tern-port
106 |
107 | # Stores VSCode versions used for testing VSCode extensions
108 | .vscode-test
109 | # Elastic Beanstalk Files
110 | .elasticbeanstalk/*
111 | !.elasticbeanstalk/*.cfg.yml
112 | !.elasticbeanstalk/*.global.yml
113 |
114 | # Elastic Beanstalk deployment created by GitHub Actions
115 | deploy.zip
116 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | help@daily.co. All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All community leaders are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Community leaders will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from community leaders, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series
85 | of actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or
92 | permanent ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within
112 | the community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0, available at
118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
119 |
120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
121 | enforcement ladder](https://github.com/mozilla/diversity).
122 |
123 | [homepage]: https://www.contributor-covenant.org
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | https://www.contributor-covenant.org/faq. Translations are available at
127 | https://www.contributor-covenant.org/translations.
128 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for looking into contributing to`daily-demos`! We want this repo to help people experiment with different Daily projects more quickly. We especially welcome any contributions that help us make existing demos easier to understand, improve demos' instructions and descriptions, and we're especially excited about any new demos that highlight unique ways to use the [Daily API](https://docs.daily.co/reference).
4 |
5 | **Before contributing:**
6 |
7 | - [Run daily-demos locally](#run-daily-demos-locally)
8 | - [Read our code of conduct](#read-our-code-of-conduct)
9 |
10 | **How to contribute:**
11 |
12 | - [Open or claim an issue](#open-or-claim-an-issue)
13 | - [Open a pull request](#open-a-pull-request)
14 | - [Contribute a new demo project](#contribute-a-new-demo-project)
15 |
16 | ## Before contributing
17 |
18 | ### Run call-object-react locally
19 |
20 | Please follow the instructions in `README.md`.
21 |
22 | ### Read our code of conduct
23 |
24 | We use the [Contributor Covenant](https://www.contributor-covenant.org/) for our Code of Conduct. Before contributing, [please read it](CODE_OF_CONDUCT.md).
25 |
26 | ## How to contribute
27 |
28 | ### Open or claim an issue
29 |
30 | #### Open an issue
31 |
32 | Today we work off two main issue templates: _bug reports_ and _demo/feature requests_.
33 |
34 | _Bug reports_
35 |
36 | Before creating a new bug report, please do two things:
37 |
38 | 1. If you want to report a bug you experienced while on a Daily call, try out these [troubleshooting tips](https://help.daily.co/en/articles/2303117-top-troubleshooting-tips) to see if that takes care of the bug.
39 | 2. If you're still seeing the error, check to see if somebody else has [already filed the issue](https://github.com/daily-demos/call-object-react/issues) before creating a new one.
40 |
41 | If you've done those two things and need to create an issue, we'll ask you to tell us:
42 |
43 | - What you expected to happen
44 | - What actually happened
45 | - Steps to reproduce the error
46 | - Screenshots that illustrate where and what we should be looking for when we reproduce
47 | - System information, like your device, OS, and browser
48 | - Any additional context that you think could help us work through this
49 |
50 | _Demo/feature requests_
51 |
52 | We're always happy to hear about new ways you'd like to use Daily. If you'd like a demo that we don't have yet, we'll ask you to let us know:
53 |
54 | - If the demo will help you solve a particular problem
55 | - Alternative solutions you've considered
56 | - Any additional context that might help us understand this ask
57 |
58 | #### Claim an issue
59 |
60 | All issues labeled `good-first-issue` are up for grabs. If you'd like to tackle an existing issue, feel free to assign yourself, and please leave a comment letting everyone know that you're on it.
61 |
62 | ### Open a pull request
63 |
64 | - If it's been a minute or if you haven't yet cloned, forked, or branched a repository, GitHub has some [docs to help](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests).
65 | - When creating commit messages and pull request titles, please follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard.
66 |
67 | ### Contribute a new demo project
68 |
69 | If you've built a project on Daily that you want to share with other developers, we'd be more than happy to help spread the word.
70 |
71 | To add a new demo project:
72 |
73 | Open a PR in [awesome-daily](#) and add a link to your project.
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A video chat app using React and the Daily JavaScript API
2 |
3 | ❗❗ **Note: As of July 2022, Daily's team recommends using the [custom-video-daily-react-hooks](https://github.com/daily-demos/custom-video-daily-react-hooks) demo app for sample code instead of this repo. It has a similar feature set (all features covered here are included, plus some extras!). Additionally, it showcases how to build a custom Daily video app using [Daily React Hooks](https://docs.daily.co/reference/daily-react-hooks), Daily's custom React hooks library.**
4 |
5 | **Daily recommends using [Daily React Hooks](https://docs.daily.co/reference/daily-react-hooks) for all custom React apps.**
6 |
7 | ---
8 |
9 | This demo is meant to showcase a basic but complete video chat web app using React and the low-level Daily call object. [The call object](https://docs.daily.co/docs/build-a-custom-video-chat-interface#daily-call-object) gives you direct access to the audio and video tracks, letting you build your app however you'd like with those primitives.
10 |
11 | 
12 |
13 | For a step-by-step guide on how we built this demo, [check out our blog post](https://www.daily.co/blog/building-a-custom-video-chat-app-with-react/).
14 |
15 | Check out a live version of the demo [here](https://call-object-react.netlify.app/).
16 |
17 | ## Prerequisites
18 |
19 | - [Sign up for a Daily account](https://dashboard.daily.co/signup).
20 | - [Create a Daily room URL](https://help.daily.co/en/articles/4202139-creating-and-viewing-rooms) to test a video call quickly and hardcode a room URL (_this is NOT recommended for production_).
21 |
22 | ## How the demo works
23 |
24 | In our app, when a user clicks to start a call, the app will create a [meeting room](https://docs.daily.co/reference#rooms), pass the room’s URL to a new Daily call object, and join the call [0]. The call object is something that keeps track of important information about the meeting, like other participants (including their audio and video tracks) and the things they do on the call (e.g. muting their mic or leaving), and provides methods for interacting with the meeting. The app leverages this object to update its state accordingly, and to carry out user actions like muting or screen-sharing. When the user leaves the meeting room, the call object is destroyed.
25 |
26 | [0] If you'll be hardcoding the room URL for testing, the room will be passed as is to the call object. It bears repeating that _this is NOT recommended for production_.
27 |
28 | Please note this project is designed to work with rooms that have [privacy](https://www.daily.co/blog/intro-to-room-access-control/) set to `public`. If you are hardcoding a room URL, please bare in mind that token creation, pre-authorization and knock for access have not been implemented (meaning other participants may not be able to join your call)
29 |
30 | ## Running locally
31 |
32 | 1. Install dependencies `npm i`
33 | 2. Start dev server `npm run dev`
34 | 3. Then open your browser and go to `http://localhost:3002`
35 | 4. Add the Daily room URL you created to line 31 of `api.js`, and follow the comment's instructions.
36 |
37 | OR...
38 |
39 | ## Running using Netlify CLI
40 |
41 | If you want access to the Daily REST API (using the proxy as specified in `netlify.toml`) as well as a more robust local dev environment, please do the following (in this project's directory):
42 |
43 | 1. Deploy to your Netlify account
44 | [](https://app.netlify.com/start/deploy?repository=https://github.com/daily-demos/call-object-react)
45 | 2. Install the Netlify CLI `npm i -g netlify-cli`
46 | 3. Login to your account `netlify login`
47 | 4. Rename `sample.env` to `.env` and add your API key
48 | 5. Start the dev server `netlify dev`
49 |
50 | > Note: If the API proxy isn't working locally you may need to run `netlify build` first. This will put API key in the `netlify.toml` file, so make sure you don't commit this change.
51 |
52 | ## Contributing and feedback
53 |
54 | Let us know how experimenting with this demo goes! Feel free to [open an Issue](https://github.com/daily-demos/call-object-react/issues), or reach us any time at `help@daily.co`. You can also join the conversation about this demo on [DEV](https://dev.to/trydaily/build-a-video-chat-app-in-minutes-with-react-and-daily-js-481c).
55 |
56 | ## What's next
57 |
58 | To get to know even more Daily API methods and events, explore our other demos, like [how to add your own chat interface](https://github.com/daily-co/daily-demos/tree/main/static-demos/simple-chat-demo).
59 |
60 | ---
61 |
62 | ## Related blog posts/tutorials
63 |
64 | Learn more about how to build your own video chat app in React using Daily with [this tutorial](https://www.daily.co/blog/building-a-custom-video-chat-app-with-react/).
65 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "npm run netlify-build"
3 | publish = "build/"
4 |
5 | [build.environment]
6 | # suppress failures for for build warnings
7 | CI = "false"
8 |
9 | [template.environment]
10 | DAILY_API_KEY = "Replace with Daily API key"
11 |
12 |
13 | [[redirects]]
14 | # Proxies the Daily /rooms endpoint, POST will create a room and a GET will return a list
15 | # The placeholder below gets replaced when the build command runs
16 | # as suggested here: https://docs.netlify.com/configure-builds/file-based-configuration/#inject-environment-variable-values
17 | # IF YOU RUN THIS COMMAND LOCALLY DO NOT COMMIT THIS FILE WITH THE API KEY IN IT
18 | # MAKE SURE THE PLACEHOLDER TEXT IS THERE WHENEVER YOU ARE DONE TESTING LOCALLY
19 | from = "/api/rooms"
20 | to = "https://api.daily.co/v1/rooms"
21 | status = 200
22 | force = true
23 | headers = {Authorization = "Bearer DAILY_API_KEY_PLACEHOLDER"}
24 |
25 | # The following redirect is intended for use with most SPAs that handle routing internally.
26 | [[redirects]]
27 | from = "/*"
28 | to = "/index.html"
29 | status = 200
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@daily-co/daily-js": "^0.80.0",
7 | "@testing-library/jest-dom": "^5.16.5",
8 | "@testing-library/react": "^14.0.0",
9 | "@testing-library/user-event": "^14.4.3",
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0",
12 | "react-scripts": "^5.0.1",
13 | "serve": "^14.2.0"
14 | },
15 | "scripts": {
16 | "dev": "PORT=3002 react-scripts start",
17 | "build": "react-scripts build",
18 | "netlify-build": "sed -i s/DAILY_API_KEY_PLACEHOLDER/${DAILY_API_KEY}/g netlify.toml && npm run build",
19 | "start": "PORT=3002 serve build",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": "react-app"
24 | },
25 | "browserslist": {
26 | "production": [
27 | ">0.2%",
28 | "not dead",
29 | "not op_mini all"
30 | ],
31 | "development": [
32 | "last 1 chrome version",
33 | "last 1 firefox version",
34 | "last 1 safari version"
35 | ]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/call-object-react/be83520cfaace5b1493dd4d76a2283c9bd83521f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Daily React Demo
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/call-object-react/be83520cfaace5b1493dd4d76a2283c9bd83521f/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/call-object-react/be83520cfaace5b1493dd4d76a2283c9bd83521f/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/sample.env:
--------------------------------------------------------------------------------
1 | DAILY_API_KEY=REPLACE_WITH_YOUR_API_KEY
--------------------------------------------------------------------------------
/screenshot-react-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/call-object-react/be83520cfaace5b1493dd4d76a2283c9bd83521f/screenshot-react-demo.png
--------------------------------------------------------------------------------
/src/CallObjectContext.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default React.createContext();
4 |
--------------------------------------------------------------------------------
/src/api.js:
--------------------------------------------------------------------------------
1 | const newRoomEndpoint = `${window.location.origin}/api/rooms`;
2 |
3 | /**
4 | * Create a short-lived room for demo purposes.
5 | *
6 | * It uses the redirect proxy as specified in netlify.toml`
7 | * This will work locally if you following the Netlify specific instructions
8 | * in README.md
9 | *
10 | * See https://docs.daily.co/reference#create-room for more information on how
11 | * to use the Daily REST API to create rooms and what options are available.
12 | */
13 | async function createRoom() {
14 | const exp = Math.round(Date.now() / 1000) + 60 * 30;
15 | const options = {
16 | properties: {
17 | exp: exp,
18 | },
19 | };
20 | let response = await fetch(newRoomEndpoint, {
21 | method: 'POST',
22 | body: JSON.stringify(options),
23 | mode: 'cors',
24 | }),
25 | room = await response.json();
26 | return room;
27 |
28 | // Comment out the above and uncomment the below, using your own URL
29 | // return { url: 'https://your-domain.daily.co/hello' }
30 | }
31 |
32 | export default { createRoom };
33 |
--------------------------------------------------------------------------------
/src/components/App/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | background-color: #4a4a4a;
3 | position: absolute;
4 | height: 100%;
5 | width: 100%;
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useCallback } from 'react';
2 | import Call from '../Call/Call';
3 | import StartButton from '../StartButton/StartButton';
4 | import api from '../../api';
5 | import './App.css';
6 | import Tray from '../Tray/Tray';
7 | import CallObjectContext from '../../CallObjectContext';
8 | import { roomUrlFromPageUrl, pageUrlFromRoomUrl } from '../../urlUtils';
9 | import DailyIframe from '@daily-co/daily-js';
10 | import { logDailyEvent } from '../../logUtils';
11 |
12 | const STATE_IDLE = 'STATE_IDLE';
13 | const STATE_CREATING = 'STATE_CREATING';
14 | const STATE_JOINING = 'STATE_JOINING';
15 | const STATE_JOINED = 'STATE_JOINED';
16 | const STATE_LEAVING = 'STATE_LEAVING';
17 | const STATE_ERROR = 'STATE_ERROR';
18 |
19 | export default function App() {
20 | const [appState, setAppState] = useState(STATE_IDLE);
21 | const [roomUrl, setRoomUrl] = useState(null);
22 | const [callObject, setCallObject] = useState(null);
23 |
24 | /**
25 | * Creates a new call room.
26 | */
27 | const createCall = useCallback(() => {
28 | setAppState(STATE_CREATING);
29 | return api
30 | .createRoom()
31 | .then((room) => room.url)
32 | .catch((error) => {
33 | console.log('Error creating room', error);
34 | setRoomUrl(null);
35 | setAppState(STATE_IDLE);
36 | });
37 | }, []);
38 |
39 | /**
40 | * Starts joining an existing call.
41 | *
42 | * NOTE: In this demo we show how to completely clean up a call with destroy(),
43 | * which requires creating a new call object before you can join() again.
44 | * This isn't strictly necessary, but is good practice when you know you'll
45 | * be done with the call object for a while and you're no longer listening to its
46 | * events.
47 | */
48 | const startJoiningCall = useCallback((url) => {
49 | const newCallObject = DailyIframe.createCallObject();
50 | setRoomUrl(url);
51 | setCallObject(newCallObject);
52 | setAppState(STATE_JOINING);
53 | newCallObject.join({ url });
54 | }, []);
55 |
56 | /**
57 | * Starts leaving the current call.
58 | */
59 | const startLeavingCall = useCallback(() => {
60 | if (!callObject) return;
61 | // If we're in the error state, we've already "left", so just clean up
62 | if (appState === STATE_ERROR) {
63 | callObject.destroy().then(() => {
64 | setRoomUrl(null);
65 | setCallObject(null);
66 | setAppState(STATE_IDLE);
67 | });
68 | } else {
69 | setAppState(STATE_LEAVING);
70 | callObject.leave();
71 | }
72 | }, [callObject, appState]);
73 |
74 | /**
75 | * If a room's already specified in the page's URL when the component mounts,
76 | * join the room.
77 | */
78 | useEffect(() => {
79 | const url = roomUrlFromPageUrl();
80 | url && startJoiningCall(url);
81 | }, [startJoiningCall]);
82 |
83 | /**
84 | * Update the page's URL to reflect the active call when roomUrl changes.
85 | *
86 | * This demo uses replaceState rather than pushState in order to avoid a bit
87 | * of state-management complexity. See the comments around enableCallButtons
88 | * and enableStartButton for more information.
89 | */
90 | useEffect(() => {
91 | const pageUrl = pageUrlFromRoomUrl(roomUrl);
92 | if (pageUrl === window.location.href) return;
93 | window.history.replaceState(null, null, pageUrl);
94 | }, [roomUrl]);
95 |
96 | /**
97 | * Uncomment to attach call object to window for debugging purposes.
98 | */
99 | // useEffect(() => {
100 | // window.callObject = callObject;
101 | // }, [callObject]);
102 |
103 | /**
104 | * Update app state based on reported meeting state changes.
105 | *
106 | * NOTE: Here we're showing how to completely clean up a call with destroy().
107 | * This isn't strictly necessary between join()s, but is good practice when
108 | * you know you'll be done with the call object for a while and you're no
109 | * longer listening to its events.
110 | */
111 | useEffect(() => {
112 | if (!callObject) return;
113 |
114 | const events = ['joined-meeting', 'left-meeting', 'error'];
115 |
116 | function handleNewMeetingState(event) {
117 | event && logDailyEvent(event);
118 | switch (callObject.meetingState()) {
119 | case 'joined-meeting':
120 | setAppState(STATE_JOINED);
121 | break;
122 | case 'left-meeting':
123 | callObject.destroy().then(() => {
124 | setRoomUrl(null);
125 | setCallObject(null);
126 | setAppState(STATE_IDLE);
127 | });
128 | break;
129 | case 'error':
130 | setAppState(STATE_ERROR);
131 | break;
132 | default:
133 | break;
134 | }
135 | }
136 |
137 | // Use initial state
138 | handleNewMeetingState();
139 |
140 | // Listen for changes in state
141 | for (const event of events) {
142 | callObject.on(event, handleNewMeetingState);
143 | }
144 |
145 | // Stop listening for changes in state
146 | return function cleanup() {
147 | for (const event of events) {
148 | callObject.off(event, handleNewMeetingState);
149 | }
150 | };
151 | }, [callObject]);
152 |
153 | /**
154 | * Listen for app messages from other call participants.
155 | */
156 | useEffect(() => {
157 | if (!callObject) {
158 | return;
159 | }
160 |
161 | function handleAppMessage(event) {
162 | if (event) {
163 | logDailyEvent(event);
164 | console.log(`received app message from ${event.fromId}: `, event.data);
165 | }
166 | }
167 |
168 | callObject.on('app-message', handleAppMessage);
169 |
170 | return function cleanup() {
171 | callObject.off('app-message', handleAppMessage);
172 | };
173 | }, [callObject]);
174 |
175 | /**
176 | * Show the call UI if we're either joining, already joined, or are showing
177 | * an error.
178 | */
179 | const showCall = [STATE_JOINING, STATE_JOINED, STATE_ERROR].includes(
180 | appState
181 | );
182 |
183 | /**
184 | * Only enable the call buttons (camera toggle, leave call, etc.) if we're joined
185 | * or if we've errored out.
186 | *
187 | * !!!
188 | * IMPORTANT: calling callObject.destroy() *before* we get the "joined-meeting"
189 | * can result in unexpected behavior. Disabling the leave call button
190 | * until then avoids this scenario.
191 | * !!!
192 | */
193 | const enableCallButtons = [STATE_JOINED, STATE_ERROR].includes(appState);
194 |
195 | /**
196 | * Only enable the start button if we're in an idle state (i.e. not creating,
197 | * joining, etc.).
198 | *
199 | * !!!
200 | * IMPORTANT: only one call object is meant to be used at a time. Creating a
201 | * new call object with DailyIframe.createCallObject() *before* your previous
202 | * callObject.destroy() completely finishes can result in unexpected behavior.
203 | * Disabling the start button until then avoids that scenario.
204 | * !!!
205 | */
206 | const enableStartButton = appState === STATE_IDLE;
207 |
208 | return (
209 |
210 | {showCall ? (
211 | // NOTE: for an app this size, it's not obvious that using a Context
212 | // is the best choice. But for larger apps with deeply-nested components
213 | // that want to access call object state and bind event listeners to the
214 | // call object, this can be a helpful pattern.
215 |
216 |
217 |
221 |
222 | ) : (
223 | {
226 | createCall().then((url) => startJoiningCall(url));
227 | }}
228 | />
229 | )}
230 |
7 | Looks like you need to upgrade your browser to make Daily video calls.
8 |
9 | See
10 | this page
11 | for help getting on a supported browser version.
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Call/Call.css:
--------------------------------------------------------------------------------
1 | .call {
2 | position: relative; /* To make it a "positioned" container so children layout works */
3 | height: calc(100% - 60px); /* Space for the tray */
4 | }
5 |
6 | .large-tiles {
7 | height: calc(100% - 132.5px);
8 | width: 80%;
9 | position: relative;
10 | left: 50%;
11 | transform: translate(-50%, 0);
12 | display: grid;
13 | grid-template-columns: repeat(auto-fit, minmax(30%, 1fr));
14 | align-items: center;
15 | overflow-y: scroll;
16 | }
17 |
18 | .small-tiles {
19 | height: 132.5px; /* Video height + 10px padding either side */
20 | display: flex;
21 | align-items: center;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/Call/Call.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext, useReducer, useCallback } from 'react';
2 | import './Call.css';
3 | import Tile from '../Tile/Tile';
4 | import CallObjectContext from '../../CallObjectContext';
5 | import CallMessage from '../CallMessage/CallMessage';
6 | import {
7 | initialCallState,
8 | CLICK_ALLOW_TIMEOUT,
9 | PARTICIPANTS_CHANGE,
10 | CAM_OR_MIC_ERROR,
11 | FATAL_ERROR,
12 | callReducer,
13 | isLocal,
14 | isScreenShare,
15 | containsScreenShare,
16 | getMessage,
17 | } from './callState';
18 | import { logDailyEvent } from '../../logUtils';
19 |
20 | export default function Call() {
21 | const callObject = useContext(CallObjectContext);
22 | const [callState, dispatch] = useReducer(callReducer, initialCallState);
23 |
24 | /**
25 | * Start listening for participant changes, when the callObject is set.
26 | */
27 | useEffect(() => {
28 | if (!callObject) return;
29 |
30 | const events = [
31 | 'participant-joined',
32 | 'participant-updated',
33 | 'participant-left',
34 | ];
35 |
36 | function handleNewParticipantsState(event) {
37 | event && logDailyEvent(event);
38 | dispatch({
39 | type: PARTICIPANTS_CHANGE,
40 | participants: callObject.participants(),
41 | });
42 | }
43 |
44 | // Use initial state
45 | handleNewParticipantsState();
46 |
47 | // Listen for changes in state
48 | for (const event of events) {
49 | callObject.on(event, handleNewParticipantsState);
50 | }
51 |
52 | // Stop listening for changes in state
53 | return function cleanup() {
54 | for (const event of events) {
55 | callObject.off(event, handleNewParticipantsState);
56 | }
57 | };
58 | }, [callObject]);
59 |
60 | /**
61 | * Start listening for call errors, when the callObject is set.
62 | */
63 | useEffect(() => {
64 | if (!callObject) return;
65 |
66 | function handleCameraErrorEvent(event) {
67 | logDailyEvent(event);
68 | dispatch({
69 | type: CAM_OR_MIC_ERROR,
70 | message:
71 | (event && event.errorMsg && event.errorMsg.errorMsg) || 'Unknown',
72 | });
73 | }
74 |
75 | // We're making an assumption here: there is no camera error when callObject
76 | // is first assigned.
77 |
78 | callObject.on('camera-error', handleCameraErrorEvent);
79 |
80 | return function cleanup() {
81 | callObject.off('camera-error', handleCameraErrorEvent);
82 | };
83 | }, [callObject]);
84 |
85 | /**
86 | * Start listening for fatal errors, when the callObject is set.
87 | */
88 | useEffect(() => {
89 | if (!callObject) return;
90 |
91 | function handleErrorEvent(e) {
92 | logDailyEvent(e);
93 | dispatch({
94 | type: FATAL_ERROR,
95 | message: (e && e.errorMsg) || 'Unknown',
96 | });
97 | }
98 |
99 | // We're making an assumption here: there is no error when callObject is
100 | // first assigned.
101 |
102 | callObject.on('error', handleErrorEvent);
103 |
104 | return function cleanup() {
105 | callObject.off('error', handleErrorEvent);
106 | };
107 | }, [callObject]);
108 |
109 | /**
110 | * Start a timer to show the "click allow" message, when the component mounts.
111 | */
112 | useEffect(() => {
113 | const t = setTimeout(() => {
114 | dispatch({ type: CLICK_ALLOW_TIMEOUT });
115 | }, 2500);
116 |
117 | return function cleanup() {
118 | clearTimeout(t);
119 | };
120 | }, []);
121 |
122 | /**
123 | * Send an app message to the remote participant whose tile was clicked on.
124 | */
125 | const sendHello = useCallback(
126 | (participantId) => {
127 | callObject &&
128 | callObject.sendAppMessage({ hello: 'world' }, participantId);
129 | },
130 | [callObject]
131 | );
132 |
133 | function getTiles() {
134 | let largeTiles = [];
135 | let smallTiles = [];
136 | Object.entries(callState.callItems).forEach(([id, callItem]) => {
137 | const isLarge =
138 | isScreenShare(id) ||
139 | (!isLocal(id) && !containsScreenShare(callState.callItems));
140 | const tile = (
141 | {
152 | sendHello(id);
153 | }
154 | }
155 | />
156 | );
157 | if (isLarge) {
158 | largeTiles.push(tile);
159 | } else {
160 | smallTiles.push(tile);
161 | }
162 | });
163 | return [largeTiles, smallTiles];
164 | }
165 |
166 | const [largeTiles, smallTiles] = getTiles();
167 | const message = getMessage(callState);
168 | return (
169 |
170 |
171 | {
172 | !message
173 | ? largeTiles
174 | : null /* Avoid showing large tiles to make room for the message */
175 | }
176 |