├── .eslintrc
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── custom
├── .gitignore
├── README.md
├── active-speaker
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ │ ├── App
│ │ │ ├── App.js
│ │ │ └── index.js
│ │ ├── Room
│ │ │ ├── Room.js
│ │ │ └── index.js
│ │ └── SpeakerView
│ │ │ ├── SpeakerTile
│ │ │ ├── SpeakerTile.js
│ │ │ └── index.js
│ │ │ ├── SpeakerView.js
│ │ │ └── index.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── basic-call
│ ├── .babelrc
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ │ ├── App
│ │ │ ├── App.js
│ │ │ ├── Asides.js
│ │ │ ├── Modals.js
│ │ │ └── index.js
│ │ ├── Call
│ │ │ ├── Container.js
│ │ │ ├── Header.js
│ │ │ ├── Room.js
│ │ │ ├── VideoGrid.js
│ │ │ └── WaitingRoom.js
│ │ └── Prejoin
│ │ │ ├── CreatingRoom.js
│ │ │ ├── Intro.js
│ │ │ └── NotConfigured.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ │ ├── createRoom.js
│ │ │ └── token.js
│ │ ├── index.js
│ │ └── not-found.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── fitness-demo
│ ├── .babelrc
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ │ ├── App
│ │ │ ├── App.js
│ │ │ ├── AsideHeader.js
│ │ │ ├── Asides.js
│ │ │ ├── Modals.js
│ │ │ └── index.js
│ │ ├── Call
│ │ │ ├── ChatAside.js
│ │ │ ├── Container.js
│ │ │ ├── Header.js
│ │ │ ├── InviteOthers.js
│ │ │ ├── PeopleAside.js
│ │ │ ├── Room.js
│ │ │ ├── VideoView.js
│ │ │ └── WaitingRoom.js
│ │ ├── GridView
│ │ │ ├── GridView.js
│ │ │ └── index.js
│ │ ├── Prejoin
│ │ │ ├── Intro.js
│ │ │ └── NotConfigured.js
│ │ ├── SpeakerView
│ │ │ ├── ScreensAndPins
│ │ │ │ ├── ScreenPinTile.js
│ │ │ │ ├── ScreensAndPins.js
│ │ │ │ └── index.js
│ │ │ ├── SpeakerTile
│ │ │ │ ├── SpeakerTile.js
│ │ │ │ └── index.js
│ │ │ ├── SpeakerView.js
│ │ │ └── index.js
│ │ └── Tray
│ │ │ ├── Chat.js
│ │ │ ├── Record.js
│ │ │ ├── ScreenShare.js
│ │ │ ├── Stream.js
│ │ │ └── index.js
│ ├── contexts
│ │ ├── ChatProvider.js
│ │ └── ClassStateProvider.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── [room].js
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ │ ├── createRoom.js
│ │ │ ├── editRoom.js
│ │ │ ├── presence.js
│ │ │ ├── room.js
│ │ │ └── token.js
│ │ ├── index.js
│ │ └── not-found.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ ├── pattern-bg.png
│ │ └── pattern-ls.svg
├── flying-emojis
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ │ ├── App.js
│ │ ├── FlyingEmojisOverlay.js
│ │ └── Tray.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── live-streaming
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ │ ├── App.js
│ │ ├── LiveStreamingModal.js
│ │ └── Tray.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── live-transcription
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ │ ├── App.js
│ │ ├── TranscriptionAside.js
│ │ └── Tray.js
│ ├── env.example
│ ├── image.gif
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── pagination
│ ├── .babelrc
│ ├── App.js
│ ├── README.md
│ ├── components
│ │ ├── App.js
│ │ ├── PaginatedVideoGrid.js
│ │ └── Tray.js
│ ├── env.example
│ ├── image.png
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── recording
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ │ ├── App.js
│ │ ├── RecordingModal.js
│ │ └── Tray.js
│ ├── contexts
│ │ └── RecordingProvider.js
│ ├── env.example
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── api
│ │ └── index.js
│ └── public
│ │ └── assets
│ │ ├── daily-logo-dark.svg
│ │ ├── daily-logo.svg
│ │ ├── join.mp3
│ │ ├── message.mp3
│ │ └── pattern-bg.png
├── shared
│ ├── components
│ │ ├── Aside
│ │ │ ├── Aside.js
│ │ │ ├── NetworkAside.js
│ │ │ ├── PeopleAside.js
│ │ │ └── index.js
│ │ ├── Audio
│ │ │ ├── Audio.js
│ │ │ ├── AudioTrack.js
│ │ │ ├── CombinedAudioTrack.js
│ │ │ └── index.js
│ │ ├── Button
│ │ │ ├── Button.js
│ │ │ └── index.js
│ │ ├── Capsule
│ │ │ ├── Capsule.js
│ │ │ └── index.js
│ │ ├── Card
│ │ │ ├── Card.js
│ │ │ └── index.js
│ │ ├── DeviceSelect
│ │ │ ├── DeviceSelect.js
│ │ │ └── index.js
│ │ ├── DeviceSelectModal
│ │ │ ├── DeviceSelectModal.js
│ │ │ └── index.js
│ │ ├── ExpiryTimer
│ │ │ ├── ExpiryTimer.js
│ │ │ └── index.js
│ │ ├── Field
│ │ │ ├── Field.js
│ │ │ └── index.js
│ │ ├── GlobalStyle
│ │ │ ├── GlobalStyle.js
│ │ │ └── index.js
│ │ ├── HairCheck
│ │ │ ├── HairCheck.js
│ │ │ └── index.js
│ │ ├── Header
│ │ │ ├── Header.js
│ │ │ └── index.js
│ │ ├── HeaderCapsule
│ │ │ ├── HeaderCapsule.js
│ │ │ └── index.js
│ │ ├── Input
│ │ │ ├── Input.js
│ │ │ └── index.js
│ │ ├── Loader
│ │ │ ├── Loader.js
│ │ │ └── index.js
│ │ ├── MessageCard
│ │ │ ├── MessageCard.js
│ │ │ └── index.js
│ │ ├── Modal
│ │ │ ├── Modal.js
│ │ │ └── index.js
│ │ ├── MuteButton
│ │ │ ├── MuteButton.js
│ │ │ └── index.js
│ │ ├── ParticipantBar
│ │ │ ├── ParticipantBar.js
│ │ │ ├── index.js
│ │ │ └── useBlockScrolling.js
│ │ ├── Tile
│ │ │ ├── Tile.js
│ │ │ ├── Video.js
│ │ │ ├── avatar.svg
│ │ │ └── index.js
│ │ ├── Tray
│ │ │ ├── BasicTray.js
│ │ │ ├── Tray.js
│ │ │ ├── TrayButton.js
│ │ │ ├── TrayMicButton.js
│ │ │ └── index.js
│ │ ├── VideoContainer
│ │ │ ├── VideoContainer.js
│ │ │ └── index.js
│ │ ├── WaitingRoom
│ │ │ ├── WaitingParticipantRow.js
│ │ │ ├── WaitingRoomModal.js
│ │ │ ├── WaitingRoomNotification.js
│ │ │ └── index.js
│ │ └── Well
│ │ │ ├── Well.js
│ │ │ └── index.js
│ ├── constants.js
│ ├── contexts
│ │ ├── CallProvider.js
│ │ ├── LiveStreamingProvider.js
│ │ ├── MediaDeviceProvider.js
│ │ ├── ParticipantsProvider.js
│ │ ├── ScreenShareProvider.js
│ │ ├── TracksProvider.js
│ │ ├── TranscriptionProvider.js
│ │ ├── UIStateProvider.js
│ │ ├── WaitingRoomProvider.js
│ │ ├── participantsState.js
│ │ ├── tracksState.js
│ │ └── useCallMachine.js
│ ├── hooks
│ │ ├── useActiveSpeaker.js
│ │ ├── useAudioLevel.js
│ │ ├── useAudioTrack.js
│ │ ├── useCallUI.js
│ │ ├── useCamSubscriptions.js
│ │ ├── useJoinSound.js
│ │ ├── useNetworkState.js
│ │ ├── useResize.js
│ │ ├── useResponsive.js
│ │ ├── useScrollbarWidth.js
│ │ ├── useSharedState.js
│ │ ├── useSound.js
│ │ ├── useSoundLoader.js
│ │ └── useVideoTrack.js
│ ├── icons
│ │ ├── add-md.svg
│ │ ├── add-person-lg.svg
│ │ ├── avatar-md.svg
│ │ ├── camera-off-md.svg
│ │ ├── camera-off-sm.svg
│ │ ├── camera-on-md.svg
│ │ ├── camera-on-sm.svg
│ │ ├── chat-md.svg
│ │ ├── close-sm.svg
│ │ ├── emoji-sm.svg
│ │ ├── grid-md.svg
│ │ ├── leave-md.svg
│ │ ├── lock-md.svg
│ │ ├── mic-off-md.svg
│ │ ├── mic-off-sm.svg
│ │ ├── mic-on-md.svg
│ │ ├── mic-on-sm.svg
│ │ ├── more-md.svg
│ │ ├── network-md.svg
│ │ ├── people-md.svg
│ │ ├── play-sm.svg
│ │ ├── raquo-md.svg
│ │ ├── record-md.svg
│ │ ├── settings-md.svg
│ │ ├── settings-sm.svg
│ │ ├── share-sm.svg
│ │ ├── speaker-view-md.svg
│ │ ├── star-md.svg
│ │ └── streaming-md.svg
│ ├── index.js
│ ├── lib
│ │ ├── demoProps.js
│ │ ├── mediaUtils.js
│ │ ├── slugify.js
│ │ ├── sortByKey.js
│ │ ├── sortLastActive.js
│ │ └── token.js
│ ├── package.json
│ └── styles
│ │ ├── defaultTheme.js
│ │ └── global.js
└── text-chat
│ ├── .babelrc
│ ├── README.md
│ ├── components
│ ├── App.js
│ ├── ChatAside.js
│ └── Tray.js
│ ├── contexts
│ └── ChatProvider.js
│ ├── env.example
│ ├── hooks
│ └── useMessageSound.js
│ ├── image.png
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ ├── _app.js
│ ├── _document.js
│ ├── api
│ └── index.js
│ └── public
│ ├── assets
│ ├── daily-logo-dark.svg
│ ├── daily-logo.svg
│ ├── join.mp3
│ ├── message.mp3
│ └── pattern-bg.png
│ └── components
│ └── Header
│ ├── Header.js
│ └── index.js
├── logo.png
├── package-lock.json
├── package.json
├── prebuilt
├── README.md
├── basic-embed
│ ├── .env.example
│ ├── .gitignore
│ ├── README.md
│ ├── basic-embed.gif
│ ├── components
│ │ ├── Call.js
│ │ ├── CreateRoomButton.js
│ │ ├── ExpiryTimer.js
│ │ └── Home.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── api
│ │ │ └── room
│ │ │ │ └── index.js
│ │ └── index.js
│ ├── public
│ │ ├── daily-logo.svg
│ │ ├── favicon.ico
│ │ └── github-logo.png
│ └── yarn.lock
├── chat-overlay
│ ├── README.md
│ ├── image.jpg
│ ├── index.html
│ ├── main.css
│ └── main.js
└── ios-webview
│ ├── .gitignore
│ ├── README.md
│ ├── WebViewPrebuilt.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── WebViewPrebuilt
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
│ └── image.png
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "prettier"],
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true
7 | },
8 | "rules": {
9 | "import/no-extraneous-dependencies": 0,
10 | "@next/next/no-img-element": 0,
11 | "import/order": [
12 | "error",
13 | {
14 | "groups": ["builtin", "external", "parent", "sibling", "index"],
15 | "pathGroups": [
16 | {
17 | "pattern": "react",
18 | "group": "external",
19 | "position": "before"
20 | }
21 | ],
22 | "pathGroupsExcludedImportTypes": ["react"],
23 | "alphabetize": {
24 | "order": "asc"
25 | }
26 | }
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.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 | # next.js
12 | .next
13 | out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "singleQuote": true
6 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2021, Daily
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # [Daily](https://daily.co) | Examples
4 |
5 | This repository uses [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) so it's required to have yarn [installed](https://classic.yarnpkg.com/en/docs/install).
6 |
7 | ## Getting started
8 |
9 | Setup dependencies via `yarn install`.
10 |
11 | Add the required environment variables (e.g. your Daily API key) for the demo being used. Each demo's README will list the required environment variables to run it locally.
12 |
13 | Run an example via `yarn workspace @custom/basic-call dev` (replacing `basic-call` with the name of the demo).
14 |
15 | Please note: these demos are intended as educational resources for using the Daily platform as well as showcasing common usage patterns and best practices. That said, they are not intended to be used as production ready applications.
16 |
17 | ---
18 |
19 | ## Contents
20 |
21 | ## [Custom (Web)](./custom/)
22 |
23 | Examples that showcase the Daily call object using our Javascript library
24 |
25 | ## [Prebuilt UI](./prebuilt)
26 |
27 | Examples that showcase using and customizing the Daily Prebuilt UI
28 |
29 | ---
30 |
31 | ## Contributions
32 |
33 | We welcome all contributions that make it easier for developers to get to know Daily through these demos. If you'd like to open or claim an issue, add your own demo, or contribute in another way, please read CONTRIBUTING.md.
34 |
35 | ## Contact us
36 |
37 | We're always happy to help developers building on Daily. Reach out to us any time at help@daily.co, or chat with us.
38 |
--------------------------------------------------------------------------------
/custom/.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 | # next.js
12 | .next
13 | out
14 |
15 | # production
16 | build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
--------------------------------------------------------------------------------
/custom/active-speaker/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/active-speaker/README.md:
--------------------------------------------------------------------------------
1 | # Active Speaker
2 |
3 | 
4 |
5 | ### Live example
6 |
7 | **[See it in action here ➡️](https://custom-active-speaker.vercel.app)**
8 |
9 | ---
10 |
11 | ## What does this demo do?
12 |
13 | - Uses an active speaker view mode that shows the currently talking participant (or active screen share) in a larger tile
14 | - Introduces the `ParticipantBar` column that virtually scrolls through all call participants
15 | - Uses manual subscriptions to paginate between tiles that are currently in view. For more information about how this works, please refer to the [pagination demo](../pagination)
16 |
17 | Please note: this demo is not currently mobile optimised
18 |
19 | ### Getting started
20 |
21 | ```
22 | # set both DAILY_API_KEY and DAILY_DOMAIN
23 | mv env.example .env.local
24 |
25 | yarn
26 | yarn workspace @custom/active-speaker dev
27 | ```
28 |
29 | ## Deploy your own on Vercel
30 |
31 | [](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
32 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import { Room } from '../Room';
5 |
6 | // Extend our basic call app component with our custom Room componenet
7 | export const AppWithSpeakerViewRoom = () => (
8 | ,
11 | }}
12 | />
13 | );
14 |
15 | export default AppWithSpeakerViewRoom;
16 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/App/index.js:
--------------------------------------------------------------------------------
1 | export { AppWithSpeakerViewRoom as default } from './App';
2 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/Room/Room.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { SpeakerView } from '../SpeakerView';
4 |
5 | export const Room = () => ;
6 |
7 | export default Room;
8 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/Room/index.js:
--------------------------------------------------------------------------------
1 | export { Room as default } from './Room';
2 | export { Room } from './Room';
3 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/SpeakerView/SpeakerTile/index.js:
--------------------------------------------------------------------------------
1 | export { SpeakerTile as default } from './SpeakerTile';
2 | export { SpeakerTile } from './SpeakerTile';
3 |
--------------------------------------------------------------------------------
/custom/active-speaker/components/SpeakerView/index.js:
--------------------------------------------------------------------------------
1 | export { SpeakerView as default } from './SpeakerView';
2 | export { SpeakerView } from './SpeakerView';
3 |
--------------------------------------------------------------------------------
/custom/active-speaker/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
10 |
11 | # Enable manual track subscriptions
12 | MANUAL_TRACK_SUBS=1
--------------------------------------------------------------------------------
/custom/active-speaker/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/active-speaker/image.png
--------------------------------------------------------------------------------
/custom/active-speaker/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/active-speaker/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/active-speaker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/active-speaker",
3 | "description": "Basic Call + Active Speaker",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/basic-call": "*",
14 | "@custom/shared": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/active-speaker/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithSpeakerViewRoom from '../components/App';
4 |
5 | App.customAppComponent = ;
6 |
7 | export default App;
8 |
--------------------------------------------------------------------------------
/custom/active-speaker/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/active-speaker/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | // Pass through domain as prop
8 | return {
9 | props: defaultProps,
10 | };
11 | }
12 |
13 | export default Index;
14 |
--------------------------------------------------------------------------------
/custom/active-speaker/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/active-speaker/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/active-speaker/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/active-speaker/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/active-speaker/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/active-speaker/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/basic-call/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/basic-call/.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 | # next.js
12 | .next
13 | out
14 |
15 | # production
16 | build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
--------------------------------------------------------------------------------
/custom/basic-call/README.md:
--------------------------------------------------------------------------------
1 | # Basic call
2 |
3 | 
4 |
5 | ### Live example
6 |
7 | **[See it in action here ➡️](https://custom-basic-call.vercel.app)**
8 |
9 | ---
10 |
11 | ## What does this demo do?
12 |
13 | - Built on [NextJS](https://nextjs.org/)
14 | - Create a Daily instance using call object mode
15 | - Manage user media devices
16 | - Render UI based on the call state
17 | - Handle media and call errors
18 | - Obtain call access token via Daily REST API
19 | - Handle preauthentication, knock for access and auto join
20 |
21 | Please note: this demo is not currently mobile optimised
22 |
23 | ### Getting started
24 |
25 | ```
26 | # set both DAILY_API_KEY and DAILY_DOMAIN
27 | mv env.example .env.local
28 |
29 | # from project root...
30 | yarn
31 | yarn workspace @custom/basic-call dev
32 | ```
33 |
34 | ## How does this example work?
35 |
36 | This demo puts to work the following [shared libraries](../shared):
37 |
38 | **[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
39 | Convenience context that provides an interface to media devices throughout app
40 |
41 | **[CallProvider.js](../shared/contexts/CallProvider.js)**
42 | Primary call context that manages Daily call state, participant state and call object interaction
43 |
44 | **[useCallMachine.js](../shared/contexts/useCallMachine.js)**
45 | Abstraction hook that manages Daily call state and error handling
46 |
47 | **[ParticipantsProvider.js](../shared/contexts/ParticipantsProvider.js)**
48 | Manages participant state and abstracts common selectors / derived data
49 |
50 | ## Deploy your own on Vercel
51 |
52 | [](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
53 |
--------------------------------------------------------------------------------
/custom/basic-call/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import ExpiryTimer from '@custom/shared/components/ExpiryTimer';
3 | import { useCallState } from '@custom/shared/contexts/CallProvider';
4 | import { useCallUI } from '@custom/shared/hooks/useCallUI';
5 |
6 | import PropTypes from 'prop-types';
7 | import Room from '../Call/Room';
8 | import { Asides } from './Asides';
9 | import { Modals } from './Modals';
10 |
11 | export const App = ({ customComponentForState }) => {
12 | const { roomExp, state } = useCallState();
13 |
14 | const componentForState = useCallUI({
15 | state,
16 | room: ,
17 | ...customComponentForState,
18 | });
19 |
20 | // Memoize children to avoid unnecessary renders from HOC
21 | return useMemo(
22 | () => (
23 | <>
24 | {roomExp && }
25 |
26 | {componentForState()}
27 |
28 |
29 |
40 |
41 | >
42 | ),
43 | [componentForState, roomExp]
44 | );
45 | };
46 |
47 | App.propTypes = {
48 | customComponentForState: PropTypes.any,
49 | };
50 |
51 | export default App;
52 |
--------------------------------------------------------------------------------
/custom/basic-call/components/App/Asides.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NetworkAside } from '@custom/shared/components/Aside';
3 | import { PeopleAside } from '@custom/shared/components/Aside';
4 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
5 |
6 | export const Asides = () => {
7 | const { asides } = useUIState();
8 |
9 | return (
10 | <>
11 |
12 |
13 | {asides.map((AsideComponent) => (
14 |
15 | ))}
16 | >
17 | );
18 | };
19 |
20 | export default Asides;
21 |
--------------------------------------------------------------------------------
/custom/basic-call/components/App/Modals.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DeviceSelectModal from '@custom/shared/components/DeviceSelectModal/DeviceSelectModal';
3 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
4 |
5 | export const Modals = () => {
6 | const { modals } = useUIState();
7 |
8 | return (
9 | <>
10 |
11 | {modals.map((ModalComponent) => (
12 |
13 | ))}
14 | >
15 | );
16 | };
17 |
18 | export default Modals;
19 |
--------------------------------------------------------------------------------
/custom/basic-call/components/App/index.js:
--------------------------------------------------------------------------------
1 | export { App as default } from './App';
2 |
--------------------------------------------------------------------------------
/custom/basic-call/components/Call/Container.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { Audio } from '@custom/shared/components/Audio';
3 | import { BasicTray } from '@custom/shared/components/Tray';
4 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
5 | import { useJoinSound } from '@custom/shared/hooks/useJoinSound';
6 | import PropTypes from 'prop-types';
7 | import { WaitingRoom } from './WaitingRoom';
8 |
9 | export const Container = ({ children }) => {
10 | const { isOwner } = useParticipants();
11 |
12 | useJoinSound();
13 |
14 | const roomComponents = useMemo(
15 | () => (
16 | <>
17 | {/* Show waiting room notification & modal if call owner */}
18 | {isOwner && }
19 | {/* Tray buttons */}
20 |
21 | {/* Audio tags */}
22 |
23 | >
24 | ),
25 | [isOwner]
26 | );
27 |
28 | return (
29 |
30 | {children}
31 | {roomComponents}
32 |
33 |
41 |
42 | );
43 | };
44 |
45 | Container.propTypes = {
46 | children: PropTypes.node,
47 | };
48 |
49 | export default Container;
50 |
--------------------------------------------------------------------------------
/custom/basic-call/components/Call/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import HeaderCapsule from '@custom/shared/components/HeaderCapsule';
3 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
4 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
5 |
6 | export const Header = () => {
7 | const { participantCount } = useParticipants();
8 | const { customCapsule } = useUIState();
9 |
10 | return useMemo(
11 | () => (
12 |
13 |
20 |
21 | {process.env.PROJECT_TITLE}
22 |
23 | {`${participantCount} ${
24 | participantCount === 1 ? 'participant' : 'participants'
25 | }`}
26 |
27 | {customCapsule && (
28 |
29 | {customCapsule.variant === 'recording' && }
30 | {customCapsule.label}
31 |
32 | )}
33 |
34 |
50 |
51 | ),
52 | [participantCount, customCapsule]
53 | );
54 | };
55 |
56 | export default Header;
57 |
--------------------------------------------------------------------------------
/custom/basic-call/components/Call/Room.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import VideoContainer from '@custom/shared/components/VideoContainer/VideoContainer';
3 |
4 | import { Container } from './Container';
5 | import { Header } from './Header';
6 | import { VideoGrid } from './VideoGrid';
7 |
8 | export function Room({ children }) {
9 | return (
10 |
11 |
12 | {children ? children : }
13 |
14 | );
15 | }
16 |
17 | export default Room;
18 |
--------------------------------------------------------------------------------
/custom/basic-call/components/Call/WaitingRoom.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | WaitingRoomModal,
4 | WaitingRoomNotification,
5 | } from '@custom/shared/components/WaitingRoom';
6 | import { useWaitingRoom } from '@custom/shared/contexts/WaitingRoomProvider';
7 |
8 | export const WaitingRoom = () => {
9 | const { setShowModal, showModal } = useWaitingRoom();
10 | return (
11 | <>
12 |
13 | {showModal && setShowModal(false)} />}
14 | >
15 | );
16 | };
17 |
18 | export default WaitingRoom;
19 |
--------------------------------------------------------------------------------
/custom/basic-call/components/Prejoin/NotConfigured.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, CardBody, CardHeader } from '@custom/shared/components/Card';
3 |
4 | export const NotConfigured = () => (
5 |
6 | Environmental variables not set
7 |
8 |
9 | Please ensure you have set both the DAILY_API_KEY
and{' '}
10 | DAILY_DOMAIN
environmental variables. An example can be
11 | found in the provided env.example
file.
12 |
13 |
14 | If you do not yet have a Daily developer account, please{' '}
15 |
20 | create one now
21 |
22 | . You can find your Daily API key on the{' '}
23 |
28 | developer page
29 | {' '}
30 | of the dashboard.
31 |
32 |
33 |
34 | );
35 |
36 | export default NotConfigured;
37 |
--------------------------------------------------------------------------------
/custom/basic-call/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
10 | # Run in demo mode (will create a demo room for you to try)
11 | DAILY_DEMO_MODE=0
--------------------------------------------------------------------------------
/custom/basic-call/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/basic-call/image.png
--------------------------------------------------------------------------------
/custom/basic-call/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/basic-call/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')(['@custom/shared']);
3 |
4 | const packageJson = require('./package.json');
5 |
6 | module.exports = withPlugins([withTM], {
7 | env: {
8 | PROJECT_TITLE: packageJson.description,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/custom/basic-call/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/basic-call",
3 | "description": "Basic Call Example",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "next": "^11.1.2",
15 | "pluralize": "^8.0.0"
16 | },
17 | "devDependencies": {
18 | "babel-plugin-module-resolver": "^4.1.0",
19 | "next-compose-plugins": "^2.2.1",
20 | "next-transpile-modules": "^8.0.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/custom/basic-call/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GlobalStyle from '@custom/shared/components/GlobalStyle';
3 | import Head from 'next/head';
4 | import PropTypes from 'prop-types';
5 |
6 | function App({ Component, pageProps }) {
7 | return (
8 | <>
9 |
10 | Daily - {process.env.PROJECT_TITLE}
11 |
12 |
13 |
20 | >
21 | );
22 | }
23 |
24 | App.defaultProps = {
25 | Component: null,
26 | pageProps: {},
27 | };
28 |
29 | App.propTypes = {
30 | Component: PropTypes.elementType,
31 | pageProps: PropTypes.object,
32 | };
33 |
34 | App.asides = [];
35 | App.modals = [];
36 | App.customTrayComponent = null;
37 | App.customAppComponent = null;
38 |
39 | export default App;
40 |
--------------------------------------------------------------------------------
/custom/basic-call/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | export default MyDocument;
24 |
--------------------------------------------------------------------------------
/custom/basic-call/pages/api/createRoom.js:
--------------------------------------------------------------------------------
1 | export default async function handler(req, res) {
2 | const { privacy, expiryMinutes, ...rest } = req.body;
3 |
4 | if (req.method === 'POST') {
5 | console.log(`Creating room on domain ${process.env.DAILY_DOMAIN}`);
6 |
7 | const options = {
8 | method: 'POST',
9 | headers: {
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
12 | },
13 | body: JSON.stringify({
14 | privacy: privacy || 'public',
15 | properties: {
16 | exp: Math.round(Date.now() / 1000) + (expiryMinutes || 5) * 60, // expire in x minutes
17 | eject_at_room_exp: true,
18 | enable_knocking: privacy !== 'public',
19 | ...rest,
20 | },
21 | }),
22 | };
23 |
24 | const dailyRes = await fetch(
25 | `${process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'}/rooms`,
26 | options
27 | );
28 |
29 | const { name, url, error } = await dailyRes.json();
30 |
31 | if (error) {
32 | return res.status(500).json({ error });
33 | }
34 |
35 | return res
36 | .status(200)
37 | .json({ name, url, domain: process.env.DAILY_DOMAIN });
38 | }
39 |
40 | return res.status(500);
41 | }
42 |
--------------------------------------------------------------------------------
/custom/basic-call/pages/api/token.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is an example server-side function that generates a meeting token
3 | * server-side. You could replace this on your own back-end to include
4 | * custom user authentication, etc.
5 | */
6 | export default async function handler(req, res) {
7 | const { roomName, isOwner } = req.body;
8 |
9 | if (req.method === 'POST' && roomName) {
10 | console.log(`Getting token for room '${roomName}' as owner: ${isOwner}`);
11 |
12 | const options = {
13 | method: 'POST',
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
17 | },
18 | body: JSON.stringify({
19 | properties: { room_name: roomName, is_owner: isOwner },
20 | }),
21 | };
22 |
23 | const dailyRes = await fetch(
24 | `${
25 | process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'
26 | }/meeting-tokens`,
27 | options
28 | );
29 |
30 | const { token, error } = await dailyRes.json();
31 |
32 | if (error) {
33 | return res.status(500).json({ error });
34 | }
35 |
36 | return res.status(200).json({ token, domain: process.env.DAILY_DOMAIN });
37 | }
38 |
39 | return res.status(500);
40 | }
41 |
--------------------------------------------------------------------------------
/custom/basic-call/pages/not-found.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MessageCard from '@custom/shared/components/MessageCard';
3 | import { useRouter } from 'next/router';
4 |
5 | export default function RoomNotFound() {
6 | const router = useRouter();
7 |
8 | const returnToHomePage = () => router.push('/');
9 |
10 | return (
11 |
12 |
13 | The room you are trying to join does not exist. Have you created the
14 | room using the Daily REST API or the dashboard?
15 |
16 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/custom/basic-call/public/assets/daily-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom/basic-call/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/basic-call/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/basic-call/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/basic-call/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/basic-call/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/basic-call/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/fitness-demo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/fitness-demo/.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 | # next.js
12 | .next
13 | out
14 |
15 | # production
16 | build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
--------------------------------------------------------------------------------
/custom/fitness-demo/components/App/AsideHeader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
3 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
4 | import { CHAT_ASIDE } from '../Call/ChatAside';
5 |
6 | export const AsideHeader = () => {
7 | const { showAside, setShowAside } = useUIState();
8 |
9 | return (
10 | <>
11 |
12 |
setShowAside(PEOPLE_ASIDE)}
15 | >
16 |
People
17 |
18 |
setShowAside(CHAT_ASIDE)}
21 | >
22 |
Chat
23 |
24 |
25 |
49 | >
50 | )
51 | };
52 |
53 | export default AsideHeader;
--------------------------------------------------------------------------------
/custom/fitness-demo/components/App/Asides.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NetworkAside } from '@custom/shared/components/Aside';
3 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
4 | import { PeopleAside } from '../Call/PeopleAside';
5 |
6 | export const Asides = () => {
7 | const { asides } = useUIState();
8 |
9 | return (
10 | <>
11 |
12 |
13 | {asides.map((AsideComponent) => (
14 |
15 | ))}
16 | >
17 | );
18 | };
19 |
20 | export default Asides;
21 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/App/Modals.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DeviceSelectModal from '@custom/shared/components/DeviceSelectModal/DeviceSelectModal';
3 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
4 |
5 | export const Modals = () => {
6 | const { modals } = useUIState();
7 |
8 | return (
9 | <>
10 |
11 | {modals.map((ModalComponent) => (
12 |
13 | ))}
14 | >
15 | );
16 | };
17 |
18 | export default Modals;
19 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/App/index.js:
--------------------------------------------------------------------------------
1 | export { App as default } from './App';
2 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Call/Container.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react';
2 | import { Audio } from '@custom/shared/components/Audio';
3 | import { BasicTray } from '@custom/shared/components/Tray';
4 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
5 | import { useJoinSound } from '@custom/shared/hooks/useJoinSound';
6 | import PropTypes from 'prop-types';
7 | import { WaitingRoom } from './WaitingRoom';
8 |
9 | export const Container = ({ children }) => {
10 | const { isOwner } = useParticipants();
11 |
12 | useJoinSound();
13 |
14 | const roomComponents = useMemo(
15 | () => (
16 | <>
17 | {/* Show waiting room notification & modal if call owner */}
18 | {isOwner && }
19 | {/* Tray buttons */}
20 |
21 | {/* Audio tags */}
22 |
23 | >
24 | ),
25 | [isOwner]
26 | );
27 |
28 | return (
29 |
30 | {children}
31 | {roomComponents}
32 |
33 |
41 |
42 | );
43 | };
44 |
45 | Container.propTypes = {
46 | children: PropTypes.node,
47 | };
48 |
49 | export default Container;
50 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Call/Room.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { VideoView } from './VideoView';
3 |
4 | export const Room = () => ;
5 |
6 | export default Room;
7 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Call/VideoView.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
3 | import { useUIState, VIEW_MODE_SPEAKER } from '@custom/shared/contexts/UIStateProvider';
4 | import { GridView } from '../GridView';
5 | import { SpeakerView } from '../SpeakerView';
6 | import InviteOthers from './InviteOthers';
7 |
8 | export const VideoView = () => {
9 | const { viewMode, setIsShowingScreenshare } = useUIState();
10 | const { participants, screens } = useParticipants();
11 |
12 | useEffect(() => {
13 | const hasScreens = screens.length > 0;
14 | setIsShowingScreenshare(hasScreens);
15 | }, [screens, setIsShowingScreenshare]);
16 |
17 | if (!participants.length) return null;
18 | if (participants.length === 1 && !screens.length > 0) return ;
19 |
20 | return viewMode === VIEW_MODE_SPEAKER ? : ;
21 | };
22 |
23 | export default VideoView;
24 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Call/WaitingRoom.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | WaitingRoomModal,
4 | WaitingRoomNotification,
5 | } from '@custom/shared/components/WaitingRoom';
6 | import { useWaitingRoom } from '@custom/shared/contexts/WaitingRoomProvider';
7 |
8 | export const WaitingRoom = () => {
9 | const { setShowModal, showModal } = useWaitingRoom();
10 | return (
11 | <>
12 |
13 | {showModal && setShowModal(false)} />}
14 | >
15 | );
16 | };
17 |
18 | export default WaitingRoom;
19 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/GridView/index.js:
--------------------------------------------------------------------------------
1 | export { GridView as default } from './GridView';
2 | export { GridView } from './GridView';
3 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Prejoin/NotConfigured.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, CardBody, CardHeader } from '@custom/shared/components/Card';
3 |
4 | export const NotConfigured = () => (
5 |
6 | Environmental variables not set
7 |
8 |
9 | Please ensure you have set both the DAILY_API_KEY
and{' '}
10 | DAILY_DOMAIN
environmental variables. An example can be
11 | found in the provided env.example
file.
12 |
13 |
14 | If you do not yet have a Daily developer account, please{' '}
15 |
20 | create one now
21 |
22 | . You can find your Daily API key on the{' '}
23 |
28 | developer page
29 | {' '}
30 | of the dashboard.
31 |
32 |
33 |
34 | );
35 |
36 | export default NotConfigured;
37 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/SpeakerView/ScreensAndPins/ScreenPinTile.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Tile from '@custom/shared/components/Tile';
3 |
4 | export const ScreenPinTile = ({
5 | height,
6 | hideName = false,
7 | item,
8 | maxWidth,
9 | ratio: initialRatio,
10 | }) => {
11 | const [ratio, setRatio] = useState(initialRatio);
12 | const handleResize = (aspectRatio) => setRatio(aspectRatio);
13 |
14 | if (item.isScreenshare) {
15 | return (
16 |
26 | );
27 | }
28 |
29 | return (
30 |
39 | );
40 | };
41 |
42 | export default ScreenPinTile;
--------------------------------------------------------------------------------
/custom/fitness-demo/components/SpeakerView/ScreensAndPins/index.js:
--------------------------------------------------------------------------------
1 | export { ScreensAndPins } from './ScreensAndPins';
--------------------------------------------------------------------------------
/custom/fitness-demo/components/SpeakerView/SpeakerTile/index.js:
--------------------------------------------------------------------------------
1 | export { SpeakerTile as default } from './SpeakerTile';
2 | export { SpeakerTile } from './SpeakerTile';
3 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/SpeakerView/index.js:
--------------------------------------------------------------------------------
1 | export { SpeakerView as default } from './SpeakerView';
2 | export { SpeakerView } from './SpeakerView';
3 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Tray/Chat.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
5 | import { ReactComponent as IconChat } from '@custom/shared/icons/chat-md.svg';
6 | import { useChat } from '../../contexts/ChatProvider';
7 | import { CHAT_ASIDE } from '../Call/ChatAside';
8 |
9 | export const ChatTray = () => {
10 | const { toggleAside } = useUIState();
11 | const { hasNewMessages } = useChat();
12 |
13 | return (
14 | toggleAside(CHAT_ASIDE)}
18 | >
19 |
20 |
21 | );
22 | };
23 |
24 | export default ChatTray;
25 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Tray/Record.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import { RECORDING_MODAL } from '@custom/recording/components/RecordingModal';
4 | import {
5 | RECORDING_ERROR,
6 | RECORDING_RECORDING,
7 | RECORDING_SAVED,
8 | RECORDING_UPLOADING,
9 | useRecording,
10 | } from '@custom/recording/contexts/RecordingProvider';
11 | import { TrayButton } from '@custom/shared/components/Tray';
12 | import { useCallState } from '@custom/shared/contexts/CallProvider';
13 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
14 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
15 | import { ReactComponent as IconRecord } from '@custom/shared/icons/record-md.svg';
16 |
17 |
18 | export const Tray = () => {
19 | const { enableRecording } = useCallState();
20 | const { openModal } = useUIState();
21 | const { recordingState } = useRecording();
22 | const { localParticipant } = useParticipants();
23 |
24 | useEffect(() => {
25 | console.log(`⏺️ Recording state: ${recordingState}`);
26 |
27 | if (recordingState === RECORDING_ERROR) {
28 | // show error modal here
29 | }
30 | }, [recordingState]);
31 |
32 | const isRecording = [
33 | RECORDING_RECORDING,
34 | RECORDING_UPLOADING,
35 | RECORDING_SAVED,
36 | ].includes(recordingState);
37 |
38 | if (!enableRecording) return null;
39 | if (!localParticipant.isOwner) return null;
40 |
41 | return (
42 | openModal(RECORDING_MODAL)}
46 | >
47 |
48 |
49 | );
50 | };
51 |
52 | export default Tray;
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Tray/ScreenShare.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useCallState } from '@custom/shared/contexts/CallProvider';
5 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
6 | import { useScreenShare } from '@custom/shared/contexts/ScreenShareProvider';
7 | import { ReactComponent as IconShare } from '@custom/shared/icons/share-sm.svg';
8 |
9 | export const ScreenShareTray = () => {
10 | const { enableScreenShare } = useCallState();
11 | const { localParticipant } = useParticipants();
12 | const {
13 | isSharingScreen,
14 | isDisabled,
15 | startScreenShare,
16 | stopScreenShare
17 | } = useScreenShare();
18 |
19 | const toggleScreenShare = () =>
20 | isSharingScreen ? stopScreenShare() : startScreenShare();
21 |
22 | if (!enableScreenShare) return null;
23 | if (!localParticipant.isOwner) return null;
24 |
25 | return (
26 |
32 |
33 |
34 | );
35 | };
36 |
37 | export default ScreenShareTray;
38 |
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Tray/Stream.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LIVE_STREAMING_MODAL } from '@custom/live-streaming/components/LiveStreamingModal';
4 | import { TrayButton } from '@custom/shared/components/Tray';
5 | import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
6 | import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
7 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
8 | import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
9 |
10 |
11 | export const Stream = () => {
12 | const { openModal } = useUIState();
13 | const { isStreaming } = useLiveStreaming();
14 | const { localParticipant } = useParticipants();
15 |
16 | if (!localParticipant.isOwner) return null;
17 |
18 | return (
19 | openModal(LIVE_STREAMING_MODAL)}
23 | >
24 |
25 |
26 | );
27 | };
28 |
29 | export default Stream;
--------------------------------------------------------------------------------
/custom/fitness-demo/components/Tray/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ChatTray from './Chat';
3 | import RecordTray from './Record';
4 | import ScreenShareTray from './ScreenShare';
5 | import StreamTray from './Stream';
6 |
7 | export const Tray = () => {
8 | return (
9 | <>
10 |
11 |
12 |
13 |
14 | >
15 | );
16 | };
17 |
18 | export default Tray;
19 |
--------------------------------------------------------------------------------
/custom/fitness-demo/contexts/ClassStateProvider.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useContext,
4 | useCallback,
5 | useMemo,
6 | useEffect,
7 | } from 'react';
8 | import { useUIState, VIEW_MODE_SPEAKER, VIEW_MODE_GRID } from '@custom/shared/contexts/UIStateProvider';
9 | import { useSharedState } from '@custom/shared/hooks/useSharedState';
10 | import PropTypes from 'prop-types';
11 |
12 | export const PRE_CLASS_LOBBY = 'Pre-class lobby';
13 | export const CLASS_IN_SESSION = 'Class-in session';
14 | export const POST_CLASS_LOBBY = 'Post-class lobby';
15 |
16 | export const ClassStateContext = createContext();
17 |
18 | export const ClassStateProvider = ({ children }) => {
19 | const { setPreferredViewMode } = useUIState();
20 |
21 | const { sharedState, setSharedState } = useSharedState({
22 | initialValues: { type: PRE_CLASS_LOBBY },
23 | });
24 |
25 | const classType = useMemo(() => sharedState.type, [sharedState.type]);
26 |
27 | const setClassType = useCallback(() => {
28 | if (sharedState.type === PRE_CLASS_LOBBY) setSharedState({ type: CLASS_IN_SESSION });
29 | if (sharedState.type === CLASS_IN_SESSION) setSharedState({ type: POST_CLASS_LOBBY });
30 | }, [sharedState.type, setSharedState]);
31 |
32 | useEffect(() => {
33 | if (sharedState.type === CLASS_IN_SESSION) setPreferredViewMode(VIEW_MODE_SPEAKER);
34 | else setPreferredViewMode(VIEW_MODE_GRID);
35 | }, [setPreferredViewMode, sharedState.type]);
36 |
37 | return (
38 |
39 | {children}
40 |
41 | );
42 | };
43 |
44 | ClassStateProvider.propTypes = {
45 | children: PropTypes.node,
46 | };
47 |
48 | export const useClassState = () => useContext(ClassStateContext);
--------------------------------------------------------------------------------
/custom/fitness-demo/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
10 | # Enable manual track subscriptions
11 | MANUAL_TRACK_SUBS=1
--------------------------------------------------------------------------------
/custom/fitness-demo/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/fitness-demo/image.png
--------------------------------------------------------------------------------
/custom/fitness-demo/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/fitness-demo/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | '@custom/text-chat',
6 | '@custom/live-streaming',
7 | '@custom/recording',
8 | ]);
9 |
10 | const packageJson = require('./package.json');
11 |
12 | module.exports = withPlugins([withTM], {
13 | env: {
14 | PROJECT_TITLE: packageJson.description,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/custom/fitness-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/fitness-demo",
3 | "description": "Basic Call + Fitness Demo",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "@custom/text-chat": "*",
16 | "@custom/live-streaming": "*",
17 | "@custom/recording": "*",
18 | "next": "^11.1.2",
19 | "pluralize": "^8.0.0"
20 | },
21 | "devDependencies": {
22 | "babel-plugin-module-resolver": "^4.1.0",
23 | "next-compose-plugins": "^2.2.1",
24 | "next-transpile-modules": "^8.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LiveStreamingModal from '@custom/live-streaming/components/LiveStreamingModal';
3 | import RecordingModal from '@custom/recording/components/RecordingModal';
4 | import GlobalStyle from '@custom/shared/components/GlobalStyle';
5 | import Head from 'next/head';
6 | import PropTypes from 'prop-types';
7 | import { App as CustomApp } from '../components/App/App';
8 | import ChatAside from '../components/Call/ChatAside';
9 | import Tray from '../components/Tray';
10 |
11 | function App({ Component, pageProps }) {
12 | return (
13 | <>
14 |
15 | Daily - {process.env.PROJECT_TITLE}
16 |
17 |
18 |
25 | >
26 | );
27 | }
28 |
29 | App.defaultProps = {
30 | Component: null,
31 | pageProps: {},
32 | };
33 |
34 | App.propTypes = {
35 | Component: PropTypes.elementType,
36 | pageProps: PropTypes.object,
37 | };
38 |
39 | App.asides = [ChatAside];
40 | App.modals = [RecordingModal, LiveStreamingModal];
41 | App.customTrayComponent = ;
42 | App.customAppComponent = ;
43 |
44 | export default App;
45 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 |
3 | class MyDocument extends Document {
4 | render() {
5 | return (
6 |
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | export default MyDocument;
24 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/api/createRoom.js:
--------------------------------------------------------------------------------
1 | export default async function handler(req, res) {
2 | const { roomName, privacy, expiryMinutes, ...rest } = req.body;
3 |
4 | if (req.method === 'POST') {
5 | console.log(`Creating room on domain ${process.env.DAILY_DOMAIN}`);
6 |
7 | const options = {
8 | method: 'POST',
9 | headers: {
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
12 | },
13 | body: JSON.stringify({
14 | name: roomName,
15 | privacy: privacy || 'public',
16 | properties: {
17 | exp: Math.round(Date.now() / 1000) + (expiryMinutes || 5) * 60, // expire in x minutes
18 | eject_at_room_exp: true,
19 | enable_knocking: privacy !== 'public',
20 | enable_screenshare: true,
21 | enable_recording: 'local',
22 | ...rest,
23 | },
24 | }),
25 | };
26 |
27 | const dailyRes = await fetch(
28 | `${process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'}/rooms`,
29 | options
30 | );
31 |
32 | const { name, url, error } = await dailyRes.json();
33 |
34 | if (error) {
35 | return res.status(500).json({ error });
36 | }
37 |
38 | return res
39 | .status(200)
40 | .json({ name, url, domain: process.env.DAILY_DOMAIN });
41 | }
42 |
43 | return res.status(500);
44 | }
45 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/api/editRoom.js:
--------------------------------------------------------------------------------
1 | export default async function handler(req, res) {
2 | const { roomName } = req.query;
3 | const { privacy, expiryMinutes, ...rest } = req.body;
4 |
5 | if (req.method === 'POST') {
6 | console.log(`Modifying room on domain ${process.env.DAILY_DOMAIN}`);
7 |
8 | const options = {
9 | method: 'POST',
10 | headers: {
11 | 'Content-Type': 'application/json',
12 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
13 | },
14 | body: JSON.stringify({
15 | privacy: privacy || 'public',
16 | properties: {
17 | exp: Math.round(Date.now() / 1000) + (expiryMinutes || 5) * 60, // expire in x minutes
18 | eject_at_room_exp: true,
19 | enable_knocking: privacy !== 'public',
20 | ...rest,
21 | },
22 | }),
23 | };
24 |
25 | const dailyRes = await fetch(
26 | `${process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'}/rooms/${roomName}`,
27 | options
28 | );
29 |
30 | const { name, url, error } = await dailyRes.json();
31 |
32 | if (error) {
33 | return res.status(500).json({ error });
34 | }
35 |
36 | return res
37 | .status(200)
38 | .json({ name, url, domain: process.env.DAILY_DOMAIN });
39 | }
40 |
41 | return res.status(500);
42 | }
43 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/api/presence.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is an example server-side function that provides the real-time presence
3 | * data of all the active rooms in the given domain.
4 | */
5 | export default async function handler(req, res) {
6 | if (req.method === 'GET') {
7 | const options = {
8 | method: 'GET',
9 | headers: {
10 | 'Content-Type': 'application/json',
11 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
12 | },
13 | };
14 |
15 | const dailyRes = await fetch(
16 | `${
17 | process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'
18 | }/presence`,
19 | options
20 | );
21 |
22 | const response = await dailyRes.json();
23 | return res.status(200).json(response);
24 | }
25 |
26 | return res.status(500);
27 | }
28 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/api/room.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is an example server-side function that retrieves the room object.
3 | */
4 | export default async function handler(req, res) {
5 | const { name } = req.query;
6 |
7 | if (req.method === 'GET') {
8 | const options = {
9 | method: 'GET',
10 | headers: {
11 | 'Content-Type': 'application/json',
12 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
13 | },
14 | };
15 |
16 | const dailyRes = await fetch(
17 | `${
18 | process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'
19 | }/rooms/${name}`,
20 | options
21 | );
22 |
23 | const response = await dailyRes.json();
24 | return res.status(200).json(response);
25 | }
26 |
27 | return res.status(500);
28 | }
29 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/api/token.js:
--------------------------------------------------------------------------------
1 | /*
2 | * This is an example server-side function that generates a meeting token
3 | * server-side. You could replace this on your own back-end to include
4 | * custom user authentication, etc.
5 | */
6 | export default async function handler(req, res) {
7 | const { roomName, isOwner } = req.body;
8 |
9 | if (req.method === 'POST' && roomName) {
10 | console.log(`Getting token for room '${roomName}' as owner: ${isOwner}`);
11 |
12 | const options = {
13 | method: 'POST',
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
17 | },
18 | body: JSON.stringify({
19 | properties: { room_name: roomName, is_owner: isOwner },
20 | }),
21 | };
22 |
23 | const dailyRes = await fetch(
24 | `${
25 | process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'
26 | }/meeting-tokens`,
27 | options
28 | );
29 |
30 | const { token, error } = await dailyRes.json();
31 |
32 | if (error) {
33 | return res.status(500).json({ error });
34 | }
35 |
36 | return res.status(200).json({ token, domain: process.env.DAILY_DOMAIN });
37 | }
38 |
39 | return res.status(500);
40 | }
41 |
--------------------------------------------------------------------------------
/custom/fitness-demo/pages/not-found.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MessageCard from '@custom/shared/components/MessageCard';
3 |
4 | export default function RoomNotFound() {
5 | return (
6 |
7 |
8 | The room you are trying to join does not exist. Have you created the
9 | room using the Daily REST API or the dashboard?
10 |
11 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/custom/fitness-demo/public/assets/daily-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom/fitness-demo/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/fitness-demo/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/fitness-demo/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/fitness-demo/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/fitness-demo/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/fitness-demo/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/fitness-demo/public/assets/pattern-ls.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom/flying-emojis/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/flying-emojis/README.md:
--------------------------------------------------------------------------------
1 | # Flying Emojis
2 |
3 | 
4 |
5 | ### Live example
6 |
7 | **[See it in action here ➡️](https://custom-flying-emojis.vercel.app)**
8 |
9 | ---
10 |
11 | ## What does this demo do?
12 |
13 | - Use [sendAppMessage](https://docs.daily.co/reference#%EF%B8%8F-sendappmessage) to send flying emojis to all clients
14 | - Implements a custom ` ` that adds ` ` component that listens for incoming emoji events and appends a new node to the DOM
15 | - Todo: pool emoji DOM nodes to optimise on DOM mutations
16 |
17 | Please note: this demo is not currently mobile optimised
18 |
19 | ### Getting started
20 |
21 | ```
22 | # set both DAILY_API_KEY and DAILY_DOMAIN
23 | mv env.example .env.local
24 |
25 | yarn
26 | yarn workspace @custom/flying-emojis dev
27 | ```
28 |
29 | ## Deploy your own on Vercel
30 |
31 | [](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
32 |
--------------------------------------------------------------------------------
/custom/flying-emojis/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/components/App';
3 | import FlyingEmojiOverlay from './FlyingEmojisOverlay';
4 |
5 | export const AppWithEmojis = () => (
6 | <>
7 |
8 |
9 | >
10 | );
11 |
12 | export default AppWithEmojis;
13 |
--------------------------------------------------------------------------------
/custom/flying-emojis/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
--------------------------------------------------------------------------------
/custom/flying-emojis/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/flying-emojis/image.png
--------------------------------------------------------------------------------
/custom/flying-emojis/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/flying-emojis/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/flying-emojis/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/flying-emojis",
3 | "description": "Basic Call + Flying Emojis",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/basic-call": "*",
14 | "@custom/shared": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/flying-emojis/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithEmojis from '../components/App';
4 | import Tray from '../components/Tray';
5 |
6 | App.customAppComponent = ;
7 | App.customTrayComponent = ;
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/custom/flying-emojis/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Doc from '@custom/basic-call/pages/_document';
2 |
3 | export default Doc;
4 |
--------------------------------------------------------------------------------
/custom/flying-emojis/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/flying-emojis/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | // Pass through domain as prop
8 | return {
9 | props: defaultProps,
10 | };
11 | }
12 |
13 | export default Index;
14 |
--------------------------------------------------------------------------------
/custom/flying-emojis/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/flying-emojis/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/flying-emojis/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/flying-emojis/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/flying-emojis/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/flying-emojis/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/live-streaming/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/live-streaming/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider';
5 |
6 | // Extend our basic call app component with the live streaming context
7 | export const AppWithLiveStreaming = () => (
8 |
9 |
10 |
11 | );
12 |
13 | export default AppWithLiveStreaming;
14 |
--------------------------------------------------------------------------------
/custom/live-streaming/components/Tray.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
5 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
6 | import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
7 |
8 | import { LIVE_STREAMING_MODAL } from './LiveStreamingModal';
9 |
10 | export const Tray = () => {
11 | const { openModal } = useUIState();
12 | const { isStreaming } = useLiveStreaming();
13 |
14 | return (
15 | openModal(LIVE_STREAMING_MODAL)}
19 | >
20 |
21 |
22 | );
23 | };
24 |
25 | export default Tray;
26 |
--------------------------------------------------------------------------------
/custom/live-streaming/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
--------------------------------------------------------------------------------
/custom/live-streaming/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-streaming/image.png
--------------------------------------------------------------------------------
/custom/live-streaming/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/live-streaming/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/live-streaming/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/live-streaming",
3 | "description": "Basic Call + Live Streaming",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/live-streaming/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithLiveStreaming from '../components/App';
4 |
5 | import { LiveStreamingModal } from '../components/LiveStreamingModal';
6 | import Tray from '../components/Tray';
7 |
8 | App.modals = [LiveStreamingModal];
9 | App.customAppComponent = ;
10 | App.customTrayComponent = ;
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/custom/live-streaming/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Doc from '@custom/basic-call/pages/_document';
2 |
3 | export default Doc;
4 |
--------------------------------------------------------------------------------
/custom/live-streaming/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/live-streaming/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | return {
8 | props: {
9 | ...defaultProps,
10 | forceFetchToken: true,
11 | forceOwner: true,
12 | },
13 | };
14 | }
15 |
16 | export default Index;
17 |
--------------------------------------------------------------------------------
/custom/live-streaming/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-streaming/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/live-streaming/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-streaming/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/live-streaming/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-streaming/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/live-transcription/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/live-transcription/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import { TranscriptionProvider } from '@custom/shared/contexts/TranscriptionProvider';
5 |
6 | // Extend our basic call app component with the Live Transcription context
7 | export const AppWithTranscription = () => (
8 |
9 |
10 |
11 | );
12 |
13 | export default AppWithTranscription;
14 |
--------------------------------------------------------------------------------
/custom/live-transcription/components/Tray.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useTranscription } from '@custom/shared/contexts/TranscriptionProvider';
5 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
6 | import { ReactComponent as IconTranscription } from '@custom/shared/icons/chat-md.svg';
7 | import { TRANSCRIPTION_ASIDE } from './TranscriptionAside';
8 |
9 | export const Tray = () => {
10 | const { toggleAside } = useUIState();
11 | const { hasNewMessages } = useTranscription();
12 |
13 | return (
14 | {
18 | toggleAside(TRANSCRIPTION_ASIDE);
19 | }}
20 | >
21 |
22 |
23 | );
24 | };
25 |
26 | export default Tray;
27 |
--------------------------------------------------------------------------------
/custom/live-transcription/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
--------------------------------------------------------------------------------
/custom/live-transcription/image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-transcription/image.gif
--------------------------------------------------------------------------------
/custom/live-transcription/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/live-transcription/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/live-transcription/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/live-transcription",
3 | "description": "Basic Call + Transcription Example",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/live-transcription/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithTranscription from '../components/App';
4 |
5 | import TranscriptionAside from '../components/TranscriptionAside';
6 | import Tray from '../components/Tray';
7 |
8 | App.asides = [TranscriptionAside];
9 | App.customAppComponent = ;
10 | App.customTrayComponent = ;
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/custom/live-transcription/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/live-transcription/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | // Pass through domain as prop
8 | return {
9 | props: {
10 | ...defaultProps,
11 | forceFetchToken: true,
12 | forceOwner: true,
13 | },
14 | };
15 | }
16 |
17 | export default Index;
18 |
--------------------------------------------------------------------------------
/custom/live-transcription/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-transcription/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/live-transcription/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-transcription/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/live-transcription/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/live-transcription/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/pagination/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/pagination/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import Room from '@custom/basic-call/components/Room';
5 | import PaginatedVideoGrid from '../PaginatedVideoGrid';
6 |
7 | export const AppWithPagination = () => (
8 | ,
11 | }}
12 | />
13 | );
14 |
15 | export default AppWithPagination;
16 |
--------------------------------------------------------------------------------
/custom/pagination/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import Room from '@custom/basic-call/components/Call/Room';
5 | import PaginatedVideoGrid from './PaginatedVideoGrid';
6 |
7 | export const AppWithPagination = () => (
8 |
12 |
13 |
14 | ),
15 | }}
16 | />
17 | );
18 |
19 | export default AppWithPagination;
20 |
--------------------------------------------------------------------------------
/custom/pagination/components/Tray.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useCallState } from '@custom/shared/contexts/CallProvider';
5 | import { ReactComponent as IconAdd } from '@custom/shared/icons/add-md.svg';
6 |
7 | export const Tray = () => {
8 | const { callObject } = useCallState();
9 |
10 | return (
11 | <>
12 | {
15 | callObject.addFakeParticipant();
16 | }}
17 | >
18 |
19 |
20 | >
21 | );
22 | };
23 |
24 | export default Tray;
25 |
--------------------------------------------------------------------------------
/custom/pagination/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
10 | # Enable manual track subscriptions
11 | MANUAL_TRACK_SUBS=1
--------------------------------------------------------------------------------
/custom/pagination/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/pagination/image.png
--------------------------------------------------------------------------------
/custom/pagination/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/pagination/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/pagination",
3 | "description": "Basic Call + Pagination",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/pagination/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/pages/_app';
4 | import AppWithPagination from '../components/App';
5 |
6 | import Tray from '../components/Tray';
7 |
8 | App.customTrayComponent = ;
9 | App.customAppComponent = ;
10 |
11 | export default App;
12 |
--------------------------------------------------------------------------------
/custom/pagination/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Doc from '@custom/basic-call/pages/_document';
2 |
3 | export default Doc;
4 |
--------------------------------------------------------------------------------
/custom/pagination/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/pagination/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | return {
8 | props: {
9 | ...defaultProps,
10 | forceFetchToken: true,
11 | forceOwner: true,
12 | },
13 | };
14 | }
15 |
16 | export default Index;
17 |
--------------------------------------------------------------------------------
/custom/pagination/public/assets/daily-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom/pagination/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/pagination/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/pagination/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/pagination/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/pagination/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/pagination/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/recording/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/recording/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import { RecordingProvider } from '../contexts/RecordingProvider';
5 |
6 | // Extend our basic call app component with the recording context
7 | export const AppWithRecording = () => (
8 |
9 |
10 |
11 | );
12 |
13 | export default AppWithRecording;
14 |
--------------------------------------------------------------------------------
/custom/recording/components/Tray.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
5 | import { ReactComponent as IconRecord } from '@custom/shared/icons/record-md.svg';
6 |
7 | import {
8 | RECORDING_ERROR,
9 | RECORDING_RECORDING,
10 | RECORDING_SAVED,
11 | RECORDING_UPLOADING,
12 | useRecording,
13 | } from '../contexts/RecordingProvider';
14 | import { RECORDING_MODAL } from './RecordingModal';
15 |
16 | export const Tray = () => {
17 | const { openModal } = useUIState();
18 | const { recordingState } = useRecording();
19 |
20 | useEffect(() => {
21 | console.log(`⏺️ Recording state: ${recordingState}`);
22 |
23 | if (recordingState === RECORDING_ERROR) {
24 | // show error modal here
25 | }
26 | }, [recordingState]);
27 |
28 | const isRecording = [
29 | RECORDING_RECORDING,
30 | RECORDING_UPLOADING,
31 | RECORDING_SAVED,
32 | ].includes(recordingState);
33 |
34 | return (
35 | openModal(RECORDING_MODAL)}
39 | >
40 |
41 |
42 | );
43 | };
44 |
45 | export default Tray;
46 |
--------------------------------------------------------------------------------
/custom/recording/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
--------------------------------------------------------------------------------
/custom/recording/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/recording/image.png
--------------------------------------------------------------------------------
/custom/recording/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/recording/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/recording/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/recording",
3 | "description": "Basic Call + Recording",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/recording/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithRecording from '../components/App';
4 |
5 | import { RecordingModal } from '../components/RecordingModal';
6 | import Tray from '../components/Tray';
7 |
8 | App.modals = [RecordingModal];
9 | App.customAppComponent = ;
10 | App.customTrayComponent = ;
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/custom/recording/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Doc from '@custom/basic-call/pages/_document';
2 |
3 | export default Doc;
4 |
--------------------------------------------------------------------------------
/custom/recording/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/recording/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | return {
8 | props: {
9 | ...defaultProps,
10 | forceFetchToken: true,
11 | forceOwner: true,
12 | },
13 | };
14 | }
15 |
16 | export default Index;
17 |
--------------------------------------------------------------------------------
/custom/recording/public/assets/daily-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom/recording/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/recording/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/recording/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/recording/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/recording/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/recording/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/shared/components/Aside/Aside.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { ReactComponent as IconClose } from '../../icons/close-sm.svg';
4 | import Button from '../Button';
5 |
6 | export const ASIDE_WIDTH = 380;
7 |
8 | export const Aside = ({ onClose, children }) => (
9 |
10 | {children}
11 |
12 |
18 |
19 |
20 |
21 |
55 |
56 | );
57 |
58 | Aside.propTypes = {
59 | children: PropTypes.node,
60 | onClose: PropTypes.func,
61 | };
62 |
63 | export default Aside;
64 |
--------------------------------------------------------------------------------
/custom/shared/components/Aside/index.js:
--------------------------------------------------------------------------------
1 | export { Aside } from './Aside';
2 | export { PeopleAside } from './PeopleAside';
3 | export { NetworkAside } from './NetworkAside';
4 |
--------------------------------------------------------------------------------
/custom/shared/components/Audio/AudioTrack.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react';
2 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
3 | import PropTypes from 'prop-types';
4 |
5 | export const AudioTrack = ({ track }) => {
6 | const audioRef = useRef(null);
7 | const { setShowAutoplayFailedModal } = useUIState();
8 |
9 | useEffect(() => {
10 | const audioTag = audioRef.current;
11 | if (!audioTag) return false;
12 | let playTimeout;
13 |
14 | const handleCanPlay = () => {
15 | playTimeout = setTimeout(() => {
16 | setShowAutoplayFailedModal(true);
17 | }, 1500);
18 | };
19 | const handlePlay = () => {
20 | clearTimeout(playTimeout);
21 | };
22 | audioTag.addEventListener('canplay', handleCanPlay);
23 | audioTag.addEventListener('play', handlePlay);
24 | audioTag.srcObject = new MediaStream([track]);
25 |
26 | return () => {
27 | audioTag?.removeEventListener('canplay', handleCanPlay);
28 | audioTag?.removeEventListener('play', handlePlay);
29 | };
30 | }, [setShowAutoplayFailedModal, track]);
31 |
32 | return track ? : null;
33 | };
34 |
35 | AudioTrack.propTypes = {
36 | track: PropTypes.object,
37 | };
38 |
39 | export default AudioTrack;
40 |
--------------------------------------------------------------------------------
/custom/shared/components/Audio/index.js:
--------------------------------------------------------------------------------
1 | export { Audio } from './Audio';
2 | export { AudioTrack } from './AudioTrack';
3 | export { CombinedAudioTrack } from './CombinedAudioTrack';
4 |
--------------------------------------------------------------------------------
/custom/shared/components/Button/index.js:
--------------------------------------------------------------------------------
1 | export { Button as default } from './Button';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Capsule/Capsule.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import PropTypes from 'prop-types';
4 |
5 | export const Capsule = ({ children, variant }) => (
6 |
7 | {children}
8 |
35 |
36 | );
37 |
38 | Capsule.propTypes = {
39 | children: PropTypes.node,
40 | variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']),
41 | };
42 |
43 | export default Capsule;
44 |
--------------------------------------------------------------------------------
/custom/shared/components/Capsule/index.js:
--------------------------------------------------------------------------------
1 | export { Capsule as default } from './Capsule';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Card/index.js:
--------------------------------------------------------------------------------
1 | export { Card, CardHeader, CardBody, CardFooter } from './Card';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/DeviceSelect/index.js:
--------------------------------------------------------------------------------
1 | export { DeviceSelect as default } from './DeviceSelect';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/DeviceSelectModal/DeviceSelectModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from '@custom/shared/components/Modal';
3 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
4 | import Button from '../Button';
5 | import DeviceSelect from '../DeviceSelect';
6 |
7 | export const DEVICE_MODAL = 'device';
8 |
9 | export const DeviceSelectModal = () => {
10 | const { currentModals, closeModal } = useUIState();
11 |
12 | return (
13 | closeModal(DEVICE_MODAL)}
17 | actions={[
18 |
19 | Cancel
20 | ,
21 |
22 | Update
23 | ,
24 | ]}
25 | >
26 |
27 |
28 | );
29 | };
30 |
31 | export default DeviceSelectModal;
32 |
--------------------------------------------------------------------------------
/custom/shared/components/DeviceSelectModal/index.js:
--------------------------------------------------------------------------------
1 | export { DeviceSelectModal } from './DeviceSelectModal';
2 | export { DEVICE_MODAL } from './DeviceSelectModal';
3 |
--------------------------------------------------------------------------------
/custom/shared/components/ExpiryTimer/ExpiryTimer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const ExpiryTimer = ({ expiry }) => {
5 | const [secs, setSecs] = useState('--:--');
6 |
7 | // If room has an expiry time, we'll calculate how many seconds until expiry
8 | useEffect(() => {
9 | if (!expiry) {
10 | return false;
11 | }
12 | const i = setInterval(() => {
13 | const timeLeft = Math.round((expiry - Date.now()) / 1000);
14 | if (timeLeft < 0) {
15 | return setSecs(null);
16 | }
17 | setSecs(`${Math.floor(timeLeft / 60)}:${`0${timeLeft % 60}`.slice(-2)}`);
18 | }, 1000);
19 |
20 | return () => clearInterval(i);
21 | }, [expiry]);
22 |
23 | if (!secs) {
24 | return null;
25 | }
26 |
27 | return (
28 |
29 | {secs}
30 |
46 |
47 | );
48 | };
49 |
50 | ExpiryTimer.propTypes = {
51 | expiry: PropTypes.number,
52 | };
53 |
54 | export default ExpiryTimer;
55 |
--------------------------------------------------------------------------------
/custom/shared/components/ExpiryTimer/index.js:
--------------------------------------------------------------------------------
1 | export { ExpiryTimer as default } from './ExpiryTimer';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Field/Field.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const Field = ({ label, children }) => (
5 |
6 | {label &&
{label}
}
7 |
{children}
8 |
9 |
20 |
21 | );
22 |
23 | Field.propTypes = {
24 | label: PropTypes.string,
25 | children: PropTypes.node,
26 | };
27 |
28 | export default Field;
29 |
--------------------------------------------------------------------------------
/custom/shared/components/Field/index.js:
--------------------------------------------------------------------------------
1 | export { Field as default } from './Field';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/GlobalStyle/index.js:
--------------------------------------------------------------------------------
1 | export { GlobalStyle as default } from './GlobalStyle';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/HairCheck/index.js:
--------------------------------------------------------------------------------
1 | export { HairCheck as default } from './HairCheck';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export { Header as default } from './Header';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/HeaderCapsule/HeaderCapsule.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classNames from 'classnames';
4 | import PropTypes from 'prop-types';
5 |
6 | export const HeaderCapsule = ({ children, variant }) => {
7 | const cx = classNames('capsule', variant);
8 |
9 | return (
10 |
11 | {children}
12 |
56 |
57 | );
58 | };
59 |
60 | HeaderCapsule.propTypes = {
61 | children: PropTypes.node,
62 | variant: PropTypes.string,
63 | };
64 |
65 | export default HeaderCapsule;
66 |
--------------------------------------------------------------------------------
/custom/shared/components/HeaderCapsule/index.js:
--------------------------------------------------------------------------------
1 | export { HeaderCapsule as default } from './HeaderCapsule';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Input/index.js:
--------------------------------------------------------------------------------
1 | export { TextInput, BooleanInput, SelectInput } from './Input';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Loader/index.js:
--------------------------------------------------------------------------------
1 | export { Loader as default } from './Loader';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/MessageCard/MessageCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@custom/shared/components/Button';
3 | import {
4 | Card,
5 | CardBody,
6 | CardFooter,
7 | CardHeader,
8 | } from '@custom/shared/components/Card';
9 | import PropTypes from 'prop-types';
10 |
11 | export const MessageCard = ({
12 | header,
13 | children,
14 | error = false,
15 | hideBack = false,
16 | onBack,
17 | }) => (
18 |
19 | {header && {header} }
20 | {children && {children} }
21 | {!hideBack && (
22 |
23 | {onBack ? (
24 | onBack()}>Go back
25 | ) : (
26 | window.location.reload()}>Go back
27 | )}
28 |
29 | )}
30 |
35 |
36 | );
37 |
38 | MessageCard.propTypes = {
39 | header: PropTypes.string,
40 | children: PropTypes.node,
41 | error: PropTypes.bool,
42 | onBack: PropTypes.func,
43 | hideBack: PropTypes.bool,
44 | };
45 |
46 | export default MessageCard;
47 |
--------------------------------------------------------------------------------
/custom/shared/components/MessageCard/index.js:
--------------------------------------------------------------------------------
1 | export { MessageCard as default } from './MessageCard';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/Modal/index.js:
--------------------------------------------------------------------------------
1 | export { Modal as default } from './Modal';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/MuteButton/MuteButton.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ReactComponent as IconCameraOff } from '@custom/shared/icons/camera-off-md.svg';
3 | import { ReactComponent as IconCameraOn } from '@custom/shared/icons/camera-on-md.svg';
4 | import { ReactComponent as IconMicOff } from '@custom/shared/icons/mic-off-md.svg';
5 | import { ReactComponent as IconMicOn } from '@custom/shared/icons/mic-on-md.svg';
6 | import classNames from 'classnames';
7 | import PropTypes from 'prop-types';
8 | import { useCallState } from '../../contexts/CallProvider';
9 | import Button from '../Button';
10 |
11 | export const MuteButton = ({ isMuted, mic = false, className, disabled = false, ...props }) => {
12 | const { callObject } = useCallState();
13 | const [muted, setMuted] = useState(!isMuted);
14 |
15 | const toggleDevice = (newState) => {
16 | if (mic) {
17 | callObject.setLocalAudio(newState);
18 | } else {
19 | callObject.setLocalVideo(newState);
20 | }
21 |
22 | setMuted(newState);
23 | };
24 |
25 | const Icon = mic
26 | ? [ , ]
27 | : [ , ];
28 |
29 | if (!callObject) return null;
30 |
31 | const cx = classNames(className, { muted: disabled || !muted });
32 |
33 | return (
34 | toggleDevice(!muted)}
41 | >
42 | {disabled ? Icon[0] : Icon[+muted]}
43 |
44 | );
45 | };
46 |
47 | MuteButton.propTypes = {
48 | isMuted: PropTypes.bool,
49 | mic: PropTypes.bool,
50 | className: PropTypes.string,
51 | };
52 |
53 | export default MuteButton;
54 |
--------------------------------------------------------------------------------
/custom/shared/components/MuteButton/index.js:
--------------------------------------------------------------------------------
1 | export { MuteButton as default } from './MuteButton';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/ParticipantBar/index.js:
--------------------------------------------------------------------------------
1 | export { ParticipantBar as default } from './ParticipantBar';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/ParticipantBar/useBlockScrolling.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | /**
4 | * Takes a ref to the scrolling element in ParticipantBar.
5 | * Observes DOM changes and returns true, if a TileActions menu is opened.
6 | * @returns boolean
7 | */
8 | export const useBlockScrolling = (scrollRef) => {
9 | const [blockScrolling, setBlockScrolling] = useState(false);
10 |
11 | useEffect(() => {
12 | const scrollEl = scrollRef.current;
13 | if (!scrollEl || typeof MutationObserver === 'undefined') return false;
14 |
15 | const observer = new MutationObserver((mutations) => {
16 | if (!scrollEl) return;
17 | mutations.forEach((m) => {
18 | const { target } = m;
19 | if (
20 | m.attributeName === 'class' &&
21 | target.classList.contains('tile-actions') &&
22 | scrollEl.scrollHeight > scrollEl.clientHeight
23 | ) {
24 | setBlockScrolling(target.classList.contains('showMenu'));
25 | }
26 | });
27 | });
28 |
29 | observer.observe(scrollEl, {
30 | attributes: true,
31 | subtree: true,
32 | });
33 |
34 | return () => observer.disconnect();
35 | }, [scrollRef]);
36 |
37 | return blockScrolling;
38 | };
39 |
40 | export default useBlockScrolling;
41 |
--------------------------------------------------------------------------------
/custom/shared/components/Tile/avatar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/custom/shared/components/Tile/index.js:
--------------------------------------------------------------------------------
1 | export { Tile as default } from './Tile';
2 | export { Video } from './Video';
3 |
--------------------------------------------------------------------------------
/custom/shared/components/Tray/TrayButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Button from '@custom/shared/components/Button';
3 | import PropTypes from 'prop-types';
4 |
5 | export const TrayButton = ({ children, label, onClick, orange = false }) => (
6 |
7 | onClick()} variant="dark" size="large-square">
8 | {children}
9 |
10 | {label}
11 |
12 |
28 |
29 | );
30 |
31 | TrayButton.propTypes = {
32 | children: PropTypes.node,
33 | onClick: PropTypes.func,
34 | orange: PropTypes.bool,
35 | label: PropTypes.string.isRequired,
36 | };
37 |
38 | export default TrayButton;
39 |
--------------------------------------------------------------------------------
/custom/shared/components/Tray/TrayMicButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TrayButton } from '@custom/shared/components/Tray';
3 | import { ReactComponent as IconMicOff } from '@custom/shared/icons/mic-off-md.svg';
4 | import { ReactComponent as IconMicOn } from '@custom/shared/icons/mic-on-md.svg';
5 |
6 | import PropTypes from 'prop-types';
7 |
8 | export const TrayMicButton = ({ isMuted, onClick }) => {
9 | return (
10 |
11 | {isMuted ? : }
12 |
13 | );
14 | };
15 |
16 | TrayMicButton.propTypes = {
17 | isMuted: PropTypes.bool,
18 | onClick: PropTypes.func.isRequired,
19 | };
20 |
21 | export default TrayMicButton;
22 |
--------------------------------------------------------------------------------
/custom/shared/components/Tray/index.js:
--------------------------------------------------------------------------------
1 | export { Tray, TrayButton } from './Tray';
2 | export { BasicTray } from './BasicTray';
3 |
--------------------------------------------------------------------------------
/custom/shared/components/VideoContainer/VideoContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const VideoContainer = ({ children }) => (
5 |
6 | {children}
7 |
19 |
20 | );
21 |
22 | VideoContainer.propTypes = {
23 | children: PropTypes.node,
24 | };
25 | export default VideoContainer;
26 |
--------------------------------------------------------------------------------
/custom/shared/components/VideoContainer/index.js:
--------------------------------------------------------------------------------
1 | export { VideoContainer as default } from './VideoContainer';
2 |
--------------------------------------------------------------------------------
/custom/shared/components/WaitingRoom/WaitingParticipantRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { useWaitingRoom } from '../../contexts/WaitingRoomProvider';
4 | import Button from '../Button';
5 |
6 | export const WaitingParticipantRow = ({ participant }) => {
7 | const { grantAccess, denyAccess } = useWaitingRoom();
8 |
9 | const handleAllowClick = () => {
10 | grantAccess(participant.id);
11 | };
12 | const handleDenyClick = () => {
13 | denyAccess(participant.id);
14 | };
15 |
16 | return (
17 |
18 | {participant.name}
19 |
20 |
21 | Allow
22 |
23 |
24 | Deny
25 |
26 |
27 |
28 |
49 |
50 | );
51 | };
52 |
53 | WaitingParticipantRow.propTypes = {
54 | participant: PropTypes.object,
55 | };
56 |
57 | export default WaitingParticipantRow;
58 |
--------------------------------------------------------------------------------
/custom/shared/components/WaitingRoom/WaitingRoomModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from '@custom/shared/components/Modal';
3 | import { useWaitingRoom } from '@custom/shared/contexts/WaitingRoomProvider';
4 | import PropTypes from 'prop-types';
5 | import Button from '../Button';
6 | import { WaitingParticipantRow } from './WaitingParticipantRow';
7 |
8 | export const WaitingRoomModal = ({ onClose }) => {
9 | const { denyAccess, grantAccess, waitingParticipants } = useWaitingRoom();
10 |
11 | const handleAllowAllClick = (close) => {
12 | grantAccess('all');
13 | close();
14 | };
15 | const handleDenyAllClick = (close) => {
16 | denyAccess('all');
17 | close();
18 | };
19 |
20 | return (
21 | onClose()}
25 | actions={[
26 |
32 | Allow all
33 | ,
34 |
40 | Deny all
41 | ,
42 | ]}
43 | >
44 | {waitingParticipants.map((p) => (
45 |
46 | ))}
47 |
48 | );
49 | };
50 |
51 | WaitingRoomModal.propTypes = {
52 | onClose: PropTypes.func,
53 | };
54 |
55 | export default WaitingRoomModal;
56 |
--------------------------------------------------------------------------------
/custom/shared/components/WaitingRoom/index.js:
--------------------------------------------------------------------------------
1 | export { WaitingRoomModal } from './WaitingRoomModal';
2 | export { WaitingRoomNotification } from './WaitingRoomNotification';
3 | export { WaitingParticipantRow } from './WaitingParticipantRow';
4 |
--------------------------------------------------------------------------------
/custom/shared/components/Well/Well.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import PropTypes from 'prop-types';
4 |
5 | export const Well = ({ children, variant }) => (
6 |
7 | {children}
8 |
19 |
20 | );
21 |
22 | Well.propTypes = {
23 | children: PropTypes.node,
24 | variant: PropTypes.oneOf(['error', 'warning', 'info']),
25 | };
26 |
27 | export default Well;
28 |
--------------------------------------------------------------------------------
/custom/shared/components/Well/index.js:
--------------------------------------------------------------------------------
1 | export { Well as default } from './Well';
2 |
--------------------------------------------------------------------------------
/custom/shared/constants.js:
--------------------------------------------------------------------------------
1 | export const ACCESS_STATE_UNKNOWN = 'unknown';
2 | export const ACCESS_STATE_LOBBY = 'lobby';
3 | export const ACCESS_STATE_FULL = 'full';
4 | export const ACCESS_STATE_NONE = 'none';
5 |
6 | export const MEETING_STATE_JOINED = 'joined-meeting';
7 |
8 | export const VIDEO_QUALITY_AUTO = 'auto';
9 | export const VIDEO_QUALITY_HIGH = 'high';
10 | export const VIDEO_QUALITY_LOW = 'low';
11 | export const VIDEO_QUALITY_VERY_LOW = 'very-low';
12 | export const VIDEO_QUALITY_BANDWIDTH_SAVER = 'bandwidth-saver';
13 |
14 | export const DEFAULT_ASPECT_RATIO = 16 / 9;
15 |
--------------------------------------------------------------------------------
/custom/shared/contexts/ScreenShareProvider.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useMemo } from 'react';
2 | import { useScreenShare as useDailyScreenShare } from '@daily-co/daily-react-hooks';
3 | import PropTypes from 'prop-types';
4 |
5 | export const MAX_SCREEN_SHARES = 2;
6 |
7 | const ScreenShareContext = createContext(null);
8 |
9 | export const ScreenShareProvider = ({ children }) => {
10 | const {
11 | isSharingScreen,
12 | screens,
13 | startScreenShare,
14 | stopScreenShare
15 | } = useDailyScreenShare();
16 |
17 | const isDisabled = useMemo(() => screens.length >= MAX_SCREEN_SHARES && !isSharingScreen,
18 | [isSharingScreen, screens.length]
19 | );
20 |
21 | return (
22 |
31 | {children}
32 |
33 | );
34 | };
35 |
36 | ScreenShareProvider.propTypes = {
37 | children: PropTypes.node,
38 | };
39 |
40 | export const useScreenShare = () => useContext(ScreenShareContext);
41 |
--------------------------------------------------------------------------------
/custom/shared/contexts/WaitingRoomProvider.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | createContext,
3 | useContext,
4 | useEffect,
5 | useMemo,
6 | useState,
7 | } from 'react';
8 | import { useWaitingParticipants } from '@daily-co/daily-react-hooks';
9 | import PropTypes from 'prop-types';
10 |
11 | const WaitingRoomContext = createContext(null);
12 |
13 | export const WaitingRoomProvider = ({ children }) => {
14 | const { waitingParticipants, grantAccess, denyAccess } =
15 | useWaitingParticipants();
16 | const [showModal, setShowModal] = useState(false);
17 |
18 | const multipleWaiting = useMemo(
19 | () => waitingParticipants.length > 1,
20 | [waitingParticipants]
21 | );
22 |
23 | useEffect(() => {
24 | if (waitingParticipants.length === 0) {
25 | setShowModal(false);
26 | }
27 | }, [waitingParticipants]);
28 |
29 | return (
30 |
40 | {children}
41 |
42 | );
43 | };
44 |
45 | WaitingRoomProvider.propTypes = {
46 | children: PropTypes.node,
47 | };
48 |
49 | export const useWaitingRoom = () => useContext(WaitingRoomContext);
--------------------------------------------------------------------------------
/custom/shared/hooks/useActiveSpeaker.js:
--------------------------------------------------------------------------------
1 | import { useCallState } from '../contexts/CallProvider';
2 | import { useParticipants } from '../contexts/ParticipantsProvider';
3 |
4 | /**
5 | * Convenience hook to contain all logic on determining the active speaker
6 | * (= the current one and only actively speaking person)
7 | */
8 | export const useActiveSpeaker = () => {
9 | const { broadcastRole, showLocalVideo } = useCallState();
10 | const { activeParticipant, localParticipant, participantCount } =
11 | useParticipants();
12 |
13 | // we don't show active speaker indicators EVER in a 1:1 call or when the user is alone in-call
14 | if (broadcastRole !== 'attendee' && participantCount <= 2) return null;
15 |
16 | if (!activeParticipant?.isMicMuted) {
17 | return activeParticipant?.id;
18 | }
19 |
20 | /**
21 | * When the local video is displayed and the last known active speaker
22 | * is muted, we can only fall back to the local participant.
23 | */
24 | return localParticipant?.isMicMuted || !showLocalVideo
25 | ? null
26 | : localParticipant?.id;
27 | };
28 |
29 | export default useActiveSpeaker;
30 |
--------------------------------------------------------------------------------
/custom/shared/hooks/useAudioLevel.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import getConfig from 'next/config';
3 |
4 | export const useAudioLevel = (stream) => {
5 | const [micVolume, setMicVolume] = useState(0);
6 | const { assetPrefix } = getConfig().publicRuntimeConfig;
7 |
8 | useEffect(() => {
9 | if (!stream) {
10 | setMicVolume(0);
11 | return;
12 | }
13 | const AudioCtx =
14 | typeof AudioContext !== 'undefined'
15 | ? AudioContext
16 | : typeof webkitAudioContext !== 'undefined'
17 | ? webkitAudioContext
18 | : null;
19 | if (!AudioCtx) return;
20 | const audioContext = new AudioCtx();
21 | const mediaStreamSource = audioContext.createMediaStreamSource(stream);
22 | let node;
23 |
24 | const startProcessing = async () => {
25 | try {
26 | await audioContext.audioWorklet.addModule(
27 | `${assetPrefix}/audiolevel-processor.js`
28 | );
29 |
30 | node = new AudioWorkletNode(audioContext, 'audiolevel');
31 |
32 | node.port.onmessage = (event) => {
33 | let volume = 0;
34 | if (event.data.volume) volume = event.data.volume;
35 | if (!node) return;
36 | setMicVolume(volume);
37 | };
38 |
39 | mediaStreamSource.connect(node).connect(audioContext.destination);
40 | } catch {}
41 | };
42 |
43 | startProcessing();
44 |
45 | return () => {
46 | node?.disconnect();
47 | node = null;
48 | mediaStreamSource?.disconnect();
49 | audioContext?.close();
50 | };
51 | }, [assetPrefix, stream]);
52 |
53 | return micVolume;
54 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useAudioTrack.js:
--------------------------------------------------------------------------------
1 | import { useDeepCompareMemo } from 'use-deep-compare';
2 |
3 | import { useTracks } from '../contexts/TracksProvider';
4 |
5 | export const useAudioTrack = (id) => {
6 | const { audioTracks } = useTracks();
7 |
8 | return useDeepCompareMemo(() => {
9 | const audioTrack = audioTracks?.[id];
10 | return audioTrack?.persistentTrack;
11 | }, [id, audioTracks]);
12 | };
13 |
14 | export default useAudioTrack;
--------------------------------------------------------------------------------
/custom/shared/hooks/useCamSubscriptions.js:
--------------------------------------------------------------------------------
1 | import { useDeepCompareEffect } from 'use-deep-compare';
2 | import { useTracks } from '../contexts/TracksProvider';
3 |
4 | /**
5 | * Updates cam subscriptions based on passed subscribedIds and stagedIds.
6 | * @param subscribedIds Participant ids whose cam tracks should be subscribed to.
7 | * @param stagedIds Participant ids whose cam tracks should be staged.
8 | * @param delay Throttle in milliseconds. Default: 50
9 | */
10 | export const useCamSubscriptions = (
11 | subscribedIds,
12 | stagedIds = [],
13 | throttle = 50
14 | ) => {
15 | const { updateCamSubscriptions } = useTracks();
16 |
17 | useDeepCompareEffect(() => {
18 | if (!subscribedIds || !stagedIds) return;
19 | const timeout = setTimeout(() => {
20 | updateCamSubscriptions(subscribedIds, stagedIds);
21 | }, throttle);
22 | return () => clearTimeout(timeout);
23 | }, [subscribedIds, stagedIds, throttle, updateCamSubscriptions]);
24 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useJoinSound.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from 'react';
2 |
3 | import { useDaily, useDailyEvent } from '@daily-co/daily-react-hooks';
4 | import { useSoundLoader } from './useSoundLoader';
5 |
6 | /**
7 | * Convenience hook to play `join.mp3` when first other participants joins.
8 | */
9 | export const useJoinSound = () => {
10 | const daily = useDaily();
11 | const { joinSound } = useSoundLoader();
12 | const [playJoinSound, setPlayJoinSound] = useState(false);
13 |
14 | useEffect(() => {
15 | if (!daily) return;
16 | /**
17 | * We don't want to immediately play a joined sound, when the user joins the meeting:
18 | * Upon joining all other participants, that were already in-call, will emit a
19 | * participant-joined event.
20 | * In waiting 2 seconds we make sure, that the sound is only played when the user
21 | * is **really** the first participant.
22 | */
23 | setTimeout(() => {
24 | setPlayJoinSound(true);
25 | }, 2000);
26 | }, [daily]);
27 |
28 | const handleParticipantJoined = useCallback(() => {
29 | // first other participant joined --> play sound
30 | if (!playJoinSound || Object.keys(daily.participants()).length !== 2)
31 | return;
32 | joinSound.play();
33 | }, [daily, joinSound, playJoinSound]);
34 |
35 | useDailyEvent('participant-joined', handleParticipantJoined);
36 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useResize.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from 'react';
2 |
3 | export const useResize = (callback, deps = []) => {
4 | let timeout;
5 | const handleResize = useCallback(() => {
6 | if (timeout) cancelAnimationFrame(timeout);
7 | timeout = requestAnimationFrame(() => callback());
8 | }, [callback]);
9 | useEffect(() => {
10 | window.addEventListener('resize', handleResize, { passive: true });
11 | window.addEventListener('orientationchange', handleResize, {
12 | passive: true,
13 | });
14 | callback();
15 | return () => {
16 | if (timeout) cancelAnimationFrame(timeout);
17 | window.removeEventListener('resize', handleResize);
18 | window.removeEventListener('orientationchange', handleResize);
19 | };
20 | }, deps);
21 | };
22 |
23 | export default useResize;
24 |
--------------------------------------------------------------------------------
/custom/shared/hooks/useResponsive.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | let responsiveConfig = {
4 | xs: 0,
5 | sm: 576,
6 | md: 768,
7 | lg: 992,
8 | xl: 1200,
9 | };
10 |
11 | export const useResponsive = () => {
12 | const [windowSize, setWindowSize] = useState(window.innerWidth);
13 |
14 | const handleChangeWindowSize = () => setWindowSize(window.innerWidth);
15 |
16 | const getResponsiveConfig = (size) => {
17 | const responsive = {};
18 | Object.keys(responsiveConfig).forEach(config => {
19 | responsive[config] = size > responsiveConfig[config];
20 | });
21 | return responsive;
22 | };
23 |
24 | const isMobile = () => {
25 | const config = getResponsiveConfig(windowSize);
26 | return !config.md && !config.lg && !config.xl;
27 | };
28 |
29 | useEffect(() => {
30 | window.addEventListener('resize', handleChangeWindowSize);
31 |
32 | return () => {
33 | window.removeEventListener('resize', handleChangeWindowSize);
34 | };
35 | }, []);
36 |
37 | return { config: getResponsiveConfig(windowSize), isMobile };
38 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useScrollbarWidth.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export const useScrollbarWidth = () => {
4 | const [scrollbarWidth, setScrollbarWidth] = useState(0);
5 |
6 | useEffect(() => {
7 | // Create fake div to determine if scrollbar is rendered inside or outside
8 | const div = document.createElement('div');
9 | div.style.width = '100px';
10 | div.style.height = '1px';
11 | div.style.position = 'absolute';
12 | div.style.left = '-9999em';
13 | div.style.top = '-9999em';
14 | div.style.overflow = 'auto';
15 | div.setAttribute('aria-hidden', 'true');
16 | const child = document.createElement('div');
17 | child.textContent = 'This is a test div.';
18 | div.appendChild(child);
19 | document.body.appendChild(div);
20 | const autoWidth = child.clientWidth;
21 | div.style.overflow = 'hidden';
22 | const hiddenWidth = child.clientWidth;
23 | setScrollbarWidth(hiddenWidth - autoWidth);
24 | div.remove();
25 | }, []);
26 |
27 | return scrollbarWidth;
28 | };
29 |
30 | export default useScrollbarWidth;
31 |
--------------------------------------------------------------------------------
/custom/shared/hooks/useSound.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 |
3 | const defaultNotMuted = () => false;
4 |
5 | export const useSound = (src, isMuted = defaultNotMuted) => {
6 | const audio = useRef(null);
7 |
8 | useEffect(() => {
9 | const tag = document.querySelector(`audio[src="${src}"]`);
10 |
11 | if (tag) {
12 | audio.current = tag;
13 | } else {
14 | const t = document.createElement('audio');
15 | t.src = src;
16 | t.setAttribute('playsinline', '');
17 | document.body.appendChild(t);
18 | audio.current = t;
19 | }
20 | }, [src]);
21 |
22 | const load = useCallback(() => {
23 | if (!audio.current) return;
24 | audio.current.load();
25 | }, [audio]);
26 |
27 | const play = useCallback(async () => {
28 | if (!audio.current || isMuted()) return;
29 | try {
30 | audio.current.currentTime = 0;
31 | await audio.current.play();
32 | } catch (e) {
33 | console.error(e);
34 | }
35 | }, [audio, isMuted]);
36 |
37 | return { load, play };
38 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useSoundLoader.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useMemo } from 'react';
2 |
3 | import { useCallState } from '../contexts/CallProvider';
4 | import { useSound } from './useSound';
5 |
6 | export const useSoundLoader = () => {
7 | const { enableJoinSound } = useCallState();
8 |
9 | const isJoinSoundMuted = useCallback(
10 | () => !enableJoinSound,
11 | [enableJoinSound]
12 | );
13 |
14 | const joinSound = useSound(`assets/join.mp3`, isJoinSoundMuted);
15 |
16 | const load = useCallback(() => {
17 | joinSound.load();
18 | }, [joinSound]);
19 |
20 | return useMemo(
21 | () => ({ joinSound, load }),
22 | [joinSound, load]
23 | );
24 | };
--------------------------------------------------------------------------------
/custom/shared/hooks/useVideoTrack.js:
--------------------------------------------------------------------------------
1 | import { useDeepCompareMemo } from 'use-deep-compare';
2 |
3 | import { useTracks } from '../contexts/TracksProvider';
4 | import { isLocalId, isScreenId } from '../contexts/participantsState';
5 |
6 | export const useVideoTrack = (id) => {
7 | const { videoTracks } = useTracks();
8 |
9 | const videoTrack = useDeepCompareMemo(
10 | () => videoTracks?.[id],
11 | [id, videoTracks]
12 | );
13 |
14 | /**
15 | * MediaStreamTrack's are difficult to compare.
16 | * Changes to a video track's id will likely need to be reflected in the UI / DOM.
17 | * This usually happens on P2P / SFU switches.
18 | */
19 | return useDeepCompareMemo(() => {
20 | if (
21 | videoTrack?.state === 'off' ||
22 | videoTrack?.state === 'blocked' ||
23 | (!videoTrack?.subscribed && !isLocalId(id) && !isScreenId(id))
24 | )
25 | return null;
26 | return videoTrack?.persistentTrack;
27 | }, [id, videoTrack]);
28 | };
--------------------------------------------------------------------------------
/custom/shared/icons/add-md.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/add-person-lg.svg:
--------------------------------------------------------------------------------
1 | add-27
--------------------------------------------------------------------------------
/custom/shared/icons/avatar-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/custom/shared/icons/camera-off-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/custom/shared/icons/camera-off-sm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/camera-on-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/custom/shared/icons/camera-on-sm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/chat-md.svg:
--------------------------------------------------------------------------------
1 | comments
--------------------------------------------------------------------------------
/custom/shared/icons/close-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/custom/shared/icons/emoji-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/custom/shared/icons/grid-md.svg:
--------------------------------------------------------------------------------
1 | grid_on
--------------------------------------------------------------------------------
/custom/shared/icons/leave-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/custom/shared/icons/lock-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/custom/shared/icons/mic-on-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/custom/shared/icons/mic-on-sm.svg:
--------------------------------------------------------------------------------
1 | microphone
--------------------------------------------------------------------------------
/custom/shared/icons/more-md.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/network-md.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/people-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/custom/shared/icons/play-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/custom/shared/icons/raquo-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/custom/shared/icons/record-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/custom/shared/icons/settings-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/custom/shared/icons/settings-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/custom/shared/icons/share-sm.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/custom/shared/icons/speaker-view-md.svg:
--------------------------------------------------------------------------------
1 | view_sidebar
--------------------------------------------------------------------------------
/custom/shared/icons/star-md.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/custom/shared/icons/streaming-md.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/custom/shared/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/shared/lib/demoProps.js:
--------------------------------------------------------------------------------
1 | export default function getDemoProps() {
2 | return {
3 | domain: process.env.DAILY_DOMAIN || null,
4 | // Check that both domain and key env vars are set
5 | isConfigured: !!process.env.DAILY_DOMAIN && !!process.env.DAILY_API_KEY,
6 | // Manual or automatic track subscriptions
7 | subscribeToTracksAutomatically: !process.env.MANUAL_TRACK_SUBS,
8 | // Are we running in demo mode? (automatically creates a short-expiry room)
9 | demoMode: !!process.env.DAILY_DEMO_MODE,
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/custom/shared/lib/mediaUtils.js:
--------------------------------------------------------------------------------
1 | export const asyncGetUserDevices = async (useLocal = true) => {
2 | const devices = await callObject.getInputDevices(); // navigator.mediaDevices.enumerateDevices();
3 |
4 | const defaultCam = useLocal && localStorage.getItem('defaultCamId');
5 | const defaultMic = useLocal && localStorage.getItem('defaultMicId');
6 | const defaultSpeakers = useLocal && localStorage.getItem('defaultSpeakersId');
7 |
8 | const cams = devices.filter((d) => d.kind === 'videoinput');
9 | const mics = devices.filter((d) => d.kind === 'audioinput');
10 | const speakers = devices.filter((d) => d.kind === 'audiooutput');
11 |
12 | const defaultCamDevice = devices.filter((d) => d.deviceId === defaultCam);
13 | const defaultMicDevice = devices.filter((d) => d.deviceId === defaultMic);
14 | const defaultSpeakersDevice = devices.filter(
15 | (d) => d.deviceId === defaultSpeakers
16 | );
17 |
18 | const currentCam = defaultCamDevice.length ? defaultCamDevice[0] : cams[0];
19 | const currentMic = defaultMicDevice.length ? defaultMicDevice[0] : mics[0];
20 | const currentSpeakers = defaultSpeakersDevice.length
21 | ? defaultSpeakersDevice[0]
22 | : speakers[0];
23 |
24 | return { cams, mics, speakers, currentCam, currentMic, currentSpeakers };
25 | };
26 |
27 | export default asyncGetUserDevices;
28 |
--------------------------------------------------------------------------------
/custom/shared/lib/slugify.js:
--------------------------------------------------------------------------------
1 | const convert = (keyword) => {
2 | return keyword
3 | .toString()
4 | .trim()
5 | .replace(/\s+/g, '-')
6 | };
7 |
8 | const revert = (keyword) => {
9 | return keyword
10 | .toString()
11 | .trim()
12 | .replace('-', ' ')
13 | }
14 |
15 | export const slugify = { convert, revert };
--------------------------------------------------------------------------------
/custom/shared/lib/sortByKey.js:
--------------------------------------------------------------------------------
1 | export const sortByKey = (key, caseSensitive = true) =>
2 | (a, b) => {
3 | const aKey =
4 | !caseSensitive && typeof a[key] === 'string'
5 | ? String(a[key])?.toLowerCase()
6 | : a[key];
7 | const bKey =
8 | !caseSensitive && typeof b[key] === 'string'
9 | ? String(b[key])?.toLowerCase()
10 | : b[key];
11 | if (aKey > bKey) return 1;
12 | if (aKey < bKey) return -1;
13 | return 0;
14 | };
--------------------------------------------------------------------------------
/custom/shared/lib/sortLastActive.js:
--------------------------------------------------------------------------------
1 | export const sortLastActive = (a, b) => {
2 | if (a?.lastActiveDate > b?.lastActiveDate) return -1;
3 | if (a?.lastActiveDate < b?.lastActiveDate) return 1;
4 | return 0;
5 | };
6 |
7 | export default sortLastActive;
8 |
--------------------------------------------------------------------------------
/custom/shared/lib/token.js:
--------------------------------------------------------------------------------
1 | export const parseJWT = (token) => {
2 | const base64Url = token.split('.')[1];
3 | const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
4 | const jsonPayload = decodeURIComponent(
5 | atob(base64)
6 | .split('')
7 | .map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`)
8 | .join('')
9 | );
10 |
11 | return JSON.parse(jsonPayload);
12 | };
13 |
14 | const keyMap = {
15 | ao: 'start_audio_off',
16 | ctoe: 'close_tab_on_exit',
17 | d: 'domainId',
18 | eje: 'eject_after_elapsed',
19 | ejt: 'eject_at_token_exp',
20 | er: 'enable_recording',
21 | exp: 'exp',
22 | iat: 'createdAt',
23 | nbf: 'nbf',
24 | o: 'isOwner',
25 | r: 'room',
26 | rome: 'redirect_on_meeting_exit',
27 | sr: 'start_cloud_recording',
28 | ss: 'enable_screenshare',
29 | u: 'username',
30 | ud: 'id',
31 | uil: 'lang',
32 | vo: 'start_video_off',
33 | };
34 |
35 | export const parseMeetingToken = (token) => {
36 | const parsed = parseJWT(token);
37 | const result = {};
38 | for (const [key, val] of Object.entries(parsed)) {
39 | result[keyMap[key]] = val;
40 | }
41 | return result;
42 | };
43 |
--------------------------------------------------------------------------------
/custom/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/shared",
3 | "version": "0.1.0",
4 | "private": true,
5 | "main": "index.js",
6 | "dependencies": {
7 | "@daily-co/daily-js": "^0.34.1",
8 | "@daily-co/daily-react-hooks": "^0.5.0",
9 | "bowser": "^2.11.0",
10 | "classnames": "^2.3.1",
11 | "debounce": "^1.2.1",
12 | "fast-deep-equal": "^3.1.3",
13 | "nanoid": "^3.1.23",
14 | "no-scroll": "^2.1.1",
15 | "prop-types": "^15.7.2",
16 | "react": "^17.0.2",
17 | "react-dom": "^17.0.2",
18 | "react-portal": "^4.2.1",
19 | "recoil": "^0.7.0",
20 | "shallow-equal": "^1.2.1",
21 | "use-deep-compare": "^1.1.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/custom/shared/styles/defaultTheme.js:
--------------------------------------------------------------------------------
1 | export const defaultTheme = {
2 | background: '#2B3F56',
3 | reverse: '#FFFFFF',
4 |
5 | primary: {
6 | default: '#1bebb9',
7 | dark: '#00a981',
8 | },
9 |
10 | secondary: {
11 | default: '#FF9254',
12 | dark: '#FB651E',
13 | light: '#FF9254',
14 | },
15 |
16 | blue: {
17 | light: '#2B3F56',
18 | default: '#1F2D3D',
19 | dark: '#121A24',
20 | },
21 |
22 | green: {
23 | light: '#EEFAE0',
24 | default: '#72CC18',
25 | dark: '#62A60F',
26 | },
27 |
28 | red: {
29 | light: '#FDDDDD',
30 | default: '#E71115',
31 | dark: '#BB0C0C',
32 | },
33 |
34 | gray: {
35 | wash: '#F7F9FA',
36 | light: '#E6EAEF',
37 | default: '#C8D1DC',
38 | dark: '#7B848F',
39 | },
40 |
41 | text: {
42 | default: '#2B3F56',
43 | reverse: '#FFFFFF',
44 | mid: '#7B848F',
45 | darkest: '#1F2D3D',
46 | pre: '#FB651E',
47 | },
48 | };
49 |
50 | export default defaultTheme;
51 |
--------------------------------------------------------------------------------
/custom/shared/styles/global.js:
--------------------------------------------------------------------------------
1 | export const hexa = (hex, alpha) => {
2 | const r = parseInt(hex.slice(1, 3), 16);
3 | const g = parseInt(hex.slice(3, 5), 16);
4 | const b = parseInt(hex.slice(5, 7), 16);
5 |
6 | if (alpha >= 0) {
7 | return `rgba(${r}, ${g}, ${b}, ${alpha})`;
8 | }
9 | return `rgb(${r}, ${g}, ${b})`;
10 | };
11 |
12 | export default hexa;
13 |
--------------------------------------------------------------------------------
/custom/text-chat/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": ["inline-react-svg"]
4 | }
5 |
--------------------------------------------------------------------------------
/custom/text-chat/README.md:
--------------------------------------------------------------------------------
1 | # Text Chat
2 |
3 | 
4 |
5 | ### Live example
6 |
7 | **[See it in action here ➡️](https://custom-text-chat.vercel.app)**
8 |
9 | ---
10 |
11 | ## What does this demo do?
12 |
13 | - Use [sendAppMessage](https://docs.daily.co/reference#%EF%B8%8F-sendappmessage) to send messages
14 | - Using the `useSharedState` hook to get the chat history, for the new participants.
15 | - Extend the basic call demo with a chat provider and aside
16 | - Show a notification bubble on chat tray button when a new message is received
17 | - Demonstrate how to play a sound whenever a message is received
18 |
19 | Please note: this demo is not currently mobile optimised
20 |
21 | ### Getting started
22 |
23 | ```
24 | # set both DAILY_API_KEY and DAILY_DOMAIN
25 | mv env.example .env.local
26 |
27 | yarn
28 | yarn workspace @custom/text-chat dev
29 | ```
30 |
31 | ## How does this example work?
32 |
33 | In this example we extend the [basic call demo](../basic-call) with the ability to send chat messages.
34 |
35 | We pass a custom tray object, a custom app object (wrapping the original in a new `ChatProvider`) as well as add our `ChatAside` panel. We also symlink both the `public` and `pages/api` folders from the basic call.
36 |
37 | We use the `useSharedState` hook to retrieve the previously sent messages for the newly joined participants.
38 |
39 | ## Deploy your own on Vercel
40 |
41 | [](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
42 |
--------------------------------------------------------------------------------
/custom/text-chat/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import App from '@custom/basic-call/components/App';
4 | import { ChatProvider } from '../contexts/ChatProvider';
5 |
6 | // Extend our basic call app component with the chat context
7 | export const AppWithChat = () => (
8 |
9 |
10 |
11 | );
12 |
13 | export default AppWithChat;
14 |
--------------------------------------------------------------------------------
/custom/text-chat/components/Tray.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { TrayButton } from '@custom/shared/components/Tray';
4 | import { useUIState } from '@custom/shared/contexts/UIStateProvider';
5 | import { ReactComponent as IconChat } from '@custom/shared/icons/chat-md.svg';
6 | import { useChat } from '../contexts/ChatProvider';
7 | import { CHAT_ASIDE } from './ChatAside';
8 |
9 | export const Tray = () => {
10 | const { toggleAside } = useUIState();
11 | const { hasNewMessages } = useChat();
12 |
13 | return (
14 | {
18 | toggleAside(CHAT_ASIDE);
19 | }}
20 | >
21 |
22 |
23 | );
24 | };
25 |
26 | export default Tray;
27 |
--------------------------------------------------------------------------------
/custom/text-chat/env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
9 |
--------------------------------------------------------------------------------
/custom/text-chat/hooks/useMessageSound.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useMemo } from 'react';
2 |
3 | import { useSound } from '@custom/shared/hooks/useSound';
4 | import { debounce } from 'debounce';
5 |
6 | /**
7 | * Convenience hook to play `join.mp3` when participants join the call
8 | */
9 | export const useMessageSound = () => {
10 | const { load, play } = useSound('assets/message.mp3');
11 |
12 | useEffect(() => {
13 | load();
14 | }, [load]);
15 |
16 | return useMemo(() => debounce(() => play(), 5000, true), [play]);
17 | };
18 |
19 | export default useMessageSound;
20 |
--------------------------------------------------------------------------------
/custom/text-chat/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/text-chat/image.png
--------------------------------------------------------------------------------
/custom/text-chat/index.js:
--------------------------------------------------------------------------------
1 | // Note: I am here because next-transpile-modules requires a mainfile
2 |
--------------------------------------------------------------------------------
/custom/text-chat/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')([
3 | '@custom/shared',
4 | '@custom/basic-call',
5 | ]);
6 |
7 | const packageJson = require('./package.json');
8 |
9 | module.exports = withPlugins([withTM], {
10 | env: {
11 | PROJECT_TITLE: packageJson.description,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/custom/text-chat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@custom/text-chat",
3 | "description": "Basic Call + Chat Example",
4 | "version": "0.1.0",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start",
10 | "lint": "next lint"
11 | },
12 | "dependencies": {
13 | "@custom/shared": "*",
14 | "@custom/basic-call": "*",
15 | "next": "^11.0.0",
16 | "pluralize": "^8.0.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^4.1.0",
22 | "next-compose-plugins": "^2.2.1",
23 | "next-transpile-modules": "^8.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/custom/text-chat/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import App from '@custom/basic-call/pages/_app';
3 | import AppWithChat from '../components/App';
4 |
5 | import ChatAside from '../components/ChatAside';
6 | import Tray from '../components/Tray';
7 |
8 | App.asides = [ChatAside];
9 | App.customAppComponent = ;
10 | App.customTrayComponent = ;
11 |
12 | export default App;
13 |
--------------------------------------------------------------------------------
/custom/text-chat/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Doc from '@custom/basic-call/pages/_document';
2 |
3 | export default Doc;
4 |
--------------------------------------------------------------------------------
/custom/text-chat/pages/api:
--------------------------------------------------------------------------------
1 | ../../basic-call/pages/api
--------------------------------------------------------------------------------
/custom/text-chat/pages/index.js:
--------------------------------------------------------------------------------
1 | import Index from '@custom/basic-call/pages';
2 | import getDemoProps from '@custom/shared/lib/demoProps';
3 |
4 | export async function getStaticProps() {
5 | const defaultProps = getDemoProps();
6 |
7 | // Pass through domain as prop
8 | return {
9 | props: defaultProps,
10 | };
11 | }
12 |
13 | export default Index;
14 |
--------------------------------------------------------------------------------
/custom/text-chat/public/assets/join.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/text-chat/public/assets/join.mp3
--------------------------------------------------------------------------------
/custom/text-chat/public/assets/message.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/text-chat/public/assets/message.mp3
--------------------------------------------------------------------------------
/custom/text-chat/public/assets/pattern-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/custom/text-chat/public/assets/pattern-bg.png
--------------------------------------------------------------------------------
/custom/text-chat/public/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export { Header as default } from './Header';
2 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "daily-web-examples",
3 | "version": "0.1.0",
4 | "repository": "git@github.com:daily-co/daily-web-examples.git",
5 | "license": "MIT",
6 | "private": true,
7 | "workspaces": [
8 | "custom/*",
9 | "prebuilt/*"
10 | ],
11 | "engines": {
12 | "node": ">=0.12"
13 | },
14 | "devDependencies": {
15 | "babel-eslint": "^10.1.0",
16 | "babel-plugin-inline-react-svg": "^2.0.1",
17 | "eslint": "^7.25.0",
18 | "eslint-config-next": "^11.0.1",
19 | "eslint-config-prettier": "^8.3.0",
20 | "eslint-plugin-import": "^2.22.1",
21 | "eslint-plugin-prettier": "^3.4.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/prebuilt/README.md:
--------------------------------------------------------------------------------
1 | # Prebuilt UI Examples
2 |
3 | Daily Prebuilt makes it easy for developers to add video calls — in minutes — to any site or app. Out-of-the-box features include 1,000 person calls, recording, dual screen sharing, and more. Our secure infrastructure is trusted worldwide, from startups to Fortune 500 companies.
4 |
5 | These examples demonstrate how to customise and extend the prebuilt UI.
6 |
7 | ---
8 |
9 | ## Examples
10 |
11 | ### [✅ Basic embed](./basic-embed)
12 | Embeds [Daily Prebuilt](https://docs.daily.co/prebuilt), a ready-to-use video chat interface, into a Next.js app.
13 |
14 | ### [💬 Chat overlay](./chat-overlay)
15 | Uses the Daily [sendAppMessage()](https://docs.daily.co/reference/daily-js/instance-methods/send-app-message#main) method to add a custom chat around a Daily Prebuilt call.
16 |
17 | ### [🤙 iOS WebView](./ios-webview)
18 |
19 | Shows how to wrap the prebuilt UI in a native iOS application (using WKWebView)
20 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/.env.example:
--------------------------------------------------------------------------------
1 | # Domain excluding 'https://' and 'daily.co' e.g. 'somedomain'
2 | DAILY_DOMAIN=
3 |
4 | # Obtained from https://dashboard.daily.co/developers
5 | DAILY_API_KEY=
6 |
7 | # Daily REST API endpoint
8 | DAILY_REST_DOMAIN=https://api.daily.co/v1
--------------------------------------------------------------------------------
/prebuilt/basic-embed/.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 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env
29 | .env.local
30 | .env.development.local
31 | .env.test.local
32 | .env.production.local
33 |
34 | # vercel
35 | .vercel
36 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/README.md:
--------------------------------------------------------------------------------
1 | # Daily Prebuilt: Next.js demo
2 |
3 | 
4 |
5 | **Test out the live demo**: [https://prebuilt-basic-embed.vercel.app/](https://prebuilt-basic-embed.vercel.app/)
6 |
7 | ## How the demo works
8 |
9 | This demo embeds [Daily Prebuilt](https://www.daily.co/prebuilt), a ready-to-use video chat interface, into a Next.js site. It makes use of [Next API routes](https://nextjs.org/docs/api-routes/introduction) to create Daily rooms server-side.
10 |
11 | ## Requirements
12 |
13 | You'll need to create a [Daily account](https://dashboard.daily.co/signup) before using this demo. You'll need your Daily API key, which you can find in your Daily dashboard on the [Developers page](https://dashboard.daily.co/developers), if you want to create rooms through the demo UI.
14 |
15 | You can also paste an existing Daily room into the input. The room URL should be in this format to be valid: https://domain-name.daily.co/room-name, with daily-domain changed to your domain, and room-name changed to the name of the existing room you would like to use.
16 |
17 | # Running locally
18 | 1. Copy .env.example and change it to an .env.local with your own DAILY_API_KEY and DAILY_DOMAIN
19 | 2. `cd basic-embed`
20 | 3. yarn
21 | 4. yarn workspace @prebuilt/basic-embed dev
22 |
23 | Or...
24 |
25 | # Deploy your own on Vercel
26 |
27 | [](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)
--------------------------------------------------------------------------------
/prebuilt/basic-embed/basic-embed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/prebuilt/basic-embed/basic-embed.gif
--------------------------------------------------------------------------------
/prebuilt/basic-embed/components/CreateRoomButton.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Button from '@custom/shared/components/Button';
3 | import Well from '@custom/shared/components/Well';
4 |
5 | export function CreateRoomButton({
6 | isConfigured,
7 | isValidRoom,
8 | setRoom,
9 | setExpiry,
10 | }) {
11 | const [isError, setIsError] = useState(false);
12 |
13 | /**
14 | * Send a request to create a Daily room server-side via Next API routes, then set the returned url in local state to trigger Daily iframe creation in
15 | */
16 | const createRoom = async () => {
17 | try {
18 | const res = await fetch('/api/room', {
19 | method: 'POST',
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | },
23 | });
24 | const resJson = await res.json();
25 | setExpiry(resJson.config?.exp);
26 | setRoom(resJson.url);
27 | } catch (e) {
28 | setIsError(true);
29 | }
30 | };
31 | return (
32 | <>
33 | {!isConfigured && (
34 |
35 | You must configure env variables to create rooms (see README
36 | instructions).
37 |
38 | )}
39 | {isError && (
40 | Error creating the room. Please try again.
41 | )}
42 |
43 | Create room and start
44 |
45 | >
46 | );
47 | }
48 |
49 | export default CreateRoomButton;
50 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/components/ExpiryTimer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | export const ExpiryTimer = ({ expiry }) => {
5 | const [secs, setSecs] = useState('--:--');
6 |
7 | // If room has an expiry time, we'll calculate how many seconds until expiry
8 | useEffect(() => {
9 | if (!expiry) {
10 | return false;
11 | }
12 | const i = setInterval(() => {
13 | const timeLeft = Math.round(expiry - Date.now() / 1000);
14 | if (timeLeft < 0) {
15 | return setSecs(null);
16 | }
17 | setSecs(`${Math.floor(timeLeft / 60)}:${`0${timeLeft % 60}`.slice(-2)}`);
18 | }, 1000);
19 |
20 | return () => clearInterval(i);
21 | }, [expiry]);
22 |
23 | if (!secs) {
24 | return null;
25 | }
26 |
27 | return (
28 |
29 | {secs}
30 |
46 |
47 | );
48 | };
49 |
50 | ExpiryTimer.propTypes = {
51 | expiry: PropTypes.number,
52 | };
53 |
54 | export default ExpiryTimer;
55 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/next.config.js:
--------------------------------------------------------------------------------
1 | const withPlugins = require('next-compose-plugins');
2 | const withTM = require('next-transpile-modules')(['@custom/shared']);
3 |
4 | const packageJson = require('./package.json');
5 |
6 | module.exports = withPlugins([withTM], {
7 | env: {
8 | PROJECT_TITLE: packageJson.description,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@prebuilt/basic-embed",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "clipboard-polyfill": "^3.0.3",
13 | "next": "11.1.2",
14 | "next-compose-plugins": "^2.2.1",
15 | "next-transpile-modules": "^8.0.0",
16 | "react": "17.0.2",
17 | "react-dom": "17.0.2"
18 | },
19 | "devDependencies": {
20 | "eslint": "7.32.0",
21 | "eslint-config-next": "11.1.2"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GlobalStyle from '@custom/shared/components/GlobalStyle';
3 | import Head from 'next/head';
4 |
5 | function App({ Component, pageProps }) {
6 | return (
7 | <>
8 |
9 | Daily Prebuilt + Next.js demo
10 |
14 |
15 |
16 |
17 |
18 | >
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/pages/api/room/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a demo room server-side
3 | */
4 |
5 | export default async function handler(req, res) {
6 | if (req.method === 'POST') {
7 | const options = {
8 | method: 'POST',
9 | headers: {
10 | Accept: 'application/json',
11 | 'Content-Type': 'application/json',
12 | Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
13 | },
14 | body: JSON.stringify({
15 | properties: {
16 | enable_prejoin_ui: true,
17 | enable_network_ui: true,
18 | enable_screenshare: true,
19 | enable_chat: true,
20 | exp: Math.round(Date.now() / 1000) + 300,
21 | eject_at_room_exp: true,
22 | },
23 | }),
24 | };
25 |
26 | const dailyRes = await fetch(
27 | `${process.env.DAILY_REST_DOMAIN}/rooms`,
28 | options
29 | );
30 |
31 | const response = await dailyRes.json();
32 |
33 | if (response.error) {
34 | return res.status(500).json(response.error);
35 | }
36 |
37 | return res.status(200).json(response);
38 | }
39 |
40 | return res.status(500);
41 | }
42 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Header from '@custom/shared/components/Header';
3 | import getDemoProps from '@custom/shared/lib/demoProps';
4 | import Call from '../components/Call';
5 | import Home from '../components/Home';
6 |
7 | export default function Index({ isConfigured = false }) {
8 | const [room, setRoom] = useState(null);
9 | const [expiry, setExpiry] = useState(null);
10 | const [callFrame, setCallFrame] = useState(null);
11 |
12 | return (
13 |
14 |
20 |
21 | {room ? (
22 |
29 | ) : (
30 |
35 | )}
36 |
37 |
60 |
61 | );
62 | }
63 |
64 | export async function getStaticProps() {
65 | const defaultProps = getDemoProps();
66 |
67 | return {
68 | props: defaultProps,
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/public/daily-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/prebuilt/basic-embed/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/prebuilt/basic-embed/public/favicon.ico
--------------------------------------------------------------------------------
/prebuilt/basic-embed/public/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/prebuilt/basic-embed/public/github-logo.png
--------------------------------------------------------------------------------
/prebuilt/chat-overlay/README.md:
--------------------------------------------------------------------------------
1 | # Chat overlay
2 |
3 | 
4 |
5 | ## What does this demo do?
6 |
7 | - Adds full-screen [Daily Prebuilt](https://www.daily.co/prebuilt) to a page
8 | - Using [`app-message`](https://docs.daily.co/reference#app-message) and [`sendAppMessage()`](https://docs.daily.co/reference#%EF%B8%8F-sendappmessage), send and receive chat messages (with or without `enable_chat` set to `true` in the room)
9 | - Allow users to send and receive chat messages while viewing other participants' videos
10 |
11 | ## Getting started
12 |
13 | - Clone this repository and navigate to this folder (`git clone git@github.com:daily-demos/examples.git && cd prebuilt/chat-overlay`)
14 | - Set `ROOM_URL` on the first line of `main.js` to a Daily room you have created. Sign up and create a room from the [Daily dashboard](https://dashboard.daily.co/signup), if you haven't already!
15 | - Run a server from this repo's directory. You can use something like `python -m SimpleHTTPServer` run on the command line in the repo's directory or use VSCode's Live Server extension. See [How do you set up a local testing server?](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server) for more info.
16 | - Register for a Daily.co account and create a Room [in the dashboard](https://dashboard.daily.co/rooms), and use that room URL on this page. You can join that room directly using other browser tabs or another device.
17 | - Visit the server in your browser! You can use multiple tabs for testing out the chat experience.
18 |
--------------------------------------------------------------------------------
/prebuilt/chat-overlay/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/prebuilt/chat-overlay/image.jpg
--------------------------------------------------------------------------------
/prebuilt/chat-overlay/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | fullscreen embedded prebuilt with chat overlay
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/prebuilt/chat-overlay/main.css:
--------------------------------------------------------------------------------
1 | #chatBox {
2 | position: fixed;
3 | bottom: 0;
4 | z-index: 1000;
5 | padding-left: 1.75em;
6 | margin-bottom: 7.5em;
7 | }
8 |
9 | @media (min-width: 701px) {
10 | #chatBox {
11 | padding-left: 0;
12 | }
13 | }
14 |
15 | @media (min-width: 801px) {
16 | #chatBox {
17 | margin-bottom: 4em;
18 | }
19 | }
20 |
21 | #chatBox > input {
22 | border: 1px solid #c8d1dc;
23 | border-radius: 5px;
24 | width: 74vw;
25 | max-width: 22em;
26 | background-color: rgba(255, 255, 255)
27 | }
28 |
29 | #chatBoxButton {
30 | border: 1px solid #c8d1dc;
31 | border-radius: 4px;
32 | background-color: rgba(255, 255, 255);
33 | }
34 |
35 | #chatBoxTexts > p {
36 | background-color: #121a2478;
37 | display: table;
38 | padding: 0.1em;
39 | margin: 0;
40 | color: white;
41 | font-family: monospace;
42 | font-size: 1.25em;
43 | border-radius: 0.5em;
44 | word-wrap: break-word;
45 | animation: fadeIn .5s cubic-bezier(.95,.05,.8,.04) forwards, fadeOut 15s cubic-bezier(.95,.05,.8,.04) forwards;
46 | }
47 |
48 | .hidden {
49 | visibility: hidden;
50 | display: none;
51 | }
52 |
53 | @keyframes fadeIn {
54 | from { opacity: 0; }
55 | to { opacity: 1; }
56 | }
57 |
58 | @keyframes fadeOut {
59 | from { opacity: 1; }
60 | to { opacity: 0; }
61 | }
--------------------------------------------------------------------------------
/prebuilt/chat-overlay/main.js:
--------------------------------------------------------------------------------
1 | const ROOM_URL = "https://acb.daily.co/no-chat"
2 |
3 | async function setup() {
4 | callFrame = DailyIframe.createFrame({
5 | url: ROOM_URL,
6 | showLeaveButton: true,
7 | iframeStyle: {
8 | position: 'fixed',
9 | border: 0,
10 | top: 0,
11 | left: 0,
12 | width: '100%',
13 | height: '100%',
14 | },
15 | });
16 | await callFrame.join();
17 | document.getElementById("chatBox").classList.remove('hidden');
18 | addChatBox()
19 | }
20 |
21 | function addChatMsg(event) {
22 | const chatMsg = document.createElement("p");
23 | chatMsg.innerText = (`${callFrame.participants()[event.fromId].user_name }: ${ event.data.message}`)
24 | document.getElementById("chatBoxTexts").appendChild(chatMsg)
25 | }
26 |
27 | function addChatBox() {
28 | callFrame.on("app-message", (event) => addChatMsg(event))
29 | }
30 |
31 | document.getElementById("chatBox").addEventListener("keyup", (event) => {
32 | if (event.key === 'Enter') {
33 | event.preventDefault();
34 | document.getElementById("chatBoxButton").click();
35 | }
36 | });
37 |
38 | document.getElementById("chatBoxButton").addEventListener("click", () => {
39 | let inputVal = document.getElementById("chatBoxInput").value;
40 | callFrame.sendAppMessage({ message: inputVal }, '*');
41 | const chatMsg = document.createElement("p");
42 | chatMsg.innerText = (`${callFrame.participants().local.user_name }: ${ inputVal }`);
43 | document.getElementById("chatBoxTexts").appendChild(chatMsg);
44 | document.getElementById("chatBoxInput").value = "";
45 | })
--------------------------------------------------------------------------------
/prebuilt/ios-webview/README.md:
--------------------------------------------------------------------------------
1 | # WKWebView prebuilt example
2 |
3 | 
4 |
5 | ## What does this demo do?
6 |
7 | Embeds the Daily Prebuilt UI in native iOS applicaiton using WKWebView
8 |
9 | Notes:
10 |
11 | - `getUserMedia` support was added to WKWebView in iOS >=14.4
12 | - You must add both `NSCameraUsageDescription` & `NSMicrophoneUsageDescription` to `info.plist` or `navigator.mediaDevices` will resolve as undefined
13 |
14 | ### Getting started
15 |
16 | - Load XCode project
17 | - Update the url property found in [ViewController.swift](./WebViewPrebuilt/ViewController.swift)
18 | - Run on device / simulator targeting iOS >=14.4
19 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WebViewPrebuilt
4 | //
5 | // Created by Daily on 10/06/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/WebViewPrebuilt/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/prebuilt/ios-webview/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daily-demos/examples/562a32174d6a2c5b677a32b51d990f0ecfdb2043/prebuilt/ios-webview/image.png
--------------------------------------------------------------------------------