use new components
This commit is contained in:
parent
203687bc72
commit
e4f5ee7a8f
@ -1,6 +1,7 @@
|
||||
import React from "react"
|
||||
import { Props } from "./Conference.types"
|
||||
import { LiveKitRoom, VideoConference } from "@livekit/components-react"
|
||||
import { LiveKitRoom } from "@livekit/components-react"
|
||||
import { VideoConference } from "../../VideoConference/Videoconference"
|
||||
import "@livekit/components-styles"
|
||||
import "./Conference.css"
|
||||
|
||||
|
||||
156
src/components/VideoConference/ParticipantTile.tsx
Normal file
156
src/components/VideoConference/ParticipantTile.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
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 {
|
||||
AudioTrack,
|
||||
ConnectionQualityIndicator,
|
||||
FocusToggle,
|
||||
ParticipantContext,
|
||||
ParticipantName,
|
||||
TrackMutedIndicator,
|
||||
VideoTrack,
|
||||
useEnsureParticipant,
|
||||
useMaybeLayoutContext,
|
||||
useMaybeParticipantContext,
|
||||
useMaybeTrackContext,
|
||||
useParticipantTile,
|
||||
} from "@livekit/components-react"
|
||||
import Profile from "decentraland-dapps/dist/containers/Profile"
|
||||
|
||||
/** @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
|
||||
imageSize?: "normal" | "large" | "huge" | "massive"
|
||||
}
|
||||
|
||||
/**
|
||||
* The ParticipantTile component is the base utility wrapper for displaying a visual representation of a participant.
|
||||
* This component can be used as a child of the `TrackLoop` component or by spreading a track reference as properties.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <ParticipantTile source={Track.Source.Camera} />
|
||||
*
|
||||
* <ParticipantTile {...trackReference} />
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function ParticipantTile({
|
||||
participant,
|
||||
children,
|
||||
source = Track.Source.Camera,
|
||||
onParticipantClick,
|
||||
publication,
|
||||
disableSpeakingIndicator,
|
||||
imageSize,
|
||||
...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 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]
|
||||
)
|
||||
|
||||
const participantWithProfile: Participant = React.useMemo(
|
||||
() => ({
|
||||
...trackRef.participant,
|
||||
name: "Edita me",
|
||||
}),
|
||||
[trackRef.participant]
|
||||
) as Participant
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }} {...elementProps}>
|
||||
<ParticipantContextIfNeeded participant={trackRef.participant}>
|
||||
{children ?? (
|
||||
<>
|
||||
{trackRef.publication?.kind === "video" ||
|
||||
trackRef.source === Track.Source.Camera ||
|
||||
trackRef.source === Track.Source.ScreenShare ? (
|
||||
<VideoTrack
|
||||
participant={trackRef.participant}
|
||||
source={trackRef.source}
|
||||
publication={trackRef.publication}
|
||||
onSubscriptionStatusChanged={handleSubscribe}
|
||||
/>
|
||||
) : (
|
||||
<AudioTrack
|
||||
participant={trackRef.participant}
|
||||
source={trackRef.source}
|
||||
publication={trackRef.publication}
|
||||
onSubscriptionStatusChanged={handleSubscribe}
|
||||
/>
|
||||
)}
|
||||
<div className="lk-participant-placeholder">
|
||||
<Profile address={trackRef.participant.identity} imageOnly size={imageSize} />
|
||||
</div>
|
||||
<div className="lk-participant-metadata">
|
||||
<div className="lk-participant-metadata-item">
|
||||
{trackRef.source === Track.Source.Camera ? (
|
||||
<>
|
||||
<TrackMutedIndicator source={Track.Source.Microphone} show={"muted"}></TrackMutedIndicator>
|
||||
<ParticipantName participant={participantWithProfile} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* <ScreenShareIcon style={{ marginRight: "0.25rem" }} /> */}
|
||||
<ParticipantName participant={participantWithProfile}>'s screen</ParticipantName>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<ConnectionQualityIndicator className="lk-participant-metadata-item" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<FocusToggle trackSource={trackRef.source} />
|
||||
</ParticipantContextIfNeeded>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
129
src/components/VideoConference/Videoconference.tsx
Normal file
129
src/components/VideoConference/Videoconference.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import * as React from "react"
|
||||
import type { WidgetState } from "@livekit/components-core"
|
||||
import { isEqualTrackRef, isTrackReference, log, isWeb } from "@livekit/components-core"
|
||||
import { RoomEvent, Track } from "livekit-client"
|
||||
import type { TrackReferenceOrPlaceholder } from "@livekit/components-core"
|
||||
import {
|
||||
CarouselView,
|
||||
Chat,
|
||||
ConnectionStateToast,
|
||||
ControlBar,
|
||||
FocusLayout,
|
||||
FocusLayoutContainer,
|
||||
GridLayout,
|
||||
LayoutContextProvider,
|
||||
MessageFormatter,
|
||||
RoomAudioRenderer,
|
||||
useCreateLayoutContext,
|
||||
useParticipants,
|
||||
usePinnedTracks,
|
||||
useTracks,
|
||||
} from "@livekit/components-react"
|
||||
import { ParticipantTile } from "./ParticipantTile"
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
chatMessageFormatter?: MessageFormatter
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is the default setup of a classic LiveKit video conferencing app.
|
||||
* It provides functionality like switching between participant grid view and focus view.
|
||||
*
|
||||
* @remarks
|
||||
* The component is implemented with other LiveKit components like `FocusContextProvider`,
|
||||
* `GridLayout`, `ControlBar`, `FocusLayoutContainer` and `FocusLayout`.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <LiveKitRoom>
|
||||
* <VideoConference />
|
||||
* <LiveKitRoom>
|
||||
* ```
|
||||
* @public
|
||||
*/
|
||||
export function VideoConference({ chatMessageFormatter, ...props }: VideoConferenceProps) {
|
||||
const [widgetState, setWidgetState] = React.useState<WidgetState>({ showChat: false })
|
||||
const lastAutoFocusedScreenShareTrack = React.useRef<TrackReferenceOrPlaceholder | null>(null)
|
||||
|
||||
const tracks = useTracks(
|
||||
[
|
||||
{ source: Track.Source.Camera, withPlaceholder: true },
|
||||
{ source: Track.Source.ScreenShare, withPlaceholder: false },
|
||||
],
|
||||
{ updateOnlyOn: [RoomEvent.ActiveSpeakersChanged] }
|
||||
)
|
||||
|
||||
const participants = useParticipants({
|
||||
updateOnlyOn: [RoomEvent.ParticipantConnected, RoomEvent.ParticipantDisconnected],
|
||||
})
|
||||
|
||||
const widgetUpdate = (state: WidgetState) => {
|
||||
log.debug("updating widget state", state)
|
||||
setWidgetState(state)
|
||||
}
|
||||
|
||||
const layoutContext = useCreateLayoutContext()
|
||||
|
||||
const screenShareTracks = tracks
|
||||
.filter(isTrackReference)
|
||||
.filter((track) => track.publication.source === Track.Source.ScreenShare)
|
||||
|
||||
const focusTrack = usePinnedTracks(layoutContext)?.[0]
|
||||
const carouselTracks = tracks.filter((track) => !isEqualTrackRef(track, focusTrack))
|
||||
|
||||
React.useEffect(() => {
|
||||
// If screen share tracks are published, and no pin is set explicitly, auto set the screen share.
|
||||
if (screenShareTracks.length > 0 && lastAutoFocusedScreenShareTrack.current === null) {
|
||||
log.debug("Auto set screen share focus:", { newScreenShareTrack: screenShareTracks[0] })
|
||||
layoutContext.pin.dispatch?.({ msg: "set_pin", trackReference: screenShareTracks[0] })
|
||||
lastAutoFocusedScreenShareTrack.current = screenShareTracks[0]
|
||||
} else if (
|
||||
lastAutoFocusedScreenShareTrack.current &&
|
||||
!screenShareTracks.some(
|
||||
(track) => track.publication.trackSid === lastAutoFocusedScreenShareTrack.current?.publication?.trackSid
|
||||
)
|
||||
) {
|
||||
log.debug("Auto clearing screen share focus.")
|
||||
layoutContext.pin.dispatch?.({ msg: "clear_pin" })
|
||||
lastAutoFocusedScreenShareTrack.current = null
|
||||
}
|
||||
}, [screenShareTracks.map((ref) => ref.publication.trackSid).join(), focusTrack?.publication?.trackSid])
|
||||
|
||||
return (
|
||||
<div className="lk-video-conference" {...props}>
|
||||
{isWeb() && (
|
||||
<LayoutContextProvider
|
||||
value={layoutContext}
|
||||
// onPinChange={handleFocusStateChange}
|
||||
onWidgetChange={widgetUpdate}
|
||||
>
|
||||
<div className="lk-video-conference-inner">
|
||||
{!focusTrack ? (
|
||||
<div className="lk-grid-layout-wrapper">
|
||||
<GridLayout tracks={tracks}>
|
||||
<ParticipantTile imageSize="massive" />
|
||||
</GridLayout>
|
||||
</div>
|
||||
) : (
|
||||
<div className="lk-focus-layout-wrapper">
|
||||
<FocusLayoutContainer>
|
||||
<CarouselView tracks={carouselTracks}>
|
||||
<ParticipantTile imageSize="huge" />
|
||||
</CarouselView>
|
||||
{focusTrack && <FocusLayout track={focusTrack} />}
|
||||
</FocusLayoutContainer>
|
||||
</div>
|
||||
)}
|
||||
<ControlBar controls={{ chat: true }} />
|
||||
</div>
|
||||
<Chat style={{ display: widgetState.showChat ? "flex" : "none" }} messageFormatter={chatMessageFormatter} />
|
||||
</LayoutContextProvider>
|
||||
)}
|
||||
<RoomAudioRenderer />
|
||||
<ConnectionStateToast />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
8
src/modules/config/env/dev.json
vendored
8
src/modules/config/env/dev.json
vendored
@ -1,8 +1,6 @@
|
||||
{
|
||||
"NETWORK": "sepolia",
|
||||
"CHAIN_ID": "11155111",
|
||||
"EXPLORER_URL": "https://play.decentraland.zone",
|
||||
"PEER_URL": "https://peer-ap1.decentraland.zone",
|
||||
"MARKETPLACE_GRAPH_URL": "https://api.studio.thegraph.com/query/49472/marketplace-sepolia/version/latest",
|
||||
"NETWORK": "mainnet",
|
||||
"CHAIN_ID": "1",
|
||||
"PEER_URL": "https://peer.decentraland.org",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.zone"
|
||||
}
|
||||
|
||||
7
src/modules/config/env/prod.json
vendored
7
src/modules/config/env/prod.json
vendored
@ -3,10 +3,5 @@
|
||||
"CHAIN_ID": "1",
|
||||
"EXPLORER_URL": "https://play.decentraland.org",
|
||||
"PEER_URL": "https://peer.decentraland.org",
|
||||
"MARKETPLACE_GRAPH_URL": "https://api.thegraph.com/subgraphs/name/decentraland/marketplace-goerli",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org",
|
||||
"BUILDER_URL": "https://builder.decentraland.org",
|
||||
"SYNAPSE_URL": "https://social.decentraland.org",
|
||||
"SOCIAL_RPC_URL": "wss://rpc-social-service.decentraland.org",
|
||||
"PROFILE_URL": "https://profile.decentraland.org"
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org"
|
||||
}
|
||||
|
||||
8
src/modules/config/env/stg.json
vendored
8
src/modules/config/env/stg.json
vendored
@ -1,12 +1,6 @@
|
||||
{
|
||||
"NETWORK": "mainnet",
|
||||
"CHAIN_ID": "1",
|
||||
"EXPLORER_URL": "https://play.decentraland.today",
|
||||
"PEER_URL": "https://peer.decentraland.org",
|
||||
"MARKETPLACE_GRAPH_URL": "https://api.thegraph.com/subgraphs/name/decentraland/marketplace",
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org",
|
||||
"SOCIAL_RPC_URL": "wss://rpc-social-service.decentraland.today",
|
||||
"BUILDER_URL": "https://builder.decentraland.today",
|
||||
"SYNAPSE_URL": "https://social.decentraland.today",
|
||||
"PROFILE_URL": "https://profile.decentraland.today"
|
||||
"WORLDS_CONTENT_SERVER_URL": "https://worlds-content-server.decentraland.org"
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user