-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from cloudflare/rxjs-for-rtcpeerconnection
Use RxJSPeer for the peer connection pushing/pulling
- Loading branch information
Showing
29 changed files
with
827 additions
and
529 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,104 @@ | ||
import type { FC } from 'react' | ||
import { useEffect, useRef } from 'react' | ||
import { useEffect, useMemo, useRef } from 'react' | ||
import { of } from 'rxjs' | ||
import { useSubscribedState } from '~/hooks/rxjsHooks' | ||
import { useRoomContext } from '~/hooks/useRoomContext' | ||
|
||
interface AudioStreamProps { | ||
mediaStreamTrack: MediaStreamTrack | ||
tracksToPull: string[] | ||
onTrackAdded: (id: string, track: MediaStreamTrack) => void | ||
onTrackRemoved: (id: string, track: MediaStreamTrack) => void | ||
} | ||
|
||
export const AudioStream: FC<AudioStreamProps> = ({ mediaStreamTrack }) => { | ||
export const AudioStream: FC<AudioStreamProps> = ({ | ||
tracksToPull, | ||
onTrackAdded, | ||
onTrackRemoved, | ||
}) => { | ||
const mediaStreamRef = useRef(new MediaStream()) | ||
const ref = useRef<HTMLAudioElement>(null) | ||
|
||
useEffect(() => { | ||
const audio = ref.current | ||
if (!audio) return | ||
const mediaStream = new MediaStream() | ||
mediaStream.addTrack(mediaStreamTrack) | ||
const mediaStream = mediaStreamRef.current | ||
audio.srcObject = mediaStream | ||
}, [mediaStreamTrack]) | ||
}, []) | ||
|
||
return <audio ref={ref} autoPlay /> | ||
const resetSrcObject = () => { | ||
const audio = ref.current | ||
const mediaStream = mediaStreamRef.current | ||
if (!audio || !mediaStream) return | ||
// need to set srcObject again in Chrome and call play() again for Safari | ||
// https://www.youtube.com/live/Tkx3OGrwVk8?si=K--P_AzNnAGrjraV&t=2533 | ||
// calling play() this way to make Chrome happy otherwise it throws an error | ||
audio.addEventListener('canplay', () => audio.play(), { once: true }) | ||
audio.srcObject = mediaStream | ||
} | ||
|
||
return ( | ||
<> | ||
<audio ref={ref} autoPlay /> | ||
{tracksToPull.map((track) => ( | ||
<AudioTrack | ||
key={track} | ||
track={track} | ||
mediaStream={mediaStreamRef.current} | ||
onTrackAdded={(metadata, track) => { | ||
onTrackAdded(metadata, track) | ||
resetSrcObject() | ||
}} | ||
onTrackRemoved={(metadata, track) => { | ||
onTrackRemoved(metadata, track) | ||
resetSrcObject() | ||
}} | ||
/> | ||
))} | ||
</> | ||
) | ||
} | ||
|
||
function AudioTrack({ | ||
mediaStream, | ||
track, | ||
onTrackAdded, | ||
onTrackRemoved, | ||
}: { | ||
mediaStream: MediaStream | ||
track: string | ||
onTrackAdded: (id: string, track: MediaStreamTrack) => void | ||
onTrackRemoved: (id: string, track: MediaStreamTrack) => void | ||
}) { | ||
const onTrackAddedRef = useRef(onTrackAdded) | ||
onTrackAddedRef.current = onTrackAdded | ||
const onTrackRemovedRef = useRef(onTrackRemoved) | ||
onTrackRemovedRef.current = onTrackRemoved | ||
|
||
const { peer } = useRoomContext() | ||
const trackObject = useMemo(() => { | ||
const [sessionId, trackName] = track.split('/') | ||
return { | ||
sessionId, | ||
trackName, | ||
location: 'remote', | ||
} as const | ||
}, [track]) | ||
|
||
const pulledTrack$ = useMemo(() => { | ||
return peer.pullTrack(of(trackObject)) | ||
}, [peer, trackObject]) | ||
|
||
const audioTrack = useSubscribedState(pulledTrack$) | ||
|
||
useEffect(() => { | ||
if (!audioTrack) return | ||
mediaStream.addTrack(audioTrack) | ||
onTrackAddedRef.current(track, audioTrack) | ||
return () => { | ||
mediaStream.removeTrack(audioTrack) | ||
onTrackRemovedRef.current(track, audioTrack) | ||
} | ||
}, [audioTrack]) | ||
|
||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,47 @@ | ||
import type { ReactElement } from 'react' | ||
import { useEffect, useState } from 'react' | ||
import { useMemo } from 'react' | ||
import { of, switchMap } from 'rxjs' | ||
import { useStateObservable, useSubscribedState } from '~/hooks/rxjsHooks' | ||
import { useRoomContext } from '~/hooks/useRoomContext' | ||
import keepTrying from '~/utils/keepTrying' | ||
import type { TrackObject } from '~/utils/callsTypes' | ||
import { usePulledAudioTrack } from './PullAudioTracks' | ||
|
||
interface PullTracksProps { | ||
audio?: string | ||
video?: string | ||
audio?: string | ||
children: (props: { | ||
audioTrack?: MediaStreamTrack | ||
videoTrack?: MediaStreamTrack | ||
audioTrack?: MediaStreamTrack | ||
}) => ReactElement | ||
} | ||
|
||
export const PullVideoTrack = ({ video, audio, children }: PullTracksProps) => { | ||
const { peer } = useRoomContext() | ||
|
||
const [videoTrack, setVideoTrack] = useState<MediaStreamTrack>() | ||
const audioTrack = usePulledAudioTrack(audio) | ||
|
||
useEffect(() => { | ||
if (!video || !peer) return | ||
let mounted = true | ||
const cancel = keepTrying(() => { | ||
const [sessionId, trackName] = video.split('/') | ||
// backward compatibility: ResourceID -> TrackObject | ||
return peer | ||
.pullTrack({ location: 'remote', sessionId, trackName }) | ||
.then((track) => { | ||
if (mounted) setVideoTrack(track) | ||
}) | ||
}) | ||
return () => { | ||
cancel() | ||
mounted = false | ||
} | ||
}, [peer, video]) | ||
|
||
useEffect(() => { | ||
if (videoTrack && peer) | ||
return () => { | ||
peer.closeTrack(videoTrack) | ||
} | ||
}, [videoTrack, peer]) | ||
const [sessionId, trackName] = video?.split('/') ?? [] | ||
const trackObject = useMemo( | ||
() => | ||
sessionId && trackName | ||
? ({ | ||
trackName, | ||
sessionId, | ||
location: 'remote', | ||
} satisfies TrackObject) | ||
: undefined, | ||
[sessionId, trackName] | ||
) | ||
|
||
const trackObject$ = useStateObservable(trackObject) | ||
const pulledTrack$ = useMemo( | ||
() => | ||
trackObject$.pipe( | ||
switchMap((track) => | ||
track ? peer.pullTrack(of(track)) : of(undefined) | ||
) | ||
), | ||
[peer, trackObject$] | ||
) | ||
const videoTrack = useSubscribedState(pulledTrack$) | ||
return children({ videoTrack, audioTrack }) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.