updated readme and added layouts

This commit is contained in:
J Taylor 2021-06-28 14:38:25 +01:00
parent 2cbd8c7335
commit a7c6c5047b
5 changed files with 146 additions and 23 deletions

View File

@ -1,3 +1,41 @@
# Live Streaming
![Live Streaming](./image.png)
### Live example
**[See it in action here ➡️](https://dailyjs-live-streaming.vercel.app)**
---
## What does this demo do?
- Use [startLiveStreaming](https://docs.daily.co/reference#%EF%B8%8F-startlivestreaming) to send video and audio to specified RTMP endpoint
- Listen for stream started / stopped / error events
- Allows call owner to specify stream layout (grid, single participant or active speaker) and maximum cams
- Extends the basic call demo with a live streaming provider, tray button and modal
- Show a notification bubble at the top of the screen when live streaming is in progress
Please note: this demo is not currently mobile optimised
### Getting started
```
# set both DAILY_API_KEY and DAILY_DOMAIN
mv env.example .env.local
yarn
yarn workspace @dailyjs/live-streaming dev
```
## How does this example work?
In this example we extend the [basic call demo](../basic-call) with live streaming functionality.
We pass a custom tray object, a custom app object (wrapping the original in a new `LiveStreamingProvider`) and a custom modal.
Single live streaming is only available to call owners, you must create a token when joining the call (for simplicity, we have disabled the abiltiy to join the call as a guest.)
## Deploy your own on Vercel
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/daily-co/clone-flow?repository-url=https%3A%2F%2Fgithub.com%2Fdaily-demos%2Fexamples.git&env=DAILY_DOMAIN%2CDAILY_API_KEY&envDescription=Your%20Daily%20domain%20and%20API%20key%20can%20be%20found%20on%20your%20account%20dashboard&envLink=https%3A%2F%2Fdashboard.daily.co&project-name=daily-examples&repo-name=daily-examples)

View File

@ -1,21 +1,33 @@
import React, { useEffect, useState } from 'react';
import { Button } from '@dailyjs/shared/components/Button';
import { CardBody } from '@dailyjs/shared/components/Card';
import Field from '@dailyjs/shared/components/Field';
import { TextInput } from '@dailyjs/shared/components/Input';
import { TextInput, SelectInput } from '@dailyjs/shared/components/Input';
import Modal from '@dailyjs/shared/components/Modal';
import { Well } from '@dailyjs/shared/components/Well';
import { useCallState } from '@dailyjs/shared/contexts/CallProvider';
import { useParticipants } from '@dailyjs/shared/contexts/ParticipantsProvider';
import { useUIState } from '@dailyjs/shared/contexts/UIStateProvider';
import { useLiveStreaming } from '../../contexts/LiveStreamingProvider';
export const LIVE_STREAMING_MODAL = 'live-streaming';
const LAYOUTS = [
{ label: 'Grid (default)', value: 'default' },
{ label: 'Single participant', value: 'single-participant' },
{ label: 'Active participant', value: 'active-participant' },
];
export const LiveStreamingModal = () => {
const { callObject } = useCallState();
const { allParticipants } = useParticipants();
const { currentModals, closeModal } = useUIState();
const { isStreaming, streamError } = useLiveStreaming();
const [pending, setPending] = useState(false);
const [rtmpUrl, setRtmpUrl] = useState('');
const [layout, setLayout] = useState(0);
const [maxCams, setMaxCams] = useState(9);
const [participant, setParticipant] = useState(0);
useEffect(() => {
// Reset pending state whenever stream state changes
@ -24,7 +36,12 @@ export const LiveStreamingModal = () => {
function startLiveStream() {
setPending(true);
callObject.startLiveStreaming({ rtmpUrl });
const opts =
layout === 'single-participant'
? { session_id: participant.id }
: { max_cam_streams: maxCams };
callObject.startLiveStreaming({ rtmpUrl, preset: layout, ...opts });
}
function stopLiveStreaming() {
@ -37,32 +54,93 @@ export const LiveStreamingModal = () => {
title="Live stream"
isOpen={currentModals[LIVE_STREAMING_MODAL]}
onClose={() => closeModal(LIVE_STREAMING_MODAL)}
actions={[
<Button fullWidth variant="outline">
Close
</Button>,
!isStreaming ? (
<Button
fullWidth
disabled={!rtmpUrl || pending}
onClick={() => startLiveStream()}
>
{pending ? 'Starting stream...' : 'Start live streaming'}
</Button>
) : (
<Button
fullWidth
variant="warning"
onClick={() => stopLiveStreaming()}
>
Stop live streaming
</Button>
),
]}
>
{streamError && (
<Well variant="error">
Unable to start stream. Error message: {streamError}
</Well>
)}
<Field label="Enter RTMP endpoint">
<TextInput
type="text"
placeholder="RTMP URL"
required
onChange={(e) => setRtmpUrl(e.target.value)}
/>
</Field>
{!isStreaming ? (
<Button
disabled={!rtmpUrl || pending}
onClick={() => startLiveStream()}
>
{pending ? 'Starting stream...' : 'Start live streaming'}
</Button>
) : (
<Button variant="warning" onClick={() => stopLiveStreaming()}>
Stop live streaming
</Button>
)}
<CardBody>
<Field label="Layout">
<SelectInput
onChange={(e) => setLayout(Number(e.target.value))}
value={layout}
>
{LAYOUTS.map((l, i) => (
<option value={i} key={l.value}>
{l.label}
</option>
))}
</SelectInput>
</Field>
{layout !==
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
<Field label="Additional cameras">
<SelectInput
onChange={(e) => setMaxCams(Number(e.target.value))}
value={maxCams}
>
<option value={9}>9 cameras</option>
<option value={8}>8 cameras</option>
<option value={7}>7 cameras</option>
<option value={6}>6 cameras</option>
<option value={5}>5 cameras</option>
<option value={4}>4 cameras</option>
<option value={3}>3 cameras</option>
<option value={2}>2 cameras</option>
<option value={1}>1 camera</option>
</SelectInput>
</Field>
)}
{layout ===
LAYOUTS.findIndex((l) => l.value === 'single-participant') && (
<Field label="Select participant">
<SelectInput
onChange={(e) => setParticipant(e.target.value)}
value={participant}
>
{allParticipants.map((p) => (
<option value={p.id} key={p.id}>
{p.name}
</option>
))}
</SelectInput>
</Field>
)}
<Field label="Enter RTMP endpoint">
<TextInput
type="text"
placeholder="RTMP URL"
required
onChange={(e) => setRtmpUrl(e.target.value)}
/>
</Field>
</CardBody>
</Modal>
);
};

View File

@ -20,6 +20,7 @@ export const LiveStreamingProvider = ({ children }) => {
const handleStreamStarted = useCallback(() => {
console.log('📺 Live stream started');
setIsStreaming(true);
setStreamError(null);
setCustomCapsule({ variant: 'recording', label: 'Live streaming' });
}, [setCustomCapsule]);

View File

@ -117,6 +117,12 @@ export const GlobalStyle = () => (
padding: 2px 6px;
font-size: 0.875rem;
}
hr {
border: 0;
height: 1px;
background: var(--gray-light);
}
`}</style>
);

View File

@ -269,7 +269,7 @@ export const SelectInput = ({
SelectInput.propTypes = {
onChange: PropTypes.func,
children: PropTypes.node,
value: PropTypes.number,
value: PropTypes.any,
variant: PropTypes.string,
label: PropTypes.string,
};