diff --git a/custom/fitness-demo/components/App/App.js b/custom/fitness-demo/components/App/App.js
index a86b453..4c3170d 100644
--- a/custom/fitness-demo/components/App/App.js
+++ b/custom/fitness-demo/components/App/App.js
@@ -1,5 +1,5 @@
import React, { useMemo } from 'react';
-import { LiveStreamingProvider } from '@custom/live-streaming/contexts/LiveStreamingProvider';
+import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider';
import { RecordingProvider } from '@custom/recording/contexts/RecordingProvider';
import ExpiryTimer from '@custom/shared/components/ExpiryTimer';
import { useCallState } from '@custom/shared/contexts/CallProvider';
diff --git a/custom/fitness-demo/components/Tray/Stream.js b/custom/fitness-demo/components/Tray/Stream.js
index a03885e..379a49e 100644
--- a/custom/fitness-demo/components/Tray/Stream.js
+++ b/custom/fitness-demo/components/Tray/Stream.js
@@ -1,8 +1,8 @@
import React from 'react';
import { LIVE_STREAMING_MODAL } from '@custom/live-streaming/components/LiveStreamingModal';
-import { useLiveStreaming } from '@custom/live-streaming/contexts/LiveStreamingProvider';
import { TrayButton } from '@custom/shared/components/Tray';
+import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
diff --git a/custom/live-streaming/components/App.js b/custom/live-streaming/components/App.js
index cd47217..888fa3e 100644
--- a/custom/live-streaming/components/App.js
+++ b/custom/live-streaming/components/App.js
@@ -1,7 +1,7 @@
import React from 'react';
import App from '@custom/basic-call/components/App';
-import { LiveStreamingProvider } from '../contexts/LiveStreamingProvider';
+import { LiveStreamingProvider } from '@custom/shared/contexts/LiveStreamingProvider';
// Extend our basic call app component with the live streaming context
export const AppWithLiveStreaming = () => (
diff --git a/custom/live-streaming/components/LiveStreamingModal.js b/custom/live-streaming/components/LiveStreamingModal.js
index 97896c8..f4cdee1 100644
--- a/custom/live-streaming/components/LiveStreamingModal.js
+++ b/custom/live-streaming/components/LiveStreamingModal.js
@@ -5,10 +5,9 @@ import Field from '@custom/shared/components/Field';
import { TextInput, SelectInput } from '@custom/shared/components/Input';
import Modal from '@custom/shared/components/Modal';
import Well from '@custom/shared/components/Well';
-import { useCallState } from '@custom/shared/contexts/CallProvider';
+import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
-import { useLiveStreaming } from '../contexts/LiveStreamingProvider';
export const LIVE_STREAMING_MODAL = 'live-streaming';
@@ -19,15 +18,19 @@ const LAYOUTS = [
];
export const LiveStreamingModal = () => {
- const { callObject } = useCallState();
- const { allParticipants } = useParticipants();
+ const { participants } = useParticipants();
const { currentModals, closeModal } = useUIState();
- const { isStreaming, streamError } = useLiveStreaming();
+ const {
+ isStreaming,
+ streamError,
+ startLiveStreaming,
+ stopLiveStreaming,
+ } = useLiveStreaming();
const [pending, setPending] = useState(false);
const [rtmpUrl, setRtmpUrl] = useState('');
- const [layout, setLayout] = useState(0);
+ const [layoutType, setLayoutType] = useState('default');
const [maxCams, setMaxCams] = useState(9);
- const [participant, setParticipant] = useState(0);
+ const [participantId, setParticipantId] = useState(0);
useEffect(() => {
// Reset pending state whenever stream state changes
@@ -35,18 +38,23 @@ export const LiveStreamingModal = () => {
}, [isStreaming]);
function startLiveStream() {
- setPending(true);
+ const config = {
+ rtmpUrl,
+ layout: {
+ preset: layoutType,
+ },
+ };
- const opts =
- layout === 'single-participant'
- ? { session_id: participant.id }
- : { max_cam_streams: maxCams };
- callObject.startLiveStreaming({ rtmpUrl, preset: layout, ...opts });
+ if (layoutType === 'single-participant')
+ config.layout.session_id = participantId;
+ else if (layoutType === 'default') config.layout.max_cam_streams = maxCams;
+
+ startLiveStreaming(config);
}
- function stopLiveStreaming() {
+ function stopLiveStream() {
setPending(true);
- callObject.stopLiveStreaming();
+ stopLiveStreaming();
}
return (
@@ -62,7 +70,7 @@ export const LiveStreamingModal = () => {
@@ -70,7 +78,7 @@ export const LiveStreamingModal = () => {
@@ -85,19 +93,18 @@ export const LiveStreamingModal = () => {
setLayout(Number(e.target.value))}
- value={layout}
+ onChange={(e) => setLayoutType(e.target.value)}
+ value={layoutType}
>
- {LAYOUTS.map((l, i) => (
-
))}
- {layout !==
- LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
+ {layoutType === 'default' && (
setMaxCams(Number(e.target.value))}
@@ -116,15 +123,17 @@ export const LiveStreamingModal = () => {
)}
- {layout ===
- LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
+ {layoutType === 'single-participant' && (
setParticipant(e.target.value)}
- value={participant}
+ onChange={(e) => setParticipantId(e.target.value)}
+ value={participantId}
>
- {allParticipants.map((p) => (
-
+ {participants.map((p) => (
+
))}
diff --git a/custom/live-streaming/components/Tray.js b/custom/live-streaming/components/Tray.js
index baee790..2943187 100644
--- a/custom/live-streaming/components/Tray.js
+++ b/custom/live-streaming/components/Tray.js
@@ -1,10 +1,10 @@
import React from 'react';
import { TrayButton } from '@custom/shared/components/Tray';
+import { useLiveStreaming } from '@custom/shared/contexts/LiveStreamingProvider';
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
import { ReactComponent as IconStream } from '@custom/shared/icons/streaming-md.svg';
-import { useLiveStreaming } from '../contexts/LiveStreamingProvider';
import { LIVE_STREAMING_MODAL } from './LiveStreamingModal';
export const Tray = () => {
diff --git a/custom/live-streaming/contexts/LiveStreamingProvider.js b/custom/live-streaming/contexts/LiveStreamingProvider.js
deleted file mode 100644
index 6e940c8..0000000
--- a/custom/live-streaming/contexts/LiveStreamingProvider.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React, {
- useState,
- createContext,
- useContext,
- useEffect,
- useCallback,
-} from 'react';
-import { useCallState } from '@custom/shared/contexts/CallProvider';
-import { useUIState } from '@custom/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);
- setStreamError(null);
- 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/custom/shared/contexts/LiveStreamingProvider.js b/custom/shared/contexts/LiveStreamingProvider.js
new file mode 100644
index 0000000..8e877a9
--- /dev/null
+++ b/custom/shared/contexts/LiveStreamingProvider.js
@@ -0,0 +1,65 @@
+import React, {
+ createContext,
+ useContext,
+ useCallback,
+} from 'react';
+import { useLiveStreaming as useDailyLiveStreaming } from '@daily-co/daily-react-hooks';
+import PropTypes from 'prop-types';
+import { useUIState } from './UIStateProvider';
+
+export const LiveStreamingContext = createContext();
+
+export const LiveStreamingProvider = ({ children }) => {
+ const { setCustomCapsule } = useUIState();
+
+ const handleStreamStarted = useCallback(() => {
+ console.log('📺 Live stream started');
+ setCustomCapsule({ variant: 'recording', label: 'Live streaming' });
+ }, [setCustomCapsule]);
+
+ const handleStreamStopped = useCallback(() => {
+ console.log('📺 Live stream stopped');
+ setCustomCapsule(null);
+ }, [setCustomCapsule]);
+
+ const handleStreamError = useCallback(
+ (e) => {
+ console.log('📺 Live stream error ' + e.errorMsg);
+ setCustomCapsule(null);
+ },
+ [setCustomCapsule]
+ );
+
+ const {
+ isLiveStreaming,
+ layout,
+ errorMsg,
+ startLiveStreaming,
+ updateLiveStreaming,
+ stopLiveStreaming
+ } = useDailyLiveStreaming({
+ onLiveStreamingStarted: handleStreamStarted,
+ onLiveStreamingStopped: handleStreamStopped,
+ onLiveStreamingError: handleStreamError,
+ });
+
+ return (
+
+ {children}
+
+ );
+};
+
+LiveStreamingProvider.propTypes = {
+ children: PropTypes.node,
+};
+
+export const useLiveStreaming = () => useContext(LiveStreamingContext);
diff --git a/custom/shared/contexts/callState.js b/custom/shared/contexts/callState.js
deleted file mode 100644
index 2ff9112..0000000
--- a/custom/shared/contexts/callState.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Call State
- * ---
- * Duck file that keeps state of call participants
- */
-
-export const ACTION_PARTICIPANT_JOINED = 'ACTION_PARTICIPANT_JOINED';
-export const ACTION_PARTICIPANT_UPDATED = 'ACTION_PARTICIPANT_UPDATED';
-export const ACTION_PARTICIPANTED_LEFT = 'ACTION_PARTICIPANT_LEFT';
-
-export const initialCallState = {
- audioTracks: {},
- videoTracks: {},
- callItems: {},
- fatalError: false,
-};
-
-export function isLocal(id) {
- return id === 'local';
-}
-
-function getCallItems(newParticipants, prevCallItems) {
- const callItems = {};
- const entries = Object.entries(newParticipants);
- entries.forEach(([id, participant]) => {
- const prevState = prevCallItems[id];
- const hasLoaded = !prevState?.isLoading;
- const missingTracks = !(participant.audioTrack || participant.videoTrack);
- const joined = prevState?.joined || new Date().getTime() / 1000;
- const local = isLocal(id);
-
- callItems[id] = {
- id,
- name: participant.user_name || 'Guest',
- audioTrack: participant.audioTrack,
- videoTrack: participant.videoTrack,
- hasNameSet: !!participant.user_name,
- isActiveSpeaker: !!prevState?.isActiveSpeaker,
- isCamMuted: !participant.video,
- isLoading: !hasLoaded && missingTracks,
- isLocal: local,
- isMicMuted: !participant.audio,
- isOwner: !!participant.owner,
- isRecording: !!participant.record,
- lastActiveDate: prevState?.lastActiveDate ?? null,
- mutedByHost: participant?.tracks?.audio?.off?.byRemoteRequest,
- isScreenshare: false,
- joined,
- };
-
- if (participant.screenVideoTrack || participant.screenAudioTrack) {
- callItems[`${id}-screen`] = {
- audioTrack: participant.tracks.screenAudio.persistentTrack,
- hasNameSet: null,
- id: `${id}-screen`,
- isLoading: false,
- isLocal: local,
- isScreenshare: true,
- lastActiveDate: prevState?.lastActiveDate ?? null,
- name: participant.user_name,
- videoTrack: participant.screenVideoTrack,
- };
- }
- });
- return callItems;
-}
-
-export function isScreenShare(id) {
- return id.endsWith('-screen');
-}
-
-export function containsScreenShare(participants) {
- return Object.keys(participants).some((id) => isScreenShare(id));
-}
-
-export function callReducer(state, action) {
- switch (action.type) {
- case ACTION_PARTICIPANT_UPDATED:
- return {
- ...state,
- callItems: getCallItems(action.participants, state.callItems),
- };
- default:
- throw new Error();
- }
-}