diff --git a/dailyjs/basic-call/components/Intro/Intro.js b/dailyjs/basic-call/components/Intro/Intro.js
index 301509e..2fe43bb 100644
--- a/dailyjs/basic-call/components/Intro/Intro.js
+++ b/dailyjs/basic-call/components/Intro/Intro.js
@@ -23,10 +23,12 @@ export const Intro = ({
onJoin,
title,
fetching = false,
+ forceFetchToken = false,
+ forceOwner = false,
}) => {
const [roomName, setRoomName] = useState();
- const [owner, setOwner] = useState(false);
- const [fetchToken, setFetchToken] = useState(false);
+ const [fetchToken, setFetchToken] = useState(forceFetchToken);
+ const [owner, setOwner] = useState(forceOwner);
useEffect(() => {
setRoomName(room);
@@ -51,10 +53,12 @@ export const Intro = ({
required
/>
-
- setFetchToken(e.target.checked)} />
-
- {fetchToken && (
+ {!forceFetchToken && (
+
+ setFetchToken(e.target.checked)} />
+
+ )}
+ {fetchToken && !forceOwner && (
setOwner(e.target.checked)} />
@@ -79,6 +83,8 @@ Intro.propTypes = {
domain: PropTypes.string.isRequired,
onJoin: PropTypes.func.isRequired,
fetching: PropTypes.bool,
+ forceFetchToken: PropTypes.bool,
+ forceOwner: PropTypes.bool,
};
export default Intro;
diff --git a/dailyjs/basic-call/components/Room/Header.js b/dailyjs/basic-call/components/Room/Header.js
index 401d272..a18e5d5 100644
--- a/dailyjs/basic-call/components/Room/Header.js
+++ b/dailyjs/basic-call/components/Room/Header.js
@@ -1,8 +1,10 @@
import React, { useMemo } from 'react';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
+import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
export const Header = () => {
const { participantCount } = useParticipants();
+ const { customCapsule } = useUIState();
return useMemo(
() => (
@@ -14,6 +16,12 @@ export const Header = () => {
participantCount > 1 ? 'participants' : 'participant'
}`}
+ {customCapsule && (
+
+ {customCapsule.variant === 'recording' && }
+ {customCapsule.label}
+
+ )}
),
- [participantCount]
+ [participantCount, customCapsule]
);
};
diff --git a/dailyjs/basic-call/pages/index.js b/dailyjs/basic-call/pages/index.js
index d950ebe..67cdc83 100644
--- a/dailyjs/basic-call/pages/index.js
+++ b/dailyjs/basic-call/pages/index.js
@@ -21,6 +21,8 @@ export default function Index({
domain,
isConfigured = false,
predefinedRoom = false,
+ forceFetchToken = false,
+ forceOwner = false,
asides,
modals,
customTrayComponent,
@@ -74,6 +76,8 @@ export default function Index({
) : (
(
-
+// Extend our basic call app component with the live streaming context
+export const AppWithLiveStreaming = () => (
+
-
+
);
-export default AppWithChat;
+export default AppWithLiveStreaming;
diff --git a/dailyjs/live-streaming/components/App/index.js b/dailyjs/live-streaming/components/App/index.js
index 770f031..c46acf2 100644
--- a/dailyjs/live-streaming/components/App/index.js
+++ b/dailyjs/live-streaming/components/App/index.js
@@ -1 +1 @@
-export { AppWithChat as default } from './App';
+export { AppWithLiveStreaming as default } from './App';
diff --git a/dailyjs/live-streaming/components/LiveStreamingModal/LiveStreamingModal.js b/dailyjs/live-streaming/components/LiveStreamingModal/LiveStreamingModal.js
index 337e1ed..6280635 100644
--- a/dailyjs/live-streaming/components/LiveStreamingModal/LiveStreamingModal.js
+++ b/dailyjs/live-streaming/components/LiveStreamingModal/LiveStreamingModal.js
@@ -1,25 +1,49 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { Button } from '@dailyjs/shared/components/Button';
import Field from '@dailyjs/shared/components/Field';
import { TextInput } from '@dailyjs/shared/components/Input';
import Modal from '@dailyjs/shared/components/Modal';
+import { Well } from '@dailyjs/shared/components/Well';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
+import { useLiveStreaming } from '../../contexts/LiveStreamingProvider';
export const LIVE_STREAMING_MODAL = 'live-streaming';
export const LiveStreamingModal = () => {
const { callObject } = useCallState();
const { currentModals, closeModal } = useUIState();
+ const { isStreaming, streamError } = useLiveStreaming();
+ const [pending, setPending] = useState(false);
const [rtmpUrl, setRtmpUrl] = useState('');
+ useEffect(() => {
+ // Reset pending state whenever stream state changes
+ setPending(false);
+ }, [isStreaming]);
+
+ function startLiveStream() {
+ setPending(true);
+ callObject.startLiveStreaming({ rtmpUrl });
+ }
+
+ function stopLiveStreaming() {
+ setPending(true);
+ callObject.stopLiveStreaming();
+ }
+
return (
closeModal(LIVE_STREAMING_MODAL)}
>
-
+ {streamError && (
+
+ Unable to start stream. Error message: {streamError}
+
+ )}
+
{
onChange={(e) => setRtmpUrl(e.target.value)}
/>
-
-
+ {!isStreaming ? (
+
+ ) : (
+
+ )}
);
};
diff --git a/dailyjs/live-streaming/components/Tray/Tray.js b/dailyjs/live-streaming/components/Tray/Tray.js
index d9ae3f5..b185aa3 100644
--- a/dailyjs/live-streaming/components/Tray/Tray.js
+++ b/dailyjs/live-streaming/components/Tray/Tray.js
@@ -4,15 +4,18 @@ import { TrayButton } from '@dailyjs/shared/components/Tray';
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
import { ReactComponent as IconStream } from '@dailyjs/shared/icons/streaming-md.svg';
+import { useLiveStreaming } from '../../contexts/LiveStreamingProvider';
import { LIVE_STREAMING_MODAL } from '../LiveStreamingModal';
export const Tray = () => {
const { openModal } = useUIState();
+ const { isStreaming } = useLiveStreaming();
return (
<>
openModal(LIVE_STREAMING_MODAL)}
>
diff --git a/dailyjs/live-streaming/contexts/ChatProvider.js b/dailyjs/live-streaming/contexts/ChatProvider.js
deleted file mode 100644
index 6ad611a..0000000
--- a/dailyjs/live-streaming/contexts/ChatProvider.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import React, {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useState,
-} from 'react';
-import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
-import { nanoid } from 'nanoid';
-import PropTypes from 'prop-types';
-
-export const ChatContext = createContext();
-
-export const ChatProvider = ({ children }) => {
- const { callObject } = useCallState();
- const [chatHistory, setChatHistory] = useState([]);
- const [hasNewMessages, setHasNewMessages] = useState(false);
-
- const handleNewMessage = useCallback(
- (e) => {
- const participants = callObject.participants();
- const sender = participants[e.fromId].user_name
- ? participants[e.fromId].user_name
- : 'Guest';
-
- setChatHistory((oldState) => [
- ...oldState,
- { sender, message: e.data.message, id: nanoid() },
- ]);
-
- setHasNewMessages(true);
- },
- [callObject]
- );
-
- const sendMessage = useCallback(
- (message) => {
- if (!callObject) {
- return false;
- }
-
- console.log('💬 Sending app message');
-
- callObject.sendAppMessage({ message }, '*');
-
- // Get the sender (local participant) name
- const sender = callObject.participants().local.user_name
- ? callObject.participants().local.user_name
- : 'Guest';
-
- // Update local chat history
- return setChatHistory((oldState) => [
- ...oldState,
- { sender, message, id: nanoid(), isLocal: true },
- ]);
- },
- [callObject]
- );
-
- useEffect(() => {
- if (!callObject) {
- return false;
- }
-
- console.log(`💬 Chat provider listening for app messages`);
-
- callObject.on('app-message', handleNewMessage);
-
- return () => callObject.off('app-message', handleNewMessage);
- }, [callObject, handleNewMessage]);
-
- return (
-
- {children}
-
- );
-};
-
-ChatProvider.propTypes = {
- children: PropTypes.node,
-};
-
-export const useChat = () => useContext(ChatContext);
diff --git a/dailyjs/live-streaming/contexts/LiveStreamingProvider.js b/dailyjs/live-streaming/contexts/LiveStreamingProvider.js
new file mode 100644
index 0000000..16e1e33
--- /dev/null
+++ b/dailyjs/live-streaming/contexts/LiveStreamingProvider.js
@@ -0,0 +1,70 @@
+import React, {
+ useState,
+ createContext,
+ useContext,
+ useEffect,
+ useCallback,
+} from 'react';
+import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
+import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
+import PropTypes from 'prop-types';
+
+export const LiveStreamingContext = createContext();
+
+export const LiveStreamingProvider = ({ children }) => {
+ const [isStreaming, setIsStreaming] = useState(false);
+ const [streamError, setStreamError] = useState();
+ const { setCustomCapsule } = useUIState();
+ const { callObject } = useCallState();
+
+ const handleStreamStarted = useCallback(() => {
+ console.log('📺 Live stream started');
+ setIsStreaming(true);
+ setCustomCapsule({ variant: 'recording', label: 'Live streaming' });
+ }, [setCustomCapsule]);
+
+ const handleStreamStopped = useCallback(() => {
+ console.log('📺 Live stream stopped');
+ setIsStreaming(false);
+ setCustomCapsule(null);
+ }, [setCustomCapsule]);
+
+ const handleStreamError = useCallback(
+ (e) => {
+ setIsStreaming(false);
+ setCustomCapsule(null);
+ setStreamError(e.errorMsg);
+ },
+ [setCustomCapsule]
+ );
+
+ useEffect(() => {
+ if (!callObject) {
+ return false;
+ }
+
+ console.log('📺 Live streaming provider listening for stream events');
+
+ callObject.on('live-streaming-started', handleStreamStarted);
+ callObject.on('live-streaming-stopped', handleStreamStopped);
+ callObject.on('live-streaming-error', handleStreamError);
+
+ return () => {
+ callObject.off('live-streaming-started', handleStreamStarted);
+ callObject.off('live-streaming-stopped', handleStreamStopped);
+ callObject.on('live-streaming-error', handleStreamError);
+ };
+ }, [callObject, handleStreamStarted, handleStreamStopped, handleStreamError]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+LiveStreamingProvider.propTypes = {
+ children: PropTypes.node,
+};
+
+export const useLiveStreaming = () => useContext(LiveStreamingContext);
diff --git a/dailyjs/live-streaming/hooks/useMessageSound.js b/dailyjs/live-streaming/hooks/useMessageSound.js
deleted file mode 100644
index e894a1c..0000000
--- a/dailyjs/live-streaming/hooks/useMessageSound.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useEffect, useMemo } from 'react';
-
-import { useSound } from '@dailyjs/shared/hooks/useSound';
-import { debounce } from 'debounce';
-
-/**
- * Convenience hook to play `join.mp3` when participants join the call
- */
-export const useMessageSound = () => {
- const { load, play } = useSound('message.mp3');
-
- useEffect(() => {
- load();
- }, [load]);
-
- return useMemo(() => debounce(() => play(), 5000, true), [play]);
-};
-
-export default useMessageSound;
diff --git a/dailyjs/live-streaming/pages/_app.js b/dailyjs/live-streaming/pages/_app.js
index 1ff7cc2..7a097c4 100644
--- a/dailyjs/live-streaming/pages/_app.js
+++ b/dailyjs/live-streaming/pages/_app.js
@@ -1,12 +1,12 @@
import React from 'react';
import App from '@dailyjs/basic-call/pages/_app';
-import AppWithChat from '../components/App';
+import AppWithLiveStreaming from '../components/App';
import { LiveStreamingModal } from '../components/LiveStreamingModal';
import Tray from '../components/Tray';
App.modals = [LiveStreamingModal];
-App.customAppComponent = ;
+App.customAppComponent = ;
App.customTrayComponent = ;
export default App;
diff --git a/dailyjs/live-streaming/pages/index.js b/dailyjs/live-streaming/pages/index.js
index 5f31f95..b82698b 100644
--- a/dailyjs/live-streaming/pages/index.js
+++ b/dailyjs/live-streaming/pages/index.js
@@ -10,6 +10,8 @@ export async function getStaticProps() {
props: {
domain: process.env.DAILY_DOMAIN || null,
isConfigured,
+ forceFetchToken: true,
+ forceOwner: true,
},
};
}
diff --git a/dailyjs/shared/components/GlobalStyle/GlobalStyle.js b/dailyjs/shared/components/GlobalStyle/GlobalStyle.js
index 26be0c5..c54fe71 100644
--- a/dailyjs/shared/components/GlobalStyle/GlobalStyle.js
+++ b/dailyjs/shared/components/GlobalStyle/GlobalStyle.js
@@ -12,6 +12,7 @@ export const GlobalStyle = () => (
--primary-dark: ${theme.primary.dark};
--secondary-default: ${theme.secondary.default};
--secondary-dark: ${theme.secondary.dark};
+ --secondary-light: ${theme.secondary.light};
--blue-light: ${theme.blue.light};
--blue-default: ${theme.blue.default};
--blue-dark: ${theme.blue.dark};
diff --git a/dailyjs/shared/contexts/UIStateProvider.js b/dailyjs/shared/contexts/UIStateProvider.js
index ab6c483..2aab79b 100644
--- a/dailyjs/shared/contexts/UIStateProvider.js
+++ b/dailyjs/shared/contexts/UIStateProvider.js
@@ -12,6 +12,7 @@ export const UIStateProvider = ({
}) => {
const [showAside, setShowAside] = useState();
const [activeModals, setActiveModals] = useState({});
+ const [customCapsule, setCustomCapsule] = useState();
const openModal = useCallback((modalName) => {
setActiveModals((prevState) => ({
@@ -45,6 +46,8 @@ export const UIStateProvider = ({
toggleAside,
showAside,
setShowAside,
+ customCapsule,
+ setCustomCapsule,
}}
>
{children}
diff --git a/dailyjs/shared/styles/defaultTheme.js b/dailyjs/shared/styles/defaultTheme.js
index bce6547..ba37156 100644
--- a/dailyjs/shared/styles/defaultTheme.js
+++ b/dailyjs/shared/styles/defaultTheme.js
@@ -10,6 +10,7 @@ export const defaultTheme = {
secondary: {
default: '#FF9254',
dark: '#FB651E',
+ light: '#FF9254',
},
blue: {