added basic live streaming
This commit is contained in:
parent
c67b79af28
commit
fb3f94f36b
|
|
@ -1,10 +1,18 @@
|
|||
import React from 'react';
|
||||
import DeviceSelectModal from '@dailyjs/shared/components/DeviceSelectModal/DeviceSelectModal';
|
||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||
|
||||
export const Modals = () => (
|
||||
<>
|
||||
<DeviceSelectModal />
|
||||
</>
|
||||
);
|
||||
export const Modals = () => {
|
||||
const { modals } = useUIState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeviceSelectModal />
|
||||
{modals.map((ModalComponent) => (
|
||||
<ModalComponent key={ModalComponent.name} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modals;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ function App({ Component, pageProps }) {
|
|||
<GlobalStyle />
|
||||
<Component
|
||||
asides={App.asides}
|
||||
modals={App.modals}
|
||||
customTrayComponent={App.customTrayComponent}
|
||||
customAppComponent={App.customAppComponent}
|
||||
{...pageProps}
|
||||
|
|
@ -33,6 +34,7 @@ App.propTypes = {
|
|||
};
|
||||
|
||||
App.asides = [];
|
||||
App.modals = [];
|
||||
App.customTrayComponent = null;
|
||||
App.customAppComponent = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default function Index({
|
|||
isConfigured = false,
|
||||
predefinedRoom = false,
|
||||
asides,
|
||||
modals,
|
||||
customTrayComponent,
|
||||
customAppComponent,
|
||||
}) {
|
||||
|
|
@ -99,7 +100,11 @@ export default function Index({
|
|||
* Main call UI
|
||||
*/
|
||||
return (
|
||||
<UIStateProvider asides={asides} customTrayComponent={customTrayComponent}>
|
||||
<UIStateProvider
|
||||
asides={asides}
|
||||
modals={modals}
|
||||
customTrayComponent={customTrayComponent}
|
||||
>
|
||||
<CallProvider domain={domain} room={roomName} token={token}>
|
||||
<ParticipantsProvider>
|
||||
<TracksProvider>
|
||||
|
|
@ -120,6 +125,7 @@ Index.propTypes = {
|
|||
predefinedRoom: PropTypes.bool,
|
||||
domain: PropTypes.string,
|
||||
asides: PropTypes.arrayOf(PropTypes.func),
|
||||
modals: PropTypes.arrayOf(PropTypes.func),
|
||||
customTrayComponent: PropTypes.node,
|
||||
customAppComponent: PropTypes.node,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import Aside from '@dailyjs/shared/components/Aside';
|
||||
import { Button } from '@dailyjs/shared/components/Button';
|
||||
import { TextInput } from '@dailyjs/shared/components/Input';
|
||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||
import { useChat } from '../../contexts/ChatProvider';
|
||||
import { useMessageSound } from '../../hooks/useMessageSound';
|
||||
|
||||
export const CHAT_ASIDE = 'chat';
|
||||
|
||||
export const ChatAside = () => {
|
||||
const { showAside, setShowAside } = useUIState();
|
||||
const { sendMessage, chatHistory, hasNewMessages, setHasNewMessages } =
|
||||
useChat();
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const playMessageSound = useMessageSound();
|
||||
|
||||
const chatWindowRef = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
// Clear out any new message notifications if we're showing the chat screen
|
||||
if (showAside === CHAT_ASIDE) {
|
||||
setHasNewMessages(false);
|
||||
}
|
||||
}, [showAside, chatHistory.length, setHasNewMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasNewMessages && showAside !== CHAT_ASIDE) {
|
||||
playMessageSound();
|
||||
}
|
||||
}, [playMessageSound, showAside, hasNewMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatWindowRef.current) {
|
||||
chatWindowRef.current.scrollTop = chatWindowRef.current.scrollHeight;
|
||||
}
|
||||
}, [chatHistory?.length]);
|
||||
|
||||
if (!showAside || showAside !== CHAT_ASIDE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Aside onClose={() => setShowAside(false)}>
|
||||
<div className="messages-container" ref={chatWindowRef}>
|
||||
{chatHistory.map((chatItem) => (
|
||||
<div
|
||||
className={chatItem.isLocal ? 'message local' : 'message'}
|
||||
key={chatItem.id}
|
||||
>
|
||||
<span className="content">{chatItem.message}</span>
|
||||
<span className="sender">{chatItem.sender}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<footer className="chat-footer">
|
||||
<TextInput
|
||||
value={newMessage}
|
||||
placeholder="Type message here"
|
||||
variant="transparent"
|
||||
onChange={(e) => setNewMessage(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
className="send-button"
|
||||
variant="transparent"
|
||||
disabled={!newMessage}
|
||||
onClick={() => {
|
||||
sendMessage(newMessage);
|
||||
setNewMessage('');
|
||||
}}
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</footer>
|
||||
<style jsx>{`
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin: var(--spacing-xxs);
|
||||
padding: var(--spacing-xxs);
|
||||
background: var(--gray-wash);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.message.local {
|
||||
background: var(--gray-light);
|
||||
}
|
||||
|
||||
.message.local .sender {
|
||||
color: var(--primary-default);
|
||||
}
|
||||
|
||||
.content {
|
||||
color: var(--text-mid);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sender {
|
||||
font-weight: var(--weight-medium);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.chat-footer {
|
||||
flex-flow: row nowrap;
|
||||
box-sizing: border-box;
|
||||
padding: var(--spacing-xxs) 0;
|
||||
display: flex;
|
||||
position: relative;
|
||||
border-top: 1px solid var(--gray-light);
|
||||
}
|
||||
|
||||
.chat-footer :global(.input-container) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-footer :global(.input-container input) {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.chat-footer :global(.send-button) {
|
||||
padding: 0 var(--spacing-xs);
|
||||
}
|
||||
`}</style>
|
||||
</Aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatAside;
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { ChatAside as default } from './ChatAside';
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import React, { 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 { useCallState } from '@dailyjs/shared/contexts/CallProvider';
|
||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||
|
||||
export const LIVE_STREAMING_MODAL = 'live-streaming';
|
||||
|
||||
export const LiveStreamingModal = () => {
|
||||
const { callObject } = useCallState();
|
||||
const { currentModals, closeModal } = useUIState();
|
||||
const [rtmpUrl, setRtmpUrl] = useState('');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Live stream"
|
||||
isOpen={currentModals[LIVE_STREAMING_MODAL]}
|
||||
onClose={() => closeModal(LIVE_STREAMING_MODAL)}
|
||||
>
|
||||
<Field label="Enter room to join">
|
||||
<TextInput
|
||||
type="text"
|
||||
placeholder="RTMP URL"
|
||||
required
|
||||
onChange={(e) => setRtmpUrl(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Button
|
||||
disabled={!rtmpUrl}
|
||||
onClick={() => callObject.startLiveStreaming({ rtmpUrl })}
|
||||
>
|
||||
Start live streaming
|
||||
</Button>
|
||||
<Button onClick={() => callObject.stopLiveStreaming()}>
|
||||
Stop live streaming
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default LiveStreamingModal;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export { LiveStreamingModal as default } from './LiveStreamingModal';
|
||||
export { LiveStreamingModal } from './LiveStreamingModal';
|
||||
export { LIVE_STREAMING_MODAL } from './LiveStreamingModal';
|
||||
|
|
@ -2,24 +2,20 @@ import React from 'react';
|
|||
|
||||
import { TrayButton } from '@dailyjs/shared/components/Tray';
|
||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||
import { ReactComponent as IconChat } from '@dailyjs/shared/icons/chat-md.svg';
|
||||
import { useChat } from '../../contexts/ChatProvider';
|
||||
import { CHAT_ASIDE } from '../ChatAside/ChatAside';
|
||||
import { ReactComponent as IconStream } from '@dailyjs/shared/icons/streaming-md.svg';
|
||||
|
||||
import { LIVE_STREAMING_MODAL } from '../LiveStreamingModal';
|
||||
|
||||
export const Tray = () => {
|
||||
const { toggleAside } = useUIState();
|
||||
const { hasNewMessages } = useChat();
|
||||
const { openModal } = useUIState();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TrayButton
|
||||
label="Chat"
|
||||
bubble={hasNewMessages}
|
||||
onClick={() => {
|
||||
toggleAside(CHAT_ASIDE);
|
||||
}}
|
||||
label="Stream"
|
||||
onClick={() => openModal(LIVE_STREAMING_MODAL)}
|
||||
>
|
||||
<IconChat />
|
||||
<IconStream />
|
||||
</TrayButton>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import App from '@dailyjs/basic-call/pages/_app';
|
||||
import AppWithChat from '../components/App';
|
||||
|
||||
import ChatAside from '../components/ChatAside';
|
||||
import { LiveStreamingModal } from '../components/LiveStreamingModal';
|
||||
import Tray from '../components/Tray';
|
||||
|
||||
App.asides = [ChatAside];
|
||||
App.modals = [LiveStreamingModal];
|
||||
App.customAppComponent = <AppWithChat />;
|
||||
App.customTrayComponent = <Tray />;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
|||
import { Button } from '../Button';
|
||||
import { DeviceSelect } from '../DeviceSelect';
|
||||
|
||||
export const DEVICE_MODAL = 'device';
|
||||
|
||||
export const DeviceSelectModal = () => {
|
||||
const { showDeviceModal, setShowDeviceModal } = useUIState();
|
||||
const { currentModals, closeModal } = useUIState();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Select your device"
|
||||
isOpen={showDeviceModal}
|
||||
onClose={() => setShowDeviceModal(false)}
|
||||
isOpen={currentModals[DEVICE_MODAL]}
|
||||
onClose={() => closeModal(DEVICE_MODAL)}
|
||||
actions={[
|
||||
<Button fullWidth variant="outline">
|
||||
Cancel
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { DeviceSelectModal as default } from './DeviceSelectModal';
|
||||
export { DeviceSelectModal } from './DeviceSelectModal';
|
||||
export { DEVICE_MODAL } from './DeviceSelectModal';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import Button from '@dailyjs/shared/components/Button';
|
||||
import { DEVICE_MODAL } from '@dailyjs/shared/components/DeviceSelectModal/DeviceSelectModal';
|
||||
import { TextInput } from '@dailyjs/shared/components/Input';
|
||||
import Loader from '@dailyjs/shared/components/Loader';
|
||||
import { MuteButton } from '@dailyjs/shared/components/MuteButtons';
|
||||
|
|
@ -33,7 +34,7 @@ export const HairCheck = () => {
|
|||
const { localParticipant } = useParticipants();
|
||||
const { deviceState, camError, micError, isCamMuted, isMicMuted } =
|
||||
useMediaDevices();
|
||||
const { showDeviceModal, setShowDeviceModal } = useUIState();
|
||||
const { openModal } = useUIState();
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
const [joining, setJoining] = useState(false);
|
||||
const [denied, setDenied] = useState();
|
||||
|
|
@ -143,7 +144,7 @@ export const HairCheck = () => {
|
|||
className="device-button"
|
||||
size="medium-square"
|
||||
variant="blur"
|
||||
onClick={() => setShowDeviceModal(!showDeviceModal)}
|
||||
onClick={() => openModal(DEVICE_MODAL)}
|
||||
>
|
||||
<IconSettings />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { PEOPLE_ASIDE } from '@dailyjs/shared/components/Aside/PeopleAside';
|
||||
import { DEVICE_MODAL } from '@dailyjs/shared/components/DeviceSelectModal';
|
||||
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
|
||||
import { useMediaDevices } from '@dailyjs/shared/contexts/MediaDeviceProvider';
|
||||
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
|
||||
|
|
@ -14,7 +15,7 @@ import { Tray, TrayButton } from './Tray';
|
|||
|
||||
export const BasicTray = () => {
|
||||
const { callObject, leave } = useCallState();
|
||||
const { customTrayComponent, setShowDeviceModal, toggleAside } = useUIState();
|
||||
const { customTrayComponent, openModal, toggleAside } = useUIState();
|
||||
const { isCamMuted, isMicMuted } = useMediaDevices();
|
||||
|
||||
const toggleCamera = (newState) => {
|
||||
|
|
@ -43,7 +44,7 @@ export const BasicTray = () => {
|
|||
>
|
||||
{isMicMuted ? <IconMicOff /> : <IconMicOn />}
|
||||
</TrayButton>
|
||||
<TrayButton label="Settings" onClick={() => setShowDeviceModal(true)}>
|
||||
<TrayButton label="Settings" onClick={() => openModal(DEVICE_MODAL)}>
|
||||
<IconSettings />
|
||||
</TrayButton>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,33 @@
|
|||
import React, { useCallback, createContext, useContext, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDeepCompareMemo } from 'use-deep-compare';
|
||||
|
||||
export const UIStateContext = createContext();
|
||||
|
||||
export const UIStateProvider = ({ asides, customTrayComponent, children }) => {
|
||||
const [showDeviceModal, setShowDeviceModal] = useState(false);
|
||||
export const UIStateProvider = ({
|
||||
asides,
|
||||
modals,
|
||||
customTrayComponent,
|
||||
children,
|
||||
}) => {
|
||||
const [showAside, setShowAside] = useState();
|
||||
const [activeModals, setActiveModals] = useState({});
|
||||
|
||||
const openModal = useCallback((modalName) => {
|
||||
setActiveModals((prevState) => ({
|
||||
...prevState,
|
||||
[modalName]: true,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const closeModal = useCallback((modalName) => {
|
||||
setActiveModals((prevState) => ({
|
||||
...prevState,
|
||||
[modalName]: false,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const currentModals = useDeepCompareMemo(() => activeModals, [activeModals]);
|
||||
|
||||
const toggleAside = useCallback((newAside) => {
|
||||
setShowAside((p) => (p === newAside ? null : newAside));
|
||||
|
|
@ -15,9 +37,11 @@ export const UIStateProvider = ({ asides, customTrayComponent, children }) => {
|
|||
<UIStateContext.Provider
|
||||
value={{
|
||||
asides,
|
||||
modals,
|
||||
customTrayComponent,
|
||||
showDeviceModal,
|
||||
setShowDeviceModal,
|
||||
openModal,
|
||||
closeModal,
|
||||
currentModals,
|
||||
toggleAside,
|
||||
showAside,
|
||||
setShowAside,
|
||||
|
|
@ -31,6 +55,7 @@ export const UIStateProvider = ({ asides, customTrayComponent, children }) => {
|
|||
UIStateProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
asides: PropTypes.arrayOf(PropTypes.func),
|
||||
modals: PropTypes.arrayOf(PropTypes.func),
|
||||
customTrayComponent: PropTypes.node,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 952 B After Width: | Height: | Size: 952 B |
Loading…
Reference in New Issue