steep-balloon-41261
09/13/2023, 5:42 PMacoustic-engineer-41666
09/13/2023, 5:46 PMacoustic-engineer-41666
09/13/2023, 5:46 PMfancy-author-2900
09/13/2023, 5:48 PMfancy-author-2900
09/13/2023, 5:49 PMfancy-author-2900
09/13/2023, 5:49 PMWhen building your custom UI, it's helpful to use track renderers that are provided in this library. AudioRenderer and VideoRenderer would render an audio and video track, respectively.fancy-author-2900
09/13/2023, 5:49 PMvideorenderer and did not find it.acoustic-engineer-41666
09/13/2023, 5:50 PMacoustic-engineer-41666
09/13/2023, 5:55 PM<ParticipantTile> that renders the audio and video for your ingest participantfancy-author-2900
09/13/2023, 5:56 PMfancy-author-2900
09/13/2023, 5:56 PMfancy-author-2900
09/13/2023, 5:57 PMTrack.Source.Camerafancy-author-2900
09/13/2023, 5:57 PMfancy-author-2900
09/13/2023, 5:57 PMfancy-author-2900
09/13/2023, 5:58 PMconst tracks = useTracks(
[
{ source: Track.Source.Camera, withPlaceholder: true },
{ source: Track.Source.ScreenShare, withPlaceholder: false },
],
{ onlySubscribed: false },
);fancy-author-2900
09/13/2023, 5:58 PMuseTracks , or there might be some default behavior with useTracks that renders the stream automatically?acoustic-engineer-41666
09/13/2023, 5:59 PMacoustic-engineer-41666
09/13/2023, 5:59 PMMyVideoConference element is only for video; there’s also a RoomAudioRenderer that just plays all audio tracksacoustic-engineer-41666
09/13/2023, 6:00 PMacoustic-engineer-41666
09/13/2023, 6:01 PMfancy-author-2900
09/13/2023, 6:02 PMParticipantTilefancy-author-2900
09/13/2023, 6:02 PMfancy-author-2900
09/13/2023, 6:02 PMtrackReference?fancy-author-2900
09/13/2023, 6:02 PMfancy-author-2900
09/13/2023, 6:03 PMfancy-author-2900
09/13/2023, 6:04 PM<ParticipantTile /> ... if I use it without arguments inside of the <LiveKitRoom> I get this error: Uncaught Error: No participant provided, make sure you are inside a participant context or pass the participant explicitlyacoustic-engineer-41666
09/13/2023, 6:05 PMuseTrack you mentioned above and then pass that track ref in to the ParticipantTile as trackRef={…}acoustic-engineer-41666
09/13/2023, 6:06 PMacoustic-engineer-41666
09/13/2023, 6:06 PMfancy-author-2900
09/13/2023, 6:06 PMuseTracks , the example shows this
const trackReferences: TrackReference[] = useTracks([Track.Source.Camera]);fancy-author-2900
09/13/2023, 6:07 PMTrack.Source.Camera .. do I?acoustic-engineer-41666
09/13/2023, 6:07 PMfancy-author-2900
09/13/2023, 6:09 PMacoustic-engineer-41666
09/13/2023, 6:09 PMTrack.Source.Cameraacoustic-engineer-41666
09/13/2023, 6:11 PM<ParticipantTile trackRef={tracks[0]} />fancy-author-2900
09/13/2023, 6:12 PMUncaught Error: No participant provided, make sure you are inside a participant context or pass the participant explicitlyfancy-author-2900
09/13/2023, 6:13 PMtrackRef property: https://docs.livekit.io/reference/components/react/component/participanttile/#usagefancy-author-2900
09/13/2023, 6:14 PM<ParticipantTile {...tracks} /> to match <ParticipantTile {...trackReference} /> in the documentation, but that doesn't seem to work eitheracoustic-engineer-41666
09/13/2023, 6:14 PM<ParticipantTile {...trackReference} />
or, in your case
<ParticipantTile {...tracks[0]} />fancy-author-2900
09/13/2023, 6:15 PMUncaught Error: No participant provided, make sure you are inside a participant context or pass the participant explicitlyfancy-author-2900
09/13/2023, 6:15 PMfancy-author-2900
09/13/2023, 6:15 PMimport {
LiveKitRoom,
VideoConference,
GridLayout,
ParticipantTile,
useTracks,
RoomAudioRenderer,
ControlBar,
FocusLayout,
ConnectionState,
} from "@livekit/components-react";
import { Track } from "livekit-client";
function MyVideoConference() {
// `useTracks` returns all camera and screen share tracks. If a user
// joins without a published camera track, a placeholder track is returned.
const tracks = useTracks(
[
{ source: Track.Source.Camera, withPlaceholder: true },
{ source: Track.Source.ScreenShare, withPlaceholder: false },
],
{ onlySubscribed: false }
);
return (
<ParticipantTile {...tracks[0]} />
// <GridLayout
// tracks={tracks}
// style={{ height: "calc(100vh - var(--lk-control-bar-height))" }}
// >
// {/* The GridLayout accepts zero or one child. The child is used
// as a template to render all passed in tracks. */}
// <ParticipantTile />
// </GridLayout>
// // <FocusLayout />
);
}
const LiveKitVideoPlayer = observer(
({ workspace }: { workspace: Workspace }) => {
const serverUrl = workspace.liveKitWebSocketUrl;
const token = workspace.liveKitToken;
useEffect(() => {}, []);
return (
<LiveKitRoom
video={true}
audio={true}
token={token}
screen={false}
onConnected={() => {
console.log("connected");
}}
connectOptions={{ autoSubscribe: true }}
serverUrl={serverUrl}
// Use the default LiveKit theme for nice styles.
// data-lk-theme="default"
// style={{ height: "100vh" }}
>
{/* Your custom component with basic video conferencing functionality. */}
<MyVideoConference />
{/* The RoomAudioRenderer takes care of room-wide audio for you. */}
{/* <RoomAudioRenderer /> */}
{/* Controls for the user to start/stop audio, video, and screen
share tracks and to leave the room. */}
{/* <ControlBar /> */}
{/* <ParticipantTile /> */}
<ConnectionState />
</LiveKitRoom>
);
}
);fancy-author-2900
09/13/2023, 6:16 PM[
{ source: Track.Source.Camera, withPlaceholder: true },
{ source: Track.Source.ScreenShare, withPlaceholder: false },
],fancy-author-2900
09/13/2023, 6:17 PMfancy-author-2900
09/13/2023, 6:17 PMacoustic-engineer-41666
09/13/2023, 6:17 PMfancy-author-2900
09/13/2023, 6:18 PMacoustic-engineer-41666
09/13/2023, 6:25 PMvideo={true} and audio={true} from the LiveKitRoom component… I realize now that’s pretty confusingfancy-author-2900
09/13/2023, 6:28 PMdiv of some kind in there, with a certain id?acoustic-engineer-41666
09/13/2023, 6:29 PMvideo={true} and audio={true} from the LiveKitRoom component while keeping the original MyVideoConference implementationacoustic-engineer-41666
09/13/2023, 6:29 PMfancy-author-2900
09/13/2023, 6:30 PMfancy-author-2900
09/13/2023, 6:31 PMfancy-author-2900
09/13/2023, 6:31 PMfancy-author-2900
09/13/2023, 6:31 PMfancy-author-2900
09/13/2023, 6:31 PM<GridLayout
tracks={tracks}
style={{ height: "calc(100vh - var(--lk-control-bar-height))" }}
>
{/* The GridLayout accepts zero or one child. The child is used
as a template to render all passed in tracks. */}
<ParticipantTile />
</GridLayout>fancy-author-2900
09/13/2023, 6:31 PMfancy-author-2900
09/13/2023, 6:32 PMGridLayout and just using <ParticipantTile {...tracks[0]} /> but I get this error Uncaught Error: No participant provided, make sure you are inside a participant context or pass the participant explicitlyacoustic-engineer-41666
09/13/2023, 6:45 PM"use client";
import "@livekit/components-styles";
import {
LiveKitRoom,
RoomAudioRenderer,
ControlBar,
GridLayout,
ParticipantTile,
useTracks,
TrackLoop,
} from "@livekit/components-react";
import { Track } from "livekit-client";
import { useEffect, useState } from "react";
export default function Page() {
// TODO: get user input for room and name
const room = "quickstart-room";
const name = "quickstart-user";
const [token, setToken] = useState("");
useEffect(() => {
(async () => {
try {
const resp = await fetch(`/api/get-participant-token?room=${room}&username=${name}`);
const data = await resp.json();
setToken(data.token);
} catch (e) {
console.error(e);
}
})();
}, []);
if (token === "") {
return <div>Getting token...</div>;
}
return (
<LiveKitRoom
token={token}
connectOptions={{ autoSubscribe: false }}
serverUrl={process.env.NEXT_PUBLIC_LIVEKIT_URL}
// Use the default LiveKit theme for nice styles.
data-lk-theme="default"
style={{ height: "100dvh" }}
>
{/* Your custom component with basic video conferencing functionality. */}
{/* <MyVideoConference /> */}
<OnlyOneParticipant />
{/* The RoomAudioRenderer takes care of room-wide audio for you. */}
<RoomAudioRenderer />
{/* Controls for the user to start/stop audio, video, and screen
share tracks and to leave the room. */}
<ControlBar />
</LiveKitRoom>
);
}
function OnlyOneParticipant() {
const tracks = useTracks([{ source: Track.Source.Camera, withPlaceholder: true }], {
onlySubscribed: false,
});
// this works too, but should be identical if there's only one track:
// return (
// <TrackLoop tracks={tracks} style={{ height: "calc(100vh - var(--lk-control-bar-height))" }}>
// <ParticipantTile />
// </TrackLoop>
// );
return <ParticipantTile {...tracks[0]} />;
}acoustic-engineer-41666
09/13/2023, 6:46 PMtracks that you get from useTracks?fancy-author-2900
09/13/2023, 6:46 PMfancy-author-2900
09/13/2023, 6:51 PMreturn <ParticipantTile {...tracks[0]} />; gives me the No participant provided error, but
return (
<TrackLoop
tracks={tracks}
style={{ height: "calc(100vh - var(--lk-control-bar-height))" }}
>
<ParticipantTile />
</TrackLoop>
);fancy-author-2900
09/13/2023, 6:51 PMacoustic-engineer-41666
09/13/2023, 6:52 PMtracks value just to see what it is?
console.log("tracks =", tracks);acoustic-engineer-41666
09/13/2023, 6:52 PMOnlyOneParticipant component, before the return statement)fancy-author-2900
09/13/2023, 6:52 PM[]fancy-author-2900
09/13/2023, 6:53 PMacoustic-engineer-41666
09/13/2023, 6:54 PMfancy-author-2900
09/13/2023, 6:54 PMfancy-author-2900
09/13/2023, 6:54 PMfancy-author-2900
09/13/2023, 6:54 PMfancy-author-2900
09/13/2023, 6:55 PMacoustic-engineer-41666
09/13/2023, 6:55 PMfancy-author-2900
09/13/2023, 6:56 PMfancy-author-2900
09/13/2023, 6:56 PMfancy-author-2900
09/13/2023, 6:56 PMfancy-author-2900
09/13/2023, 6:57 PM<video class="lk-participant-media-video" data-lk-local-participant="false" data-lk-source="camera" data-lk-orientation="portrait" autoplay="" playsinline=""></video>acoustic-engineer-41666
09/13/2023, 6:57 PMacoustic-engineer-41666
09/13/2023, 6:57 PMacoustic-engineer-41666
09/13/2023, 6:58 PMfancy-author-2900
09/13/2023, 6:58 PMfancy-author-2900
09/13/2023, 6:59 PMParticipantTilefancy-author-2900
09/13/2023, 6:59 PMacoustic-engineer-41666
09/13/2023, 6:59 PMacoustic-engineer-41666
09/13/2023, 7:00 PMParticipantTile, it may also be worth looking at this example livestreaming app which does not use ParticipantTile at all: https://github.com/livekit-examples/livestream/blob/main/src/components/channel/StreamPlayer.tsx#L104acoustic-engineer-41666
09/13/2023, 7:00 PMpolite-kilobyte-67570
09/13/2023, 10:27 PMVideoTrack component directly instead of the ParticipantTile, and then you would only have the “raw” video element in your DOM. you can pass in the trackRef directly to that component.fancy-author-2900
09/14/2023, 12:04 AMVideoTrack - here is my cut-down of ParticipantTile that is currently working
import * as React from "react";
import type { Participant, TrackPublication } from "livekit-client";
import { Track } from "livekit-client";
import type {
ParticipantClickEvent,
TrackReferenceOrPlaceholder,
} from "@livekit/components-core";
import { isParticipantSourcePinned } from "@livekit/components-core";
import {
ParticipantContext,
useEnsureParticipant,
useMaybeLayoutContext,
useMaybeParticipantContext,
useMaybeTrackContext,
FocusToggle,
VideoTrack,
useParticipantTile,
} from "@livekit/components-react";
/** @public */
export function ParticipantContextIfNeeded(
props: React.PropsWithChildren<{
participant?: Participant;
}>
) {
const hasContext = !!useMaybeParticipantContext();
return props.participant && !hasContext ? (
<ParticipantContext.Provider value={props.participant}>
{props.children}
</ParticipantContext.Provider>
) : (
<>{props.children}</>
);
}
/** @public */
export interface ParticipantTileProps
extends React.HTMLAttributes<HTMLDivElement> {
disableSpeakingIndicator?: boolean;
participant?: Participant;
source?: Track.Source;
publication?: TrackPublication;
onParticipantClick?: (event: ParticipantClickEvent) => void;
}
export function CustomParticipantTile({
participant,
children,
source = Track.Source.Camera,
onParticipantClick,
publication,
disableSpeakingIndicator,
...htmlProps
}: ParticipantTileProps) {
const p = useEnsureParticipant(participant);
const trackRef: TrackReferenceOrPlaceholder = useMaybeTrackContext() ?? {
participant: p,
source,
publication,
};
const { elementProps } = useParticipantTile<HTMLDivElement>({
participant: trackRef.participant,
htmlProps,
source: trackRef.source,
publication: trackRef.publication,
disableSpeakingIndicator,
onParticipantClick,
});
// const isEncrypted = useIsEncrypted(p);
const layoutContext = useMaybeLayoutContext();
const handleSubscribe = React.useCallback(
(subscribed: boolean) => {
if (
trackRef.source &&
!subscribed &&
layoutContext &&
layoutContext.pin.dispatch &&
isParticipantSourcePinned(
trackRef.participant,
trackRef.source,
layoutContext.pin.state
)
) {
layoutContext.pin.dispatch({ msg: "clear_pin" });
}
},
[trackRef.participant, layoutContext, trackRef.source]
);
return (
// <ParticipantContextIfNeeded participant={trackRef.participant}>
<>
<VideoTrack
participant={trackRef.participant}
source={trackRef.source}
publication={trackRef.publication}
onSubscriptionStatusChanged={handleSubscribe}
manageSubscription={true}
/>
</>
// </ParticipantContextIfNeeded>
);
}fancy-author-2900
09/14/2023, 12:04 AMParticipantContextfancy-author-2900
09/14/2023, 12:04 AMfancy-author-2900
09/14/2023, 12:05 AMfancy-author-2900
09/14/2023, 12:06 AMCustomParticipantTilepolite-kilobyte-67570
09/14/2023, 5:33 AMfancy-author-2900
09/14/2023, 6:04 PMimport * as React from "react";
import { useRoomContext, useStartAudio } from "@livekit/components-react";
/** @public */
export interface AllowAudioPlaybackProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
label: string;
}
/**
* The StartAudio component is only visible when the browser blocks audio playback. This is due to some browser implemented autoplay policies.
* To start audio playback, the user must perform a user-initiated event such as clicking this button.
* As soon as audio playback starts, the button hides itself again.
*
* @example
* ```tsx
* <LiveKitRoom>
* <StartAudio label="Click to allow audio playback" />
* </LiveKitRoom>
*
*
* @see Autoplay policy on MDN web docs: {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Best_practices#autoplay_policy}
* @public
*/
export function StartLiveKitAudio({
label = "Allow Audio",
...props
}: AllowAudioPlaybackProps) {
const room = useRoomContext();
const { mergedProps } = useStartAudio({ room, props });
return (
<button {...mergedProps} className="play-button">
<span className="play-icon">▶</span>
</button>
);
}```fancy-author-2900
09/14/2023, 6:04 PM.play-button {
position: absolute; /* Position absolutely */
top: 50%; /* Center vertically */
left: 50%; /* Center horizontally */
transform: translate(-50%, -50%); /* Center the button */
background-color: transparent;
color: white; /* Set the text color to white */
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
opacity: 0.66;
display: flex;
align-items: center;
justify-content: center;
width: auto; /* Allow the button to size based on content */
padding: 0; /* Remove padding */
}
.play-icon {
font-size: 55px;
}fancy-author-2900
09/14/2023, 6:04 PMfancy-author-2900
09/14/2023, 6:04 PM