diff --git a/dailyjs/basic-call/components/App/App.js b/dailyjs/basic-call/components/App/App.js
index e025f3a..d31c62f 100644
--- a/dailyjs/basic-call/components/App/App.js
+++ b/dailyjs/basic-call/components/App/App.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useMemo } from 'react';
+import React, { useMemo } from 'react';
import ExpiryTimer from '@dailyjs/shared/components/ExpiryTimer';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { useCallUI } from '@dailyjs/shared/hooks/useCallUI';
diff --git a/dailyjs/basic-call/components/Room/Room.js b/dailyjs/basic-call/components/Room/Room.js
index 33d8cbd..45210db 100644
--- a/dailyjs/basic-call/components/Room/Room.js
+++ b/dailyjs/basic-call/components/Room/Room.js
@@ -1,64 +1,16 @@
import React from 'react';
-import { Audio } from '@dailyjs/shared/components/Audio';
-import { BasicTray } from '@dailyjs/shared/components/Tray';
-import {
- WaitingRoomModal,
- WaitingRoomNotification,
-} from '@dailyjs/shared/components/WaitingRoom';
-import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
-import { useWaitingRoom } from '@dailyjs/shared/contexts/WaitingRoomProvider';
-import useJoinSound from '@dailyjs/shared/hooks/useJoinSound';
import { VideoGrid } from '../VideoGrid';
import { Header } from './Header';
+import RoomContainer from './RoomContainer';
-export const Room = () => {
- const { setShowModal, showModal } = useWaitingRoom();
- const { localParticipant } = useParticipants();
-
- useJoinSound();
-
- return (
-
-
-
-
-
-
-
- {/* Show waiting room notification & modal if call owner */}
- {localParticipant?.isOwner && (
- <>
-
- {showModal && (
-
setShowModal(false)} />
- )}
- >
- )}
-
-
-
-
-
-
- );
-};
+export const Room = () => (
+
+
+
+
+
+
+);
export default Room;
diff --git a/dailyjs/basic-call/components/Room/RoomContainer.js b/dailyjs/basic-call/components/Room/RoomContainer.js
new file mode 100644
index 0000000..da0071f
--- /dev/null
+++ b/dailyjs/basic-call/components/Room/RoomContainer.js
@@ -0,0 +1,62 @@
+import React, { useMemo } from 'react';
+import { Audio } from '@dailyjs/shared/components/Audio';
+import { BasicTray } from '@dailyjs/shared/components/Tray';
+import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
+import useJoinSound from '@dailyjs/shared/hooks/useJoinSound';
+import PropTypes from 'prop-types';
+import WaitingRoom from '../WaitingRoom';
+
+const RoomContainer = ({ children }) => {
+ const { localParticipant } = useParticipants();
+ const isOwner = !!localParticipant?.isOwner;
+
+ useJoinSound();
+
+ const roomComponents = useMemo(
+ () => (
+ <>
+ {/* Show waiting room notification & modal if call owner */}
+ {isOwner && }
+
+ {/* Tray buttons */}
+
+
+ {/* Audio tags */}
+
+ >
+ ),
+ [isOwner]
+ );
+
+ return (
+
+ {children}
+ {roomComponents}
+
+
+
+ );
+};
+
+RoomContainer.propTypes = {
+ children: PropTypes.node,
+};
+
+export default React.memo(RoomContainer, () => true);
diff --git a/dailyjs/basic-call/components/WaitingRoom/WaitingRoom.js b/dailyjs/basic-call/components/WaitingRoom/WaitingRoom.js
new file mode 100644
index 0000000..c4b2605
--- /dev/null
+++ b/dailyjs/basic-call/components/WaitingRoom/WaitingRoom.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import {
+ WaitingRoomModal,
+ WaitingRoomNotification,
+} from '@dailyjs/shared/components/WaitingRoom';
+import { useWaitingRoom } from '@dailyjs/shared/contexts/WaitingRoomProvider';
+
+export const WaitingRoom = () => {
+ const { setShowModal, showModal } = useWaitingRoom();
+ return (
+ <>
+
+ {showModal && setShowModal(false)} />}
+ >
+ );
+};
+
+export default WaitingRoom;
diff --git a/dailyjs/basic-call/components/WaitingRoom/index.js b/dailyjs/basic-call/components/WaitingRoom/index.js
new file mode 100644
index 0000000..48d19f6
--- /dev/null
+++ b/dailyjs/basic-call/components/WaitingRoom/index.js
@@ -0,0 +1 @@
+export { WaitingRoom as default } from './WaitingRoom';
diff --git a/dailyjs/basic-call/pages/api/createRoom.js b/dailyjs/basic-call/pages/api/createRoom.js
index d8e4a02..04a61c8 100644
--- a/dailyjs/basic-call/pages/api/createRoom.js
+++ b/dailyjs/basic-call/pages/api/createRoom.js
@@ -15,6 +15,7 @@ export default async function handler(req, res) {
properties: {
exp: Math.round(Date.now() / 1000) + (expiryMinutes || 5) * 60, // expire in x minutes
eject_at_room_exp: true,
+ enable_knocking: privacy !== 'public',
},
}),
};
diff --git a/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js
index 47c587e..51426a9 100644
--- a/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js
+++ b/dailyjs/flying-emojis/components/FlyingEmojis/FlyingEmojisOverlay.js
@@ -81,7 +81,7 @@ export const FlyingEmojisOverlay = () => {
// Remove all event listeners on unmount to prevent console warnings
useEffect(
() => () =>
- overlayRef.current.childNodes.forEach((n) =>
+ overlayRef.current?.childNodes.forEach((n) =>
n.removeEventListener('animationend', handleRemoveFlyingEmoji)
),
[handleRemoveFlyingEmoji]
diff --git a/dailyjs/flying-emojis/index.js b/dailyjs/flying-emojis/index.js
new file mode 100644
index 0000000..9044efc
--- /dev/null
+++ b/dailyjs/flying-emojis/index.js
@@ -0,0 +1 @@
+// Note: I am here because next-transpile-modules requires a mainfile
diff --git a/dailyjs/live-fitness/README.md b/dailyjs/live-fitness/README.md
index 6fa7e3c..ed0d0e2 100644
--- a/dailyjs/live-fitness/README.md
+++ b/dailyjs/live-fitness/README.md
@@ -1 +1,9 @@
# Live Fitness
+
+Brings together
+
+- Live streaming
+- Recording
+- Flying emojis
+- Pagination / track management
+- Text chat
diff --git a/dailyjs/live-fitness/components/App/App.js b/dailyjs/live-fitness/components/App/App.js
new file mode 100644
index 0000000..8c3b08a
--- /dev/null
+++ b/dailyjs/live-fitness/components/App/App.js
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import App from '@dailyjs/basic-call/components/App';
+import FlyingEmojiOverlay from '@dailyjs/flying-emojis/components/FlyingEmojis';
+import { ChatProvider } from '@dailyjs/text-chat/contexts/ChatProvider';
+
+export const LiveFitnessApp = () => (
+ <>
+
+
+
+
+ >
+);
+
+export default LiveFitnessApp;
diff --git a/dailyjs/live-fitness/components/App/index.js b/dailyjs/live-fitness/components/App/index.js
new file mode 100644
index 0000000..63c0538
--- /dev/null
+++ b/dailyjs/live-fitness/components/App/index.js
@@ -0,0 +1 @@
+export { LiveFitnessApp as default } from './App';
diff --git a/dailyjs/live-fitness/components/Tray/Tray.js b/dailyjs/live-fitness/components/Tray/Tray.js
new file mode 100644
index 0000000..abf18ba
--- /dev/null
+++ b/dailyjs/live-fitness/components/Tray/Tray.js
@@ -0,0 +1,12 @@
+import React from 'react';
+import FlyingEmojiTrayButton from '@dailyjs/flying-emojis/components/Tray';
+import ChatTrayButton from '@dailyjs/text-chat/components/Tray';
+
+export const Tray = () => (
+ <>
+
+
+ >
+);
+
+export default Tray;
diff --git a/dailyjs/live-fitness/components/Tray/index.js b/dailyjs/live-fitness/components/Tray/index.js
new file mode 100644
index 0000000..100bcc8
--- /dev/null
+++ b/dailyjs/live-fitness/components/Tray/index.js
@@ -0,0 +1 @@
+export { Tray as default } from './Tray';
diff --git a/dailyjs/live-fitness/next.config.js b/dailyjs/live-fitness/next.config.js
index 9a0a6ee..889e818 100644
--- a/dailyjs/live-fitness/next.config.js
+++ b/dailyjs/live-fitness/next.config.js
@@ -2,6 +2,8 @@ const withPlugins = require('next-compose-plugins');
const withTM = require('next-transpile-modules')([
'@dailyjs/shared',
'@dailyjs/basic-call',
+ '@dailyjs/flying-emojis',
+ '@dailyjs/text-chat',
]);
const packageJson = require('./package.json');
diff --git a/dailyjs/live-fitness/package.json b/dailyjs/live-fitness/package.json
index 7ac2fc7..8432623 100644
--- a/dailyjs/live-fitness/package.json
+++ b/dailyjs/live-fitness/package.json
@@ -12,6 +12,8 @@
"dependencies": {
"@dailyjs/shared": "*",
"@dailyjs/basic-call": "*",
+ "@dailyjs/flying-emojis": "*",
+ "@dailyjs/text-chat": "*",
"next": "^11.0.0",
"pluralize": "^8.0.0",
"react": "^17.0.2",
diff --git a/dailyjs/live-fitness/pages/[room].js b/dailyjs/live-fitness/pages/[room].js
index deee255..726c882 100644
--- a/dailyjs/live-fitness/pages/[room].js
+++ b/dailyjs/live-fitness/pages/[room].js
@@ -1,6 +1,4 @@
import React, { useState, useEffect } from 'react';
-import App from '@dailyjs/basic-call/components/App';
-import Loader from '@dailyjs/shared/components/Loader';
import { CallProvider } from '@dailyjs/shared/contexts/CallProvider';
import { MediaDeviceProvider } from '@dailyjs/shared/contexts/MediaDeviceProvider';
import { ParticipantsProvider } from '@dailyjs/shared/contexts/ParticipantsProvider';
@@ -10,12 +8,19 @@ import { WaitingRoomProvider } from '@dailyjs/shared/contexts/WaitingRoomProvide
import getDemoProps from '@dailyjs/shared/lib/demoProps';
import { useRouter } from 'next/router';
import PropTypes from 'prop-types';
+import App from '../components/App';
/**
* Room page
* ---
*/
-export default function Room({ room, instructor, domain }) {
+export default function Room({
+ room,
+ instructor,
+ domain,
+ customTrayComponent,
+ asides,
+}) {
const [token, setToken] = useState();
const [tokenError, setTokenError] = useState();
@@ -65,11 +70,15 @@ export default function Room({ room, instructor, domain }) {
return Fetching token...
;
}
+ if (tokenError) {
+ return Token error
;
+ }
+
/**
* Main call UI
*/
return (
-
+
@@ -89,6 +98,8 @@ Room.propTypes = {
room: PropTypes.string.isRequired,
domain: PropTypes.string.isRequired,
instructor: PropTypes.bool,
+ customTrayComponent: PropTypes.node,
+ asides: PropTypes.arrayOf(PropTypes.func),
};
export async function getServerSideProps(context) {
diff --git a/dailyjs/live-fitness/pages/_app.js b/dailyjs/live-fitness/pages/_app.js
index 0ee1a00..60bb86c 100644
--- a/dailyjs/live-fitness/pages/_app.js
+++ b/dailyjs/live-fitness/pages/_app.js
@@ -1,30 +1,9 @@
import React from 'react';
-import GlobalHead from '@dailyjs/shared/components/GlobalHead';
-import GlobalStyle from '@dailyjs/shared/components/GlobalStyle';
-import Head from 'next/head';
-import PropTypes from 'prop-types';
+import App from '@dailyjs/basic-call/pages/_app';
+import ChatAside from '@dailyjs/text-chat/components/ChatAside';
+import Tray from '../components/Tray';
-function App({ Component, pageProps }) {
- return (
- <>
-
- Daily - {process.env.PROJECT_TITLE}
-
-
-
-
- >
- );
-}
-
-App.defaultProps = {
- Component: null,
- pageProps: {},
-};
-
-App.propTypes = {
- Component: PropTypes.elementType,
- pageProps: PropTypes.object,
-};
+App.customTrayComponent = ;
+App.asides = [ChatAside];
export default App;
diff --git a/dailyjs/live-streaming/components/App/App.js b/dailyjs/live-streaming/components/App/App.js
deleted file mode 100644
index 4749551..0000000
--- a/dailyjs/live-streaming/components/App/App.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react';
-
-import App from '@dailyjs/basic-call/components/App';
-import { LiveStreamingProvider } from '../../contexts/LiveStreamingProvider';
-
-// Extend our basic call app component with the live streaming context
-export const AppWithLiveStreaming = () => (
-
-
-
-);
-
-export default AppWithLiveStreaming;
diff --git a/dailyjs/live-streaming/components/App/index.js b/dailyjs/live-streaming/components/App/index.js
deleted file mode 100644
index c46acf2..0000000
--- a/dailyjs/live-streaming/components/App/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { AppWithLiveStreaming as default } from './App';
diff --git a/dailyjs/live-streaming/components/Tray/Tray.js b/dailyjs/live-streaming/components/Tray/Tray.js
index b185aa3..fa22345 100644
--- a/dailyjs/live-streaming/components/Tray/Tray.js
+++ b/dailyjs/live-streaming/components/Tray/Tray.js
@@ -12,15 +12,13 @@ export const Tray = () => {
const { isStreaming } = useLiveStreaming();
return (
- <>
- openModal(LIVE_STREAMING_MODAL)}
- >
-
-
- >
+ openModal(LIVE_STREAMING_MODAL)}
+ >
+
+
);
};
diff --git a/dailyjs/recording/components/Tray/Tray.js b/dailyjs/recording/components/Tray/Tray.js
index c26f253..6eeba01 100644
--- a/dailyjs/recording/components/Tray/Tray.js
+++ b/dailyjs/recording/components/Tray/Tray.js
@@ -32,15 +32,13 @@ export const Tray = () => {
].includes(recordingState);
return (
- <>
- openModal(RECORDING_MODAL)}
- >
-
-
- >
+ openModal(RECORDING_MODAL)}
+ >
+
+
);
};
diff --git a/dailyjs/shared/components/MessageCard/MessageCard.js b/dailyjs/shared/components/MessageCard/MessageCard.js
index 89a4f79..19edbc7 100644
--- a/dailyjs/shared/components/MessageCard/MessageCard.js
+++ b/dailyjs/shared/components/MessageCard/MessageCard.js
@@ -15,7 +15,7 @@ export const MessageCard = ({
hideBack = false,
onBack,
}) => (
-
+
{header && {header}}
{children && {children}}
{!hideBack && (
@@ -27,13 +27,11 @@ export const MessageCard = ({
)}
)}
- {error && (
-
- )}
+
);
diff --git a/dailyjs/shared/components/Tray/TrayButton.js b/dailyjs/shared/components/Tray/TrayButton.js
new file mode 100644
index 0000000..dffece5
--- /dev/null
+++ b/dailyjs/shared/components/Tray/TrayButton.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import Button from '@dailyjs/shared/components/Button';
+import PropTypes from 'prop-types';
+
+export const TrayButton = ({ children, label, onClick, orange = false }) => (
+
+
+ {label}
+
+
+
+);
+
+TrayButton.propTypes = {
+ children: PropTypes.node,
+ onClick: PropTypes.func,
+ orange: PropTypes.bool,
+ label: PropTypes.string.isRequired,
+};
+
+export default TrayButton;
diff --git a/dailyjs/shared/components/Tray/TrayMicButton.js b/dailyjs/shared/components/Tray/TrayMicButton.js
new file mode 100644
index 0000000..68bea2a
--- /dev/null
+++ b/dailyjs/shared/components/Tray/TrayMicButton.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import { TrayButton } from '@dailyjs/shared/components/Tray';
+import { useAudioLevel } from '@dailyjs/shared/hooks/useAudioLevel';
+import { ReactComponent as IconMicOff } from '@dailyjs/shared/icons/mic-off-md.svg';
+import { ReactComponent as IconMicOn } from '@dailyjs/shared/icons/mic-on-md.svg';
+
+import PropTypes from 'prop-types';
+
+export const TrayMicButton = ({ isMuted, onClick }) => {
+ const audioLevel = useAudioLevel('local');
+
+ return (
+
+ {isMuted ? : }
+
+ );
+};
+
+TrayMicButton.propTypes = {
+ isMuted: PropTypes.bool,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default TrayMicButton;
diff --git a/dailyjs/shared/hooks/useAudioLevel.js b/dailyjs/shared/hooks/useAudioLevel.js
new file mode 100644
index 0000000..dc8a193
--- /dev/null
+++ b/dailyjs/shared/hooks/useAudioLevel.js
@@ -0,0 +1,36 @@
+import { useEffect, useState } from 'react';
+
+export const useAudioLevel = (sessionId) => {
+ const [audioLevel, setAudioLevel] = useState(0);
+
+ useEffect(() => {
+ if (!sessionId) {
+ return false;
+ }
+
+ const i = setInterval(async () => {
+ try {
+ if (!(window.rtcpeers && window.rtcpeers.sfu)) {
+ return;
+ }
+ const consumer =
+ window.rtcpeers.sfu.consumers[`${sessionId}/cam-audio`];
+ if (!(consumer && consumer.getStats)) {
+ return;
+ }
+ const level = Array.from((await consumer.getStats()).values()).find(
+ (s) => 'audioLevel' in s
+ ).audioLevel;
+ setAudioLevel(level);
+ } catch (e) {
+ console.error(e);
+ }
+ }, 2000);
+
+ return () => clearInterval(i);
+ }, [sessionId]);
+
+ return audioLevel;
+};
+
+export default useAudioLevel;
diff --git a/dailyjs/text-chat/components/Tray/Tray.js b/dailyjs/text-chat/components/Tray/Tray.js
index 84290bf..b9b12c8 100644
--- a/dailyjs/text-chat/components/Tray/Tray.js
+++ b/dailyjs/text-chat/components/Tray/Tray.js
@@ -11,17 +11,15 @@ export const Tray = () => {
const { hasNewMessages } = useChat();
return (
- <>
- {
- toggleAside(CHAT_ASIDE);
- }}
- >
-
-
- >
+ {
+ toggleAside(CHAT_ASIDE);
+ }}
+ >
+
+
);
};
diff --git a/dailyjs/text-chat/index.js b/dailyjs/text-chat/index.js
new file mode 100644
index 0000000..9044efc
--- /dev/null
+++ b/dailyjs/text-chat/index.js
@@ -0,0 +1 @@
+// Note: I am here because next-transpile-modules requires a mainfile