A browser-based JavaScript SDK for API.stream.
The Studio Kit provides your users with everything they need to produce professional-looking live streams to any popular platform (Youtube, Facebook, Twitch, etc.) or custom destination.
This is a simple and opinionated interface for API.stream services. It implements the API.stream SDK.
📖 Read the documentation
🔍 View the live demo
npm install @api.stream/studio-kit
Developers using the Studio Kit will have access to:
- Drop-in stream canvas
- Configurable drag-and-drop controls
- Built-in animations and transitions
- Support for custom stream layouts
- Support for custom themes and presets
- Support for webcam + screenshare for a host and any number of guests
- Simple state hooks designed for interplay with popular frontend frameworks
Users of a website implementing the Studio Kit will be able to:
- Create and manage multiple projects
- Invite guests to appear on stream
- Select from custom themes and layouts
- Set up a professional-looking stream in minutes
- Go live to multiple destinations at the same time
- Add cameras and screenshares and switch between layouts in real time
import * as StudioKit from '@api.stream/studio-kit'
// Initialize the SDK to receive a Studio object
const studio = await StudioKit.init()
// Authenticate the user and fetch their projects
const user = await studio.load(ACCESS_TOKEN)
Note: ACCESS_TOKEN should be granted as part of your user login flow.
Receive this token on the backend by using the API.stream SDK.
Example - Retrieve an access token
// Listen for events to update state and re-render the UI
studio.subscribe((event, payload) => {
handleEvent(event, payload) // Update some state
render(state) // Render some UI (e.g. projects list)
})
const handleEvent = (event, payload) => {
if (event === 'BroadcastStarted') {
setIsLive(true)
} else if (event === 'BroadcastStopped') {
setIsLive(false)
}
}
A project represents a single broadcast setup.
// ScenelessProject includes utilities to assist with simple stream setup
const { ScenelessProject } = SDK.Helpers
// Create a project with participant and overlay compatibility
const project = await ScenelessProject.create()
// Activate our desired project, initializing event listeners
await studio.Command.setActiveProject({ projectId: project.id })
// Wrap the project in an interface used to manage layout,
// add participant video feeds, and change overlays
const projectCommands = ScenelessProject.commands(project)
// Join the WebRTC room to begin sending and receiving video with guests
const room = await activeProject.joinRoom({ displayName })
Now that we have a WebRTC room, let's invite some guests to join our broadcast. The first step is to create a link for each guest to join with.
const baseUrl = 'https://yourwebsite.com/guest'
const guestLink = await studio.createGuestLink(baseUrl, {
projectId: project.id,
})
Note: We can also use the Studio.createGuestToken method
On the guest page, we can have the guest join the WebRTC room. Let's demonstrate how to accomplish this in the context of a React component.
import { init, Helpers, SDK } from '@api.stream/studio-kit'
const { useStudio, StudioProvider, useDevices } = Helpers.React
// In order to make use of the helpful useStudio hook, our component must be a child of StudioProvider
const GuestApp = ({ children }) => (
<StudioProvider>
{children}
</StudioProvider>
)
const GuestCompoent = () => {
const { studio, project, room, setStudio, setProject, setRoom, webcamId, microphoneId, setWebcamId, setMicrophoneId } = useStudio()
// Initialize studio
// Pass an empty array as deps to ensure that this effect runs only once.
// There are other, better ways to accomplish this,
// but for brevity in this example, we choose this way.
useEffect(() => {
init().then(setStudio)
}, [])
// Initialize project
useEffect(() => {
if (!studio) return
if (studio.initialProject) {
// If the SDK detects a token in the URL, it will return the project
// associated with it (e.g. guest view)
setProject(studio.initialProject)
} else {
setError('Invalid token')
}
}, [studio])
// Initialize room
useEffect(() => {
if (!project) return
project
.joinRoom({
displayName,
})
.then((room) => {
setJoining(false)
setRoom(room)
})
.catch((e) => {
setError(e.message)
})
}, [project])
// Gets all webcams and microphones for the guest, listens for changes to available devices
const devices = useDevices()
// As soon as we have our devices, the first available webcam and microphone will be shared across the WebRTC Room.
useEffect(() => {
if (!webcamId) setWebcamId(devices.webcams[0]?.deviceId)
}, [webcamId, devices])
useEffect(() => {
if (!microphoneId) setMicrophoneId(devices.microphones[0]?.deviceId)
}, [microphoneId, devices])
return (
<div>
{/* some react component here*/}
</div>
)
}
Now that the Guest has joined the room, let's return to the Host's page, and add the guest to our broadcast.
const Participants = () => {
const { room, projectCommands } = useStudio()
const [participants, setParticipants] = useState<SDK.Participant[]>([])
// Listen for room participants
useEffect(() => {
return room.useParticipants((participants) => {
setParticipants(participants)
// Prune non-existent guests from the project
// Only the host can do this
projectCommands.pruneParticipants()
})
}, [])
return (
<div>
{participants.map((p) => (
<div>
{/* Include some other components to, say, display the participant's camera feed */}
<WebcamToggle participant={p} />
</div>
))}
</div>
)
}
const WebcamToggle = ({ participant }) => {
const { id } = participant
const { projectCommands } = useStudio()
// Get the initial props in case the participant is on stream
const projectParticipant = useMemo(
() => projectCommands.getParticipantState(id, 'camera'),
[],
)
const [onStream, setOnStream] = useState(Boolean(projectParticipant))
const [isMuted, setIsMuted] = useState(projectParticipant?.isMuted ?? false)
const [volume, setVolume] = useState(projectParticipant?.volume ?? 1)
const [isShowcase, setIsShowcase] = useState(false)
// Monitor whether the participant has been removed from the stream
// from some other means (e.g. dragged off canvas by host)
useEffect(() => {
return projectCommands.useParticipantState(
id,
(x) => {
setOnStream(Boolean(x))
},
'camera',
)
}, [])
// Monitor the project's showcase to determine whether this
// participant/camera is active
useEffect(
() =>
projectCommands.useShowcase((showcase) => {
setIsShowcase(showcase.participantId === id && showcase.type === 'camera')
}),
[],
)
return (
<div>
<label>
<input
type="checkbox"
checked={onStream}
onChange={(e) => {
const checked = e.target.checked
if (checked) {
// Adds the participant's webcam and microphone to the broadcast
projectCommands.addParticipant(id, { isMuted, volume }, 'camera')
} else {
projectCommands.removeParticipant(id, 'camera')
}
setOnStream(checked)
}}
/>
On stream
</label>
<label style={{ opacity: onStream ? 1 : 0.7 }}>
<input
type="checkbox"
disabled={!onStream}
checked={isShowcase}
onChange={() => {
if (isShowcase) {
projectCommands.setShowcase(null)
} else {
projectCommands.setShowcase(id, 'camera')
}
}}
/>
Showcase
</label>
</div>
)
}
With a project, we are ready to start configuring a broadcast.
// Render the drop-in stream canvas into a DOM element of your choosing
const containerEl = document.querySelector('#some-container')
studio.render({
projectId: project.id, // The project to render
containerEl, // The element to render the preview into
dragAndDrop: true, // Include drag 'n' drop functionality
})
// Configure a destination for the active project
studio.Command.setDestination({
projectId: project.id,
rtmpUrl: 'rtmp://some-rtmp-url.com/',
rtmpKey: 'some_stream_key_123',
})
Once a destination is set, the project is ready to go live.
studio.Command.startBroadcast({
projectId: project.id,
})
API.stream relies heavily on modern JWT-based authentication flows. The backend authentication service issues JWTs which clients assert on subsequent API calls. Those JWTs include grants allowing clients to safely share projects with collaborators and guests.