kasunk99-livestream-core
v0.3.58
Published
Reusable livestream viewer/host module for React Native (Expo) — mediasoup + Socket.IO
Readme
@livestream/core
Reusable livestream viewer (and later host) module for React Native / Expo. Talks to a mediasoup + Socket.IO livestream server.
Peer dependencies
Install in the host app:
react,react-nativereact-native-webrtc(required for video; use a dev build, not Expo Go)mediasoup-clientsocket.io-client
Setup
- Configure before using any component or hook:
import { setLivestreamConfig } from '@livestream/core';
setLivestreamConfig({
serverHttpUrl: 'http://localhost:3000', // or your server URL
getDisplayName: () => 'Viewer', // optional, for chat/room display
});- Use the feed or hooks:
import { LiveStreamFeed } from '@livestream/core';
<LiveStreamFeed />Or use useLiveStreams() and useViewerSocket(roomId) for custom UIs.
Components
LiveStreamFeed
| Prop | Type | Description |
|---|---|---|
| isScreenFocused | boolean | Only the on-screen stream connects while this is true (default true). |
| onLeave | () => void | Shows a close (×) button that calls this. |
| onJoinStatusChange | (joined: boolean) => void | Join status of the on-screen stream. |
| onActiveStreamChange | (stream: LiveStreamInfo \| null) => void | Fires when the on-screen stream changes (by roomId), including the first stream after load, and with null when there is no stream. It doesn't fire again for a list refetch of the same room, and it's safe to pass an inline function. |
| renderViewerActions | RenderViewerActions | Extra buttons in the viewer's bottom bar. Forwarded to every LiveStreamViewerItem. |
| safeAreaInsets | { top: number; bottom: number } | For a full-screen (edge-to-edge) feed: keeps the top bar and bottom controls clear of the status bar and navigation bar. Memoize it. Without it, the original fixed spacing is used. |
| onMinimize | () => void | Shows a minimize button to the left of ×, e.g. to enter picture-in-picture. |
| onActiveStreamEnded | (stream: LiveStreamInfo) => void | Fires once when the host of the on-screen stream ends it. |
The viewer's chat list scrolls inside the vertical feed: while a finger is on a chat that has more to scroll, the feed pauses paging (a short chat still lets the swipe change streams).
LiveStreamViewerItem
Takes the same renderViewerActions, safeAreaInsets and onMinimize props, plus onStreamEnded?: () => void (the feed passes it for the on-screen item only). renderViewerActions It is called with { stream, isActive, joined, streamEnded } and its result is rendered between the emoji (☺) and like (♥) buttons, only while the pill bar is showing (not while typing or while the emoji panel is open). Return null to render nothing. Size your own button (about 40×40 matches its neighbours), and keep the function stable with useCallback.
const renderViewerActions = useCallback<RenderViewerActions>(
({ stream, joined, streamEnded }) =>
joined && !streamEnded && stream.hostUserId ? (
<MyTipButton onPress={() => openTip(stream.hostUserId!)} />
) : null,
[openTip],
);
<LiveStreamFeed
onActiveStreamChange={(stream) => setActiveRoomId(stream?.roomId ?? null)}
renderViewerActions={renderViewerActions}
/>Tip spotlight
When a viewer tips a stream, everyone in the room (host included) sees the tipper's round avatar in the middle of the screen with an animation, their name and "sent N coins", then it fades out. The server also adds a highlighted chat line (kind: 'tip').
- Announce: after the tip succeeds in your app, call
announceTip(amount, avatarUrl, referenceId)from therenderViewerActionscontext. It emitstip-spotlighton that stream's socket. The server checks the amount, rate-limits (1 per 3 s per viewer), ignores a repeatedreferenceId, and uses its own record of the tipper's name. - Viewer:
LiveStreamViewerItemshows the spotlight on the on-screen stream.onTipSpotlight(spotlight)(on the feed or item) fires when one starts — use it to play a sound. - Host UI:
useHostSocket()returnstipSpotlightsanddismissTipSpotlight(id). Render<LiveTipSpotlightOverlay queue={tipSpotlights} onDone={dismissTipSpotlight} onShow={…} />.HostChatMessage.kind === 'tip'marks tip lines, andLiveChatMessagetakeshighlightfor them. - Socket event:
tip-spotlight→{ id, peerId, displayName, avatarUrl, amount, timestamp }(LiveTipSpotlight).
Exports
- Config:
setLivestreamConfig,getBaseUrl,getSignalingWsUrl,getDisplayName - Services:
fetchActiveStreams,ensureMediasoupGlobals - Hooks:
useViewerSocket,useLiveStreams - Components:
LiveStreamFeed,LiveStreamViewerItem,LiveTipSpotlightOverlay,LiveChatMessage(a chat bubble; long messages collapse to 3 lines with a "Show more" toggle — used by the viewer and reusable in a host UI) - Constants:
LIVE_CHAT_MAX_LENGTH(150) — max characters in one chat message; the viewer's chat input enforces it, and a host UI can use it to match - Types:
LiveStreamInfo,ProducerInfo,JoinRoomResult,RoomState,HostMediaState,LiveStreamViewerActionsContext,RenderViewerActions,LiveTipSpotlight,AnnounceTip
Server contract
- HTTP:
GET {serverHttpUrl}/api/live-streams→{ streams: { roomId, viewerCount, producerCount, title?, hostDisplayName?, hostUserId?, hostAvatarUrl? }[] } - WebSocket: Socket.IO at same origin (ws/https). Events:
join-room,create-webrtc-transport,connect-transport,produce,consume,get-producers,new-producer,room-updated, etc.
