daily-co / daily-react Goto Github PK
View Code? Open in Web Editor NEWReact hooks and components to get started with Daily
Home Page: https://docs.daily.co/reference/daily-react
License: BSD 2-Clause "Simplified" License
React hooks and components to get started with Daily
Home Page: https://docs.daily.co/reference/daily-react
License: BSD 2-Clause "Simplified" License
Using daily-react inside an application that uses Recoil will lose its ability to access its state values of the nearest ancestor RecoilRoot.
We have application set up with Recoil as its dependency. Since daily-react uses Recoil internally, it is nearly impossible share states between nested RecoilRoot.
We have tried moving DailyProvider
further up in the application structure. We don't think this approach is scalable, since daily-react Recoil does not accept any properties for its internal RecoilRoot implementation.
It would be useful to wrap DailyAudio
, DailyAudioTrack
, and DailyVideo
in React.forwardRef().
I’d love to use <DailyAudioTrack />
, but it’s useful to have a reference to the DOM node for HTMLMediaElements. For instance, it’s not possible to set volume
under the current implementation, since you’d need access to audioEl.current.volume
.
I think it’s most useful to simply accept a ref and join it to the internal ref with something like useMergeRefs
1. That way, the user can use the ref however they need.
But if that’s not to your taste, you could also define specific methods with useImperativeHandle
.
It could also be nice to accept declarative props (like volume
) and handle all of the effects internally, but it’s less flexible and more work on your end.
I also think it would be useful to accept arbitrary props
and spread them onto the returned element, like so:
export default React.forwardRefs(function DailyAudioTrack ({
onPlayFailed,
sessionId,
type = 'audio',
...rest,
}, externalRef) {
const audioEl = useRef(null);
// ...
const ref = useMergedRef(audioEl, externalRef);
return (
<audio
autoPlay
data-session-id={sessionId}
data-audio-type={type}
playsInline
ref={ref}
{ ...rest }
/>
);
});
That way, you can pass in arbitrary properties like muted
.
Reference implementation:
// Takes multiple refs and merges them into a single ref so you can attach
// multiple refs to the same element.
export function useMergedRef(refs) {
const filteredRefs = refs.filter(Boolean);
const mergedRef = useCallback(el => {
filteredRefs.forEach(ref => {
if (typeof ref === 'function') {
ref(el);
} else {
ref.current = el;
}
});
}, filteredRefs);
return mergedRef;
}
↩
Please add React 18 support
Some other packages requires React 18, so its not possible to it with daily-react
Hi again! Since my last feature request sparked some good discussion, I’m adding another idea.
Add an idType
param to useParticipantIds to allow returning user IDs, instead of the default behaviour which returns session IDs.
useParticipantIds({ idType: 'user' });
My application stores user data in a map keyed by participant user IDs. It would be really helpful to get a list of user IDs directly from this hook, rather than needing to store them in a separate callState
object as shown here.
This could go into its own hook called useUserIds
or useParticipantUserIds
, but that feels a bit heavy vs. the above proposal.
I can make a quick draft PR if this sounds useful.
Unable to initiate a call and pass it to DailyProvider in a Remix application. Since the error starts in DailyProvider I'm gonna post the issue here for now. I've tried running the same code in Vite and NextJS without any issues. When I try to use it in Remix I get errors:
react-dom.development.js:22839 Uncaught TypeError: Cannot read properties of undefined (reading 'call')
at i2.value (daily-iframe-esm.js:1:121213)
at DailyProvider.tsx:135:22
at useDailyEvent.ts:47:5
at commitHookEffectListMount (react-dom.development.js:23150:26)
at commitPassiveMountOnFiber (react-dom.development.js:24926:13)
at commitPassiveMountEffects_complete (react-dom.development.js:24891:9)
at commitPassiveMountEffects_begin (react-dom.development.js:24878:7)
at commitPassiveMountEffects (react-dom.development.js:24866:3)
at flushPassiveEffectsImpl (react-dom.development.js:27039:3)
"@daily-co/daily-js": "^0.40.0",
"@daily-co/daily-react": "^0.7.2",
Looking through the stack trace it appears to have something to do with using hydrateRoot in React 18. Very far from obvious what the specific issue is beyond that to me at least.
Here's a reproduction: https://stackblitz.com/edit/remix-daily-co?file=app/routes/index.tsx
And here's the same code in a regular react template working: https://stackblitz.com/edit/react-ts-2rur5u?file=App.tsx
DailyProvider
should cleanup its own internal callObject
state when going from passed callObject
to passed null
.
I want my application behind DailyProvider
to be able to know that the callObject
has been destroyed. Currently when passing a callObject
everything is fine, but when that callObject
is set to null
afterwards, DailyProvider
still hold the previous reference.
1 - Pass a callObject
to DailyProvider
from a setState
hook.
2 - Set that hook to null
3 - The previous callObject
still remains inside of DailyProvider
<3
I love your product.
The Docs Seems to be out of date.
https://docs.daily.co/reference/daily-react/daily-provider
It says to import a useCallObject, but that is not an exported object from @daily-co/daily-react
version: "@daily-co/daily-react": "^0.16.0",
Per this request in the daily-python sdk, I'd love to see these parameters available for daily-react for the same reasons. Thanks!
useDevices().camState
and useDevices().micState
should be set to "pending"
or "unknown"
until device access is requested and granted.
When a call is created with startAudioOff
and startVideoOff
set to true
, then joined, useDevices().camState
and useDevices().micState
return "granted"
even before device access is requested with callObject.updateParticipant('local', { setVideo: true, setAudio: true })
.
Initialize the call with startAudioOff
and startVideoOff
set to true
:
const callObject = DailyIframe.createCallObject({
startAudioOff: true,
startVideoOff: true,
// ...
});
Join the call:
await callObject.join({ url: dailyRoomUrl, token: dailyMeetingToken, userName: name });
Later (but before calling callObject.updateParticipant
), run the following:
const { camState, micState } = useDevices();
console.log(camState, micState); // > granted granted
I’m currently using the following as a workaround:
const { cameras, microphones } = useDevices();
const isMediaAccessGranted =
cameras.some(c => c.selected && c.state === 'granted') &&
microphones.some(m => m.selected && m.state === 'granted');
useDailyError is documented here, so i should be able to import it with
import { useDailyError } from '@daily-co/daily-react';
No matching export in "node_modules/@daily-co/daily-react/dist/daily-react.esm.js" for import "useDailyError"
useDailyError isn't exported from https://github.com/daily-co/daily-react/blob/main/src/index.ts
In my custom react app we use createCallObject
and manually manage the callObject lifecycle instead of the newly introduced useCallObject
hook.
I can confirm that at 0.16.0
our app works, but at 0.17.0
the callObject gets destroyed at some point after startCamera
.
I'd be happy to try and isolate some code and create a repeatable example if this is not enough info.
Our code that sets up the callObject looks like this:
import DailyIframe, {
DailyEventObjectParticipant,
DailyEventObjectParticipantLeft,
DailyEventObjectActiveSpeakerChange,
} from "@daily-co/daily-js";
import getToken from "../../lib/requests/getToken";
export const setupCallObject = async (
hashedInvitationID: string,
roomName: string,
clientID: string,
userName: string,
onParticipantJoined: (event?: DailyEventObjectParticipant) => void,
onParticipantLeft: (event?: DailyEventObjectParticipantLeft) => void,
onActiveSpeakerChange: (event?: DailyEventObjectActiveSpeakerChange) => void
) => {
console.log("[session-ui]: Setting up call object");
const dailyTokenResponse = await getToken(hashedInvitationID, clientID);
const url = `https://xxxxx.daily.co/${roomName}`;
let callObject = DailyIframe.getCallInstance();
if (callObject) {
console.log("[session-ui]: Daily iframe already exists, leaving meeting.");
await callObject.leave();
}
if (!callObject) {
callObject = DailyIframe.createCallObject({ strictMode: true });
}
callObject.on("participant-joined", onParticipantJoined);
callObject.on("participant-left", onParticipantLeft);
callObject.on("active-speaker-change", onActiveSpeakerChange);
callObject.on("call-instance-destroyed", (e) => {
console.error("callInstanceDestroyed Event", e);
});
callObject.on("error", (e) => {
console.error("callObjectError Event", e);
});
await callObject.startCamera({
token: dailyTokenResponse.token,
userName: userName.toString() || "",
url: url,
dailyConfig: {
v2CamAndMic: true,
},
});
await callObject.startLocalAudioLevelObserver(100);
await callObject.startRemoteParticipantsAudioLevelObserver(100);
return callObject;
};
Another part of our app then calls callObject.join
triggered by the button in our prejoin/haircheck component.
We render the app with the DailyProvider, and initially callObject can be undefined. I thought maybe there is something going on where DailyProvider is doing the destroying, so I also tried not having DailyProvider in the component tree until the callObject is ready to be used, but that didn't seem to do anything.
{callObject && (
<DailyProvider callObject={callObject}>
<ThemeProvider theme={getThemeByName("default")}>{children}</ThemeProvider>
</DailyProvider>
)}
{!callObject && <ThemeProvider theme={getThemeByName("default")}>{children}</ThemeProvider>}
Please tell me if I'm doing something wrong. Here's my simplified code:
function Call({ roomUrl, meetingToken }: { roomUrl: string; meetingToken: string | undefined }) {
const callRef = useRef<HTMLDivElement>(null);
const callFrame = useCallFrame({
parentEl: callRef.current,
options: CALL_OPTIONS,
shouldCreateInstance: useCallback(() => Boolean(callRef.current), []),
});
useEffect(() => {
if (!callFrame) {
return;
}
void callFrame.join({ url: roomUrl, token: meetingToken });
callFrame.on("left-meeting", () => {
void callFrame.destroy();
});
}, [callFrame, meetingToken, roomUrl]);
return (
<DailyProvider callObject={callFrame}>
<div ref={callRef} className="h-full w-full" />
</DailyProvider>
);
}
Deleting shouldCreateInstance
or the <div>
which callRef
is passed into doesn't make a difference; they're basically ignored.
Package versions:
"@daily-co/daily-js": "0.57.4",
"@daily-co/daily-react": "^0.17.1"
Currently, DailyProvider
takes callObject: DailyCall
as a prop. Both useCallFrame
and useCallObject
return DailyCall | null
. I'm not sure if this is intentional but I expected it to handle the null
case on its own.
It's not a big deal since a simple if (!callObject) return;
can fix this but I think this can be a quality of life improvement.
Edit: The documentation doesn't know about this issue.
I'm experiencing an issue similar to what I saw here:
#9
This time the issue is with the isRecording
value returned by the useRecording
hook. I expect the isRecording
value to accurately reflect whether or not there is a recording in progress for the current room.
When a user joins a room and starts a recording, the isRecording
value is true
. If that user then stops the recording and leaves the room, the isRecording
value remains true
. If the user rejoins the room, the isRecording
value is still true
even though there is no recording in progress.
During the above process, if there is another user in the room the isRecording
value changes from true
to false
as expected.
I have a modified version of our last code sandbox here:
https://codesandbox.io/s/angry-driscoll-h0ouu6?file=/src/SessionIDDisplay.js
To reproduce:
true
for both userstrue
in window A and false
in window Btrue
in window A and false
in window BDaily works in development mode: The DailyProvider
manages creating and destroying the callObject correctly.
With react 18 running in development mode, it throws the error Duplicate DailyIframe instances are not allowed
, essentially breaking the app in dev mode.
This is due to react 18 rendering the app twice in development mode.
Maybe the call object is not cleaned up correctly on unmount?
npx create-react-app my-app
import { DailyProvider } from '@daily-co/daily-react';
import './App.css';
function App() {
return <DailyProvider>Hello World</DailyProvider>;
}
export default App;
npm start
Duplicate DailyIframe instances are not allowed
"@daily-co/daily-js": "0.45.0",
"@daily-co/daily-react": "^0.8.0",
Upon receiving a screen share track, it's video should be visible.
A subscribed screen share track is received, but never leaves "loading" state.
This issue is not present in v0.10.0. Perhaps a regression with Recoil changes around participants?
When utilizing the DailyProvider
component in a Next.js typescript app, I should be able to pass children
as props to the component, as outlined in the docs.
When utilizing the DailyProvider
component and passing children
, I am receiving this error on DailyProvider
:
Type '{ children: never[]; url: string; }' is not assignable to type 'IntrinsicAttributes & Props'.
Property 'children' does not exist on type 'IntrinsicAttributes & Props'.
@daily-co/daily-react-hooks
to a Next.js typescript project utilizing Next ^12.1.5
.DailyProvider
to a page component.url
and children
to the provider.After calling daily.preAuth
I see that the useLocalSessionId
hook correctly returns the session ID of the local user. I can then call daily.join()
to the join the room with that session ID, and then call daily.leave()
to leave the room with that session ID.
At this point the useLocalSessionId
hook returns undefined
, and I expect that calling preAuth
again will issue a new local session ID that can be accessed by the useLocalSessionId
hook.
In the above scenario, if I call preAuth
again after leaving the room I see a participant-updated
event fire with a new session ID for the local user but the return value of useLocalSessionId
as well as useLocalParticipant
remains undefined
. I would expect those values to reflect the new local session ID, unless I am misunderstanding how this works?
daily.preAuth()
passing in url and token as optionsparticipant-updated
event fires with new session ID, which is correctly returned by useLocalSessionId
and useLocalParticipant
hooksdaily.join()
to join roomdaily.leave()
to leave roomdaily.preAuth
again, passing in same url and token optionsparticipant-updated
event fires with new session ID, however useLocalSessionId
and useLocalParticipant
hooks returns undefined
Room I am testing in:
https://tastemade-staging.daily.co/zPmtkqdArvkU4a53DdzC
In 671300e, a new "idle" state was added to represent devices that had not been asked for access permission yet. As soon as permissions are requested, the state should become "pending", then either "granted" or some other error state.
When a camera is muted through Daily, it returns to the "idle" state.
Add this snippet somewhere in your demo code:
const { camState, micState } = useDevices();
console.log(`Camera state is "${camState}" \tMicrophone state is "${micState}"`);
Once you’ve granted camera and microphone access, you should see the following log:
Camera state is "granted" Microphone state is "granted"
Mute your mic and you’ll see the same thing:
Camera state is "granted" Microphone state is "granted"
Mute your camera and you’ll see:
Camera state is "idle" Microphone state is "granted"
Mute state is already tracked on dailyVideo.track.muted
. Keeping mute state decoupled from media state is important.
Normally and as shown in the documentation, the initial value of callRef
has to be null
. So the type of parentEl
should be like this: parentEl?: HTMLElement | null
.
Using useCallFrame
, I get this error in the console:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://xxx.daily.co') does not match the recipient window's origin ('http://localhost:xxx').
Using an error boundary, the call frame doesn't render. But without it, everything works just fine. I'm not sure what the issue can be.
I'm wrapping my call with <DailyProvider>
and passing the call frame object as callObject
.
A user of mine started a recording but forgot to stop it. It's recording id cdb45f18-63b5-42f2-89e8-0fe111451fa1 - is there any way to retrieve this?
I'd like to be able to pipe a pre-recorded audio track from the server to everyone in the call simultaneously. Ideally, I'd also be able to have the host pause and play it.
Is this possible using the Daily API, either in REST or JS? I think the DailyAudioTrack can do this, but I can't quite tell.
How do I record just audio and not the video too? The docs (https://docs.daily.co/reference/daily-js/instance-methods/start-recording
) show the following command that records video as well. I do not want to turn off the displayed video, just not record it.
call.startRecording({
width: 1280,
height: 720,
fps: 25,
backgroundColor: '#FF1F2D3D',
layout: {
preset: 'default',
max_cam_streams: 5,
},
});
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.