Skip to content

Commit e03f8a3

Browse files
authored
feat: add handler for HLS buffer-eos event (#1690)
Adds HLS.BUFFER_EOS event handling to our live streaming sample app. ref: https://getstream.slack.com/archives/C040262MY9K/p1739873779558179 ref: GetStream/docs-content#196
1 parent 3095f86 commit e03f8a3

File tree

10 files changed

+315
-259
lines changed

10 files changed

+315
-259
lines changed

sample-apps/react/audio-rooms/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@stream-io/video-react-sdk": "workspace:^",
1414
"react": "^18.3.1",
1515
"react-dom": "^18.3.1",
16-
"react-router-dom": "^6.17.0"
16+
"react-router-dom": "^6.29.0"
1717
},
1818
"devDependencies": {
1919
"@types/react": "^18.3.2",

sample-apps/react/cookbook-participant-list/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12-
"@emotion/react": "^11.11.1",
13-
"@emotion/styled": "^11.11.0",
14-
"@mui/material": "^5.14.14",
12+
"@emotion/react": "^11.14.0",
13+
"@emotion/styled": "^11.14.0",
14+
"@mui/material": "^6.4.5",
1515
"@stream-io/video-react-sdk": "workspace:^",
1616
"@stream-io/video-styling": "workspace:^",
1717
"react": "^18.3.1",

sample-apps/react/egress-composite/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"test:e2e": "playwright test"
1212
},
1313
"dependencies": {
14-
"@emotion/css": "^11.11.2",
14+
"@emotion/css": "^11.13.5",
1515
"@sentry/react": "^8.30.0",
1616
"@stream-io/video-react-sdk": "workspace:^",
1717
"clsx": "^2.0.0",

sample-apps/react/livestream-app/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111
"preview": "vite preview"
1212
},
1313
"dependencies": {
14-
"@emotion/react": "^11.11.1",
15-
"@emotion/styled": "^11.11.0",
16-
"@mui/material": "^5.14.14",
14+
"@emotion/react": "^11.14.0",
15+
"@emotion/styled": "^11.14.0",
16+
"@mui/material": "^6.4.5",
1717
"@stream-io/video-react-sdk": "workspace:^",
1818
"clsx": "^2.0.0",
19-
"hls.js": "^1.4.3",
19+
"hls.js": "^1.5.20",
2020
"react": "^18.3.1",
2121
"react-dom": "^18.3.1",
22-
"react-router-dom": "^6.17.0"
22+
"react-router-dom": "^6.29.0"
2323
},
2424
"devDependencies": {
2525
"@types/react": "^18.3.2",

sample-apps/react/livestream-app/src/hosts/ui/BackstageControls.tsx

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ export const BackstageControls = () => {
1919
<ToggleVideoPublishingButton caption="" />
2020
<CancelCallButton />
2121
<ToggleLivestreamButton call={call} />
22+
<ToggleHLSBroadcastButton call={call} />
2223
</div>
2324
);
2425
};
2526

2627
const ToggleLivestreamButton = (props: { call: Call }) => {
2728
const { call } = props;
28-
const { useIsCallHLSBroadcastingInProgress, useCallIngress } =
29-
useCallStateHooks();
29+
const { useCallIngress, useIsCallLive } = useCallStateHooks();
3030
const ingress = useCallIngress();
31-
const isBroadcasting = useIsCallHLSBroadcastingInProgress();
31+
const isBroadcasting = useIsCallLive();
3232
const [isAwaitingResponse, setIsAwaitingResponse] = useState(false);
3333
useEffect(() => {
3434
setIsAwaitingResponse((isAwaiting) => {
@@ -71,6 +71,54 @@ const ToggleLivestreamButton = (props: { call: Call }) => {
7171
);
7272
};
7373

74+
const ToggleHLSBroadcastButton = (props: { call: Call }) => {
75+
const { call } = props;
76+
const { useIsCallHLSBroadcastingInProgress, useCallIngress } =
77+
useCallStateHooks();
78+
const ingress = useCallIngress();
79+
const isBroadcasting = useIsCallHLSBroadcastingInProgress();
80+
const [isAwaitingResponse, setIsAwaitingResponse] = useState(false);
81+
useEffect(() => {
82+
setIsAwaitingResponse((isAwaiting) => {
83+
if (isAwaiting) return false;
84+
return isAwaiting;
85+
});
86+
}, [isBroadcasting]);
87+
useEffect(() => {
88+
if (!ingress) return;
89+
console.log(`RTMP address: ${ingress.rtmp.address}`);
90+
}, [ingress]);
91+
return (
92+
<button
93+
type="button"
94+
className={`livestream-toggle-button ${
95+
isBroadcasting ? 'broadcasting' : ''
96+
}`}
97+
onClick={async () => {
98+
if (isBroadcasting) {
99+
call.stopHLS().catch((err) => {
100+
console.error('Error stopping HLS livestream', err);
101+
});
102+
} else {
103+
call.startHLS().catch((err) => {
104+
console.error('Error stopping HLS livestream', err);
105+
});
106+
}
107+
setIsAwaitingResponse(true);
108+
}}
109+
>
110+
{isAwaitingResponse ? (
111+
<LoadingIndicator />
112+
) : isBroadcasting ? (
113+
<EndBroadcastIcon />
114+
) : (
115+
<StartBroadcastIcon />
116+
)}
117+
<span>{isBroadcasting ? 'End HLS' : 'Start HLS'}</span>
118+
</button>
119+
);
120+
};
121+
74122
const StartBroadcastIcon = () => {
75123
return (
76124
<svg

sample-apps/react/livestream-app/src/viewers/HLSLivestream.scss

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
.hls-video-player {
22
width: 100%;
3-
height: 100%;
3+
height: auto;
44
}
55

66
.video-player-container--wrapper {
77
width: 100%;
88
height: 100%;
9-
display: flex;
10-
align-items: center;
11-
justify-content: center;
129
}
1310

1411
.video-player-container {
1512
display: flex;
13+
flex-direction: column;
1614
height: 100%;
1715
align-items: center;
16+
justify-content: center;
1817
}
1918

2019
.loading-indicator {
@@ -30,3 +29,9 @@
3029
justify-content: center;
3130
gap: 5px;
3231
}
32+
33+
.hls-stream-ended {
34+
flex-direction: row;
35+
justify-content: center;
36+
padding-bottom: 20px;
37+
}

sample-apps/react/livestream-app/src/viewers/HLSLivestream.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,46 @@ import { ViewerControls } from './ui/ViewerControls';
1111
import { Lobby } from './ui/Lobby';
1212

1313
export const HLSLivestreamUI = () => {
14-
const { useIsCallHLSBroadcastingInProgress, useCallEgress } =
14+
const { useIsCallHLSBroadcastingInProgress, useCallEgress, useIsCallLive } =
1515
useCallStateHooks();
1616
const isBroadcasting = useIsCallHLSBroadcastingInProgress();
17+
const isLive = useIsCallLive();
1718
const egress = useCallEgress();
1819
const hls = useMemo(() => new HLS(), []);
1920

2021
const [autoJoin, setAutoJoin] = useState(false);
2122
const [videoRef, setVideoRef] = useState<HTMLVideoElement | null>(null);
2223
const [isPlaying, setIsPlaying] = useState(false);
24+
const [hlsStreamEnded, setHlsStreamEnded] = useState(false);
25+
const playlist_url = egress?.hls?.playlist_url;
2326
useEffect(() => {
24-
if (!videoRef) return;
27+
if (!videoRef || !playlist_url) return;
28+
2529
let timeoutId: NodeJS.Timeout;
26-
if (autoJoin && isBroadcasting && egress && egress.hls) {
27-
const { playlist_url } = egress.hls;
30+
if (autoJoin && isBroadcasting) {
2831
hls.on(HLS.Events.ERROR, (e, data) => {
2932
console.error('HLS error, attempting to recover', e, data);
3033

3134
setIsPlaying(false);
35+
clearTimeout(timeoutId);
3236
timeoutId = setTimeout(() => {
3337
hls.loadSource(playlist_url);
3438
}, 1000);
3539
});
3640
hls.on(HLS.Events.LEVELS_UPDATED, (e, data) => {
3741
console.error('HLS levels updated', e, data);
3842
});
43+
hls.on(HLS.Events.BUFFER_EOS, (e, data) => {
44+
console.error('HLS buffer eos', e, data);
45+
setHlsStreamEnded(true);
46+
});
3947
hls.loadSource(playlist_url);
4048
hls.attachMedia(videoRef);
4149
}
4250
return () => {
4351
clearTimeout(timeoutId);
4452
};
45-
}, [autoJoin, hls, isBroadcasting, egress, videoRef]);
53+
}, [autoJoin, hls, isBroadcasting, videoRef, playlist_url]);
4654

4755
useEffect(() => {
4856
if (!videoRef) return;
@@ -53,11 +61,11 @@ export const HLSLivestreamUI = () => {
5361
};
5462
}, [videoRef]);
5563

56-
if (!isBroadcasting || !autoJoin) {
64+
if (!hlsStreamEnded && (!isLive || !autoJoin)) {
5765
return (
5866
<Lobby
5967
autoJoin={autoJoin}
60-
isStreaming={isBroadcasting}
68+
isStreaming={isLive}
6169
setAutoJoin={setAutoJoin}
6270
/>
6371
);
@@ -71,6 +79,9 @@ export const HLSLivestreamUI = () => {
7179
)}
7280
<div className="video-player-container--wrapper">
7381
<div className="video-player-container">
82+
{hlsStreamEnded && (
83+
<div className="hls-stream-ended">The HLS stream has ended.</div>
84+
)}
7485
<video
7586
className="hls-video-player"
7687
autoPlay

sample-apps/react/messenger-clone/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
"scripts:generate-url-params": "node scripts/generateUrlParams.js"
1313
},
1414
"dependencies": {
15-
"@mui/icons-material": "^5.14.14",
15+
"@mui/icons-material": "^6.4.5",
1616
"@stream-io/video-react-sdk": "workspace:^",
1717
"clsx": "^2.0.0",
1818
"dayjs": "^1.11.6",
1919
"immer": "^9.0.16",
2020
"nanoid": "^5.0.4",
2121
"react": "^18.3.1",
2222
"react-dom": "^18.3.1",
23-
"react-router-dom": "^6.17.0",
23+
"react-router-dom": "^6.29.0",
2424
"sass": "^1.69.5",
2525
"stream-chat": "^8.45.1",
2626
"stream-chat-react": "12.6.0"

sample-apps/react/zoom-clone/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"nanoid": "^5.0.4",
1717
"react": "^18.3.1",
1818
"react-dom": "^18.3.1",
19-
"react-router-dom": "^6.17.0",
19+
"react-router-dom": "^6.29.0",
2020
"stream-chat": "^8.45.1",
2121
"stream-chat-react": "12.6.0"
2222
},

0 commit comments

Comments
 (0)