Skip to content

Commit

Permalink
feat: add webRTC example player
Browse files Browse the repository at this point in the history
  • Loading branch information
danielkaxis committed Feb 28, 2024
1 parent e22536d commit a17e3cc
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 2 deletions.
10 changes: 10 additions & 0 deletions example-player-react/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import styled, { createGlobalStyle } from 'styled-components'
import { BasicStream } from './BasicStream'
import { MultiStream } from './MultiStream'
import { SingleStream } from './SingleStream'
import { WebRtcStream } from './WebRtcStream'

const GlobalStyle = createGlobalStyle`
body {
Expand Down Expand Up @@ -48,6 +49,11 @@ export const App = () => {
localStorage.setItem(LOCALSTORAGE_KEY, 'basic')
}, [setState])

const webrtc = useCallback(() => {
setState('webrtc')
localStorage.setItem(LOCALSTORAGE_KEY, 'webrtc')
}, [setState])

const multi = useCallback(() => {
setState('multi')
localStorage.setItem(LOCALSTORAGE_KEY, 'multi')
Expand All @@ -67,10 +73,14 @@ export const App = () => {
<Button onClick={multi} selected={state === 'multi'}>
Multi stream
</Button>
<Button onClick={webrtc} selected={state === 'webrtc'}>
webRTC stream
</Button>
</ButtonContainer>
{state === 'single' ? <SingleStream /> : null}
{state === 'basic' ? <BasicStream /> : null}
{state === 'multi' ? <MultiStream /> : null}
{state === 'webrtc' ? <WebRtcStream /> : null}
</AppContainer>
)
}
189 changes: 189 additions & 0 deletions example-player-react/WebRtcStream.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import React, { useCallback, useMemo, useState } from 'react'
import styled from 'styled-components'
import { BasicPlayer } from 'media-stream-player'

const Wrapper = styled.div`
display: grid;
width: 100%;
grid-auto-flow: row;
grid-template-rows: min-content auto;
grid-gap: 32px;
justify-content: center;
`

const StyledForm = styled.form`
display: flex;
flex-direction: column;
gap: 4px;
`

const PlayerContainer = styled.div`
height: 400px;
width: 700px;
`

const convertSearchParams = (searchParams) => {
const params = searchParams.split('&')

return params.reduce((acc, param) => {
const [key, value] = param.split('=')
if (value === '') {
return acc
}
return {
...acc,
[key]: value,
}
}, {})
}

/**
* Example application that uses web RTC streaming and the `BasicPlayer` component.
*/
export const WebRtcStream = () => {
const [refresh, setRefresh] = useState(0)

const [formValues, setFormValues] = useState(() => {
const web_rtc_creds_storage = localStorage.getItem('webrtc_creds')
if (web_rtc_creds_storage !== null) {
return JSON.parse(web_rtc_creds_storage)
}

return {
organizationArn: '',
serial: '',
env: 'stage',
token: '',
params: '',
}
})

const [creds, setCreds] = useState(null)

const handleSubmit = useCallback((ev) => {
ev.preventDefault()

localStorage.setItem('webrtc_creds', JSON.stringify(formValues))

setCreds(formValues)
setRefresh(previousValue => previousValue + 1)
}, [formValues, setCreds, setRefresh])

const handleChange = useCallback((ev) => {
ev.preventDefault()
const nextFormValues = {}
const nextValue = ev.target.value.trim()
nextFormValues[ev.target.id] = nextValue

setCreds(null)
setFormValues((previousValue) => ({ ...previousValue, ...nextFormValues }))
}, [setCreds, setFormValues])

// web RTC signaling requires a promise that returns a token
const authenticator = useCallback(async () => creds?.token, [creds?.token])

// The signaling options is configuration
// for web RTC streaming.
const signalingOptions = useMemo(() => {
if (creds === null) {
return
}
const { token, params, ...rest } = creds
const invalidCreds = Object.values(rest).some(value => value === '')
if (invalidCreds) {
return undefined
}
return {
...rest,
authenticator,
}
}, [authenticator, creds])

const vapixParams = useMemo(() => {
if (formValues.params === '') {
return undefined
}

return convertSearchParams(formValues.params)
}, [formValues])

return (
<Wrapper>
<StyledForm
onSubmit={handleSubmit}
>
<label htmlFor="organizationArn">Organization arn</label>
<input
id="organizationArn"
type="text"
placeholder="00000000-0000-0000-0000-000000000000"
value={formValues.organizationArn}
onChange={handleChange}
/>

<label htmlFor="serial">Device S/N</label>
<input
id="serial"
type="text"
placeholder="AA11BB22BB33"
value={formValues.serial}
onChange={handleChange}
/>

<label htmlFor="token">Bearer token</label>
<textarea
id="token"
placeholder="xxx-xxx-xxx-xxx"
value={formValues.token}
onChange={handleChange}
/>

<label htmlFor="env">Environment</label>
<select
id="env"
value={formValues.env}
onChange={handleChange}
>
<option value="stage">stage</option>
<option value="prod">prod</option>
</select>

<label htmlFor="params">
Stream parameters (supported: width, height, channel, framerate)
</label>
<textarea
id="params"
type="text"
placeholder="key=value&key=value"
value={formValues.params}
onChange={handleChange}
/>
{vapixParams !== undefined && (
<>
<pre>Params sent to the webRTC session</pre>
<pre>{JSON.stringify(vapixParams, null, 2)}</pre>
</>
)}

<button style={{ width: '150px', alignSelf: 'center' }} type="submit">
Submit
</button>
</StyledForm>
{creds !== null && (
<PlayerContainer>
<BasicPlayer
hostname={window.location.host}
format="WEBRTC"
autoPlay
autoRetry
refresh={refresh}
{...(signalingOptions !== undefined ? { signalingOptions } : {})}
{...(creds.params !== undefined
? { vapixParams: convertSearchParams(creds.params) }
: {})}
/>
</PlayerContainer>
)}
</Wrapper>
)
}
9 changes: 7 additions & 2 deletions player/src/BasicPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
VideoProperties,
} from './PlaybackArea'
import { Format } from './types'
import { SignalingOptions } from 'WebRtcVideo'

const DEFAULT_FORMAT = Format.JPEG

Expand All @@ -40,6 +41,8 @@ interface BasicPlayerProps {
* Activate automatic retries on RTSP errors.
*/
readonly autoRetry?: boolean
readonly signalingOptions?: SignalingOptions
readonly refresh?: number
}

export const BasicPlayer = forwardRef<PlayerNativeElement, BasicPlayerProps>(
Expand All @@ -52,6 +55,8 @@ export const BasicPlayer = forwardRef<PlayerNativeElement, BasicPlayerProps>(
autoRetry = false,
secure,
className,
signalingOptions,
refresh,
},
ref
) => {
Expand Down Expand Up @@ -145,22 +150,22 @@ export const BasicPlayer = forwardRef<PlayerNativeElement, BasicPlayerProps>(
* aspect ratio is carried over to the container, and the layers match the
* container size.
*/

return (
<MediaStreamPlayerContainer className={className}>
<Limiter ref={limiterRef}>
<Container aspectRatio={naturalAspectRatio}>
<Layer>
<PlaybackArea
forwardedRef={ref}
refresh={0}
refresh={refresh ?? 0}
play={play}
host={host}
format={format}
parameters={vapixParams}
onPlaying={onPlaying}
secure={secure}
autoRetry={autoRetry}
signalingOptions={signalingOptions}
/>
</Layer>
<Layer>
Expand Down

0 comments on commit a17e3cc

Please sign in to comment.