diff --git a/dailyjs/README.md b/dailyjs/README.md
index a927fec..81f366e 100644
--- a/dailyjs/README.md
+++ b/dailyjs/README.md
@@ -10,7 +10,15 @@ Send messages to other participants using sendAppMessage
### [πΊ Live streaming](./live-streaming)
-Broadcast call to a custom RTMP endpoint using a variety of difference layout modes
+Broadcast call to a custom RTMP endpoint using a variety of different layout modes
+
+### [βΊοΈ Recording](./recording)
+
+Record a call video and audio using both cloud and local modes
+
+### [π₯ Flying emojis](./flying-emojis)
+
+Send emoji reactions to all clients using sendAppMessage
### [π Pagination](./pagination)
diff --git a/dailyjs/basic-call/components/App/App.js b/dailyjs/basic-call/components/App/App.js
index 45b4c07..425457a 100644
--- a/dailyjs/basic-call/components/App/App.js
+++ b/dailyjs/basic-call/components/App/App.js
@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { useCallUI } from '@dailyjs/shared/hooks/useCallUI';
@@ -8,7 +8,20 @@ import { Asides } from './Asides';
import { Modals } from './Modals';
export const App = ({ customComponentForState }) => {
- const { state } = useCallState();
+ const { roomExp, state } = useCallState();
+ const [secs, setSecs] = useState();
+
+ // If room has an expiry time, we'll calculate how many seconds until expiry
+ useEffect(() => {
+ if (!roomExp) {
+ return false;
+ }
+ const i = setInterval(() => {
+ const timeLeft = Math.round((roomExp - Date.now()) / 1000);
+ setSecs(`${Math.floor(timeLeft / 60)}:${`0${timeLeft % 60}`.slice(-2)}`);
+ }, 1000);
+ return () => clearInterval(i);
+ }, [roomExp]);
const componentForState = useCallUI({
state,
@@ -17,7 +30,7 @@ export const App = ({ customComponentForState }) => {
});
// Memoize children to avoid unnecassary renders from HOC
- return useMemo(
+ const memoizedApp = useMemo(
() => (
{componentForState()}
@@ -38,11 +51,32 @@ export const App = ({ customComponentForState }) => {
),
[componentForState]
);
+
+ return (
+ <>
+ {roomExp &&
{secs}
} {memoizedApp}
+
+ >
+ );
};
App.propTypes = {
- asides: PropTypes.arrayOf(PropTypes.func),
- customComponentsForState: PropTypes.any,
+ customComponentForState: PropTypes.any,
};
export default App;
diff --git a/dailyjs/basic-call/components/CreatingRoom/CreatingRoom.js b/dailyjs/basic-call/components/CreatingRoom/CreatingRoom.js
new file mode 100644
index 0000000..4973120
--- /dev/null
+++ b/dailyjs/basic-call/components/CreatingRoom/CreatingRoom.js
@@ -0,0 +1,100 @@
+import React, { useState, useEffect } from 'react';
+import { Card, CardHeader, CardBody } from '@dailyjs/shared/components/Card';
+import Loader from '@dailyjs/shared/components/Loader';
+import { Well } from '@dailyjs/shared/components/Well';
+import PropTypes from 'prop-types';
+
+export const CreatingRoom = ({ onCreated }) => {
+ const [room, setRoom] = useState();
+ const [fetching, setFetching] = useState(false);
+ const [error, setError] = useState(false);
+
+ useEffect(() => {
+ if (room) return;
+
+ async function createRoom() {
+ setError(false);
+ setFetching(true);
+
+ console.log(`πͺ Creating new demo room...`);
+
+ // Create a room server side (using Next JS serverless)
+ const res = await fetch('/api/createRoom', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ const resJson = await res.json();
+
+ if (resJson.name) {
+ setFetching(false);
+ setRoom(resJson.name);
+ return;
+ }
+
+ setError(resJson.error || 'An unknown error occured');
+ setFetching(false);
+ }
+
+ createRoom();
+ }, [room]);
+
+ useEffect(() => {
+ if (!room || !onCreated) return;
+
+ console.log(`πͺ Room created: ${room}, joining now`);
+
+ onCreated(room, true);
+ }, [room, onCreated]);
+
+ return (
+
+ {fetching && (
+
+ Creating new demo room...
+
+ )}
+ {error && (
+
+ An error occured
+
+ {error}
+ An error occured when trying to create a demo room. Please check
+ that your environmental variables are correct and try again.
+
+
+ )}
+
+
+
+ );
+};
+
+CreatingRoom.propTypes = {
+ onCreated: PropTypes.func.isRequired,
+};
+
+export default CreatingRoom;
diff --git a/dailyjs/basic-call/components/CreatingRoom/index.js b/dailyjs/basic-call/components/CreatingRoom/index.js
new file mode 100644
index 0000000..c92b6f2
--- /dev/null
+++ b/dailyjs/basic-call/components/CreatingRoom/index.js
@@ -0,0 +1,2 @@
+export { CreatingRoom as default } from './CreatingRoom';
+export { CreatingRoom } from './CreatingRoom';
diff --git a/dailyjs/basic-call/pages/api/createRoom.js b/dailyjs/basic-call/pages/api/createRoom.js
new file mode 100644
index 0000000..1c8ff2b
--- /dev/null
+++ b/dailyjs/basic-call/pages/api/createRoom.js
@@ -0,0 +1,36 @@
+export default async function handler(req, res) {
+ if (req.method === 'POST') {
+ console.log(`Creating room on domain ${process.env.DAILY_DOMAIN}`);
+
+ const options = {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${process.env.DAILY_API_KEY}`,
+ },
+ body: JSON.stringify({
+ properties: {
+ exp: Math.round(Date.now() / 1000) + 5 * 60, // expire in 5 minutes
+ eject_at_room_exp: true,
+ },
+ }),
+ };
+
+ const dailyRes = await fetch(
+ `${process.env.DAILY_REST_DOMAIN || 'https://api.daily.co/v1'}/rooms`,
+ options
+ );
+
+ const { name, url, error } = await dailyRes.json();
+
+ if (error) {
+ return res.status(500).json({ error });
+ }
+
+ return res
+ .status(200)
+ .json({ name, url, domain: process.env.DAILY_DOMAIN });
+ }
+
+ return res.status(500);
+}
diff --git a/dailyjs/basic-call/pages/index.js b/dailyjs/basic-call/pages/index.js
index ea8a8da..d2c213b 100644
--- a/dailyjs/basic-call/pages/index.js
+++ b/dailyjs/basic-call/pages/index.js
@@ -6,9 +6,9 @@ import { TracksProvider } from '@dailyjs/shared/contexts/TracksProvider';
import { UIStateProvider } from '@dailyjs/shared/contexts/UIStateProvider';
import { WaitingRoomProvider } from '@dailyjs/shared/contexts/WaitingRoomProvider';
import getDemoProps from '@dailyjs/shared/lib/demoProps';
-
import PropTypes from 'prop-types';
import App from '../components/App';
+import { CreatingRoom } from '../components/CreatingRoom';
import { Intro, NotConfigured } from '../components/Intro';
/**
@@ -26,6 +26,7 @@ export default function Index({
forceFetchToken = false,
forceOwner = false,
subscribeToTracksAutomatically = true,
+ demoMode = false,
asides,
modals,
customTrayComponent,
@@ -75,22 +76,24 @@ export default function Index({
if (!isReady) {
return (
- {!isConfigured ? (
-
- ) : (
-
- fetchToken ? getMeetingToken(room, isOwner) : setRoomName(room)
- }
- />
- )}
+ {(() => {
+ if (!isConfigured) return ;
+ if (demoMode) return ;
+ return (
+
+ fetchToken ? getMeetingToken(room, isOwner) : setRoomName(room)
+ }
+ />
+ );
+ })()}
+
+ );
+};
+
+export default FlyingEmojisOverlay;
diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/index.js b/dailyjs/flying-emojis/components/FlyingEmojis/index.js
new file mode 100644
index 0000000..6e98cc3
--- /dev/null
+++ b/dailyjs/flying-emojis/components/FlyingEmojis/index.js
@@ -0,0 +1,2 @@
+export { FlyingEmojisOverlay } from './FlyingEmojisOverlay';
+export { FlyingEmojisOverlay as default } from './FlyingEmojisOverlay';
diff --git a/dailyjs/flying-emojis/components/Tray/Tray.js b/dailyjs/flying-emojis/components/Tray/Tray.js
new file mode 100644
index 0000000..113de3d
--- /dev/null
+++ b/dailyjs/flying-emojis/components/Tray/Tray.js
@@ -0,0 +1,86 @@
+import React, { useEffect, useState } from 'react';
+
+import Button from '@dailyjs/shared/components/Button';
+import { TrayButton } from '@dailyjs/shared/components/Tray';
+import { ReactComponent as IconStar } from '@dailyjs/shared/icons/star-md.svg';
+
+const COOLDOWN = 1500;
+
+export const Tray = () => {
+ const [showEmojis, setShowEmojis] = useState(false);
+ const [isThrottled, setIsThrottled] = useState(false);
+
+ function sendEmoji(emoji) {
+ // Dispatch custom event here so the local user can see their own emoji
+ window.dispatchEvent(
+ new CustomEvent('reaction_added', { detail: { emoji } })
+ );
+ setShowEmojis(false);
+ setIsThrottled(true);
+ }
+
+ // Pseudo-throttling (should ideally be done serverside)
+ useEffect(() => {
+ if (!isThrottled) {
+ return false;
+ }
+ const t = setTimeout(() => setIsThrottled(false), COOLDOWN);
+ return () => clearTimeout(t);
+ }, [isThrottled]);
+
+ return (
+