added basic live streaming

This commit is contained in:
Jon 2021-06-24 16:19:19 +01:00
parent c67b79af28
commit fb3f94f36b
15 changed files with 118 additions and 163 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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,
};

View File

@ -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;

View File

@ -1 +0,0 @@
export { ChatAside as default } from './ChatAside';

View File

@ -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;

View File

@ -0,0 +1,3 @@
export { LiveStreamingModal as default } from './LiveStreamingModal';
export { LiveStreamingModal } from './LiveStreamingModal';
export { LIVE_STREAMING_MODAL } from './LiveStreamingModal';

View File

@ -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>
</>
);

View File

@ -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 />;

View File

@ -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

View File

@ -1,2 +1,3 @@
export { DeviceSelectModal as default } from './DeviceSelectModal';
export { DeviceSelectModal } from './DeviceSelectModal';
export { DEVICE_MODAL } from './DeviceSelectModal';

View File

@ -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>

View File

@ -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>

View File

@ -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,
};

View File

Before

Width:  |  Height:  |  Size: 952 B

After

Width:  |  Height:  |  Size: 952 B