Skip to content

Instantly share code, notes, and snippets.

@felippepuhle
Created November 13, 2020 13:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felippepuhle/c989258ab0f47b05b09ff94d2344015c to your computer and use it in GitHub Desktop.
Save felippepuhle/c989258ab0f47b05b09ff94d2344015c to your computer and use it in GitHub Desktop.
ant media web player
export enum AntMediaCommands {
PLAY = 'play',
TAKE_CONFIGURATION = 'takeConfiguration',
TAKE_CANDIDATE = 'takeCandidate',
}
export enum AntMediaTakeConfigurationType {
OFFER = 'offer',
}
export type AntMediaTakeConfigurationSignal = {
command: AntMediaCommands.TAKE_CONFIGURATION
type: AntMediaTakeConfigurationType
sdp: string
}
export type AntMediaTakeCandidateSignal = {
command: AntMediaCommands.TAKE_CANDIDATE
candidate: string
id: string
label: number
}
export type AntMediaSignal =
| AntMediaTakeConfigurationSignal
| AntMediaTakeCandidateSignal
import React, { useRef, useState, useCallback, useEffect, memo } from 'react'
import styled from 'styled-components'
import { MEDIA } from 'src/styles'
import {
AntMediaCommands,
AntMediaTakeConfigurationType,
AntMediaTakeConfigurationSignal,
AntMediaTakeCandidateSignal,
AntMediaSignal,
} from './modules/constants'
const Wrapper = styled.div`
display: flex;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
@media (${MEDIA.LG}) {
position: relative;
top: auto;
right: auto;
bottom: auto;
left: auto;
flex: 1;
}
`
const Video = styled.video.attrs({ autoplay: true })`
flex: 1;
object-fit: cover;
object-position: top;
`
const ANT_MEDIA_URL = process.env.REACT_APP_ANT_MEDIA_URL || ''
const WS_PORT = ANT_MEDIA_URL?.indexOf('wss') > -1 ? '5443' : '5080'
const WS_URL = `${ANT_MEDIA_URL}:${WS_PORT}/WebRTCAppEE/websocket`
const STREAM_ID = process.env.REACT_APP_ANT_MEDIA_STREAM_ID || ''
const Player: React.FC = () => {
const videoRef = useRef<HTMLVideoElement>(null)
const [signalingChannel] = useState(() => new WebSocket(WS_URL))
const [peerConnection] = useState(() => new RTCPeerConnection())
const [stream] = useState(() => new MediaStream())
const onTrack = useCallback(
(event: RTCTrackEvent) => {
stream.addTrack(event.track)
},
[stream]
)
const onTakeConfiguration = useCallback(
async ({ type, sdp }: AntMediaTakeConfigurationSignal) => {
peerConnection.setRemoteDescription(
new RTCSessionDescription({ type, sdp })
)
if (type === AntMediaTakeConfigurationType.OFFER) {
const answer = await peerConnection.createAnswer()
await peerConnection.setLocalDescription(answer)
signalingChannel.send(
JSON.stringify({
command: AntMediaCommands.TAKE_CONFIGURATION,
streamId: STREAM_ID,
type: answer.type,
sdp: answer.sdp,
})
)
}
},
[signalingChannel, peerConnection]
)
const onTakeCandidate = useCallback(
({ candidate, id, label }: AntMediaTakeCandidateSignal) =>
peerConnection.addIceCandidate(
new RTCIceCandidate({
candidate: candidate,
sdpMid: id,
sdpMLineIndex: label,
})
),
[peerConnection]
)
useEffect(() => {
peerConnection.ontrack = onTrack
signalingChannel.onopen = () => {
signalingChannel.send(
JSON.stringify({
command: AntMediaCommands.PLAY,
streamId: STREAM_ID,
})
)
}
signalingChannel.onmessage = ({ data }) => {
const signal: AntMediaSignal = JSON.parse(data)
if (signal.command === AntMediaCommands.TAKE_CONFIGURATION) {
onTakeConfiguration(signal)
}
if (signal.command === AntMediaCommands.TAKE_CANDIDATE) {
onTakeCandidate(signal)
}
}
return () => {
peerConnection?.close()
signalingChannel?.close()
}
}, [
signalingChannel,
peerConnection,
onTrack,
onTakeConfiguration,
onTakeCandidate,
])
useEffect(() => {
if (!videoRef.current) {
return
}
videoRef.current.srcObject = stream
videoRef.current.play()
}, [stream])
return (
<Wrapper>
<Video ref={videoRef} />
</Wrapper>
)
}
export default memo(Player)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment