@stream-io/video-react-sdk 1.17.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,18 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { Call } from '@stream-io/video-client';
3
- import { useStreamVideoClient } from '@stream-io/video-react-bindings';
2
+ import { Call, CallingState } from '@stream-io/video-client';
4
3
  import {
4
+ useCall,
5
+ useCallStateHooks,
6
+ useStreamVideoClient,
7
+ } from '@stream-io/video-react-bindings';
8
+ import {
9
+ BackstageLayout,
10
+ BackstageLayoutProps,
5
11
  LivestreamLayout,
6
12
  LivestreamLayoutProps,
7
13
  StreamCall,
8
14
  } from '../../core';
15
+ import { useEffectEvent } from '../../hooks/useEffectEvent';
9
16
 
10
17
  export type LivestreamPlayerProps = {
11
18
  /**
@@ -16,22 +23,45 @@ export type LivestreamPlayerProps = {
16
23
  * The call ID.
17
24
  */
18
25
  callId: string;
26
+ /**
27
+ * Determines when the viewer joins the call.
28
+ *
29
+ * `"asap"` behavior means joining the call as soon as it is possible
30
+ * (either the `join_ahead_time_seconds` setting allows it, or the user
31
+ * has a the capability to join backstage).
32
+ *
33
+ * `"live"` behavior means joining the call when it goes live.
34
+ *
35
+ * @default "asap"
36
+ */
37
+ joinBehavior?: 'asap' | 'live';
19
38
  /**
20
39
  * The props for the {@link LivestreamLayout} component.
21
40
  */
22
41
  layoutProps?: LivestreamLayoutProps;
42
+ /**
43
+ * The props for the {@link BackstageLayout} component.
44
+ */
45
+ backstageProps?: BackstageLayoutProps;
46
+ /**
47
+ * Callback to handle errors while fetching or joining livestream.
48
+ */
49
+ onError?: (error: any) => void;
23
50
  };
24
51
 
25
52
  export const LivestreamPlayer = (props: LivestreamPlayerProps) => {
26
- const { callType, callId, layoutProps } = props;
53
+ const { callType, callId, ...restProps } = props;
27
54
  const client = useStreamVideoClient();
28
55
  const [call, setCall] = useState<Call>();
56
+ const onError = useEffectEvent(props.onError);
57
+
29
58
  useEffect(() => {
30
59
  if (!client) return;
31
60
  const myCall = client.call(callType, callId);
32
61
  setCall(myCall);
33
- myCall.join().catch((e) => {
34
- console.error('Failed to join call', e);
62
+ myCall.get().catch((e) => {
63
+ console.error('Failed to fetch call', e);
64
+ onError(e);
35
65
  });
36
66
  return () => {
37
67
  myCall.leave().catch((e) => {
@@ -39,13 +69,94 @@ export const LivestreamPlayer = (props: LivestreamPlayerProps) => {
39
69
  });
40
70
  setCall(undefined);
41
71
  };
42
- }, [callId, callType, client]);
72
+ }, [callId, callType, client, onError]);
43
73
 
44
- if (!call) return null;
74
+ if (!call) {
75
+ return null;
76
+ }
45
77
 
46
78
  return (
47
79
  <StreamCall call={call}>
48
- <LivestreamLayout {...layoutProps} />
80
+ <LivestreamCall {...restProps} />
49
81
  </StreamCall>
50
82
  );
51
83
  };
84
+
85
+ const LivestreamCall = (props: {
86
+ joinBehavior?: 'asap' | 'live';
87
+ layoutProps?: LivestreamLayoutProps;
88
+ backstageProps?: BackstageLayoutProps;
89
+ onError?: (error: any) => void;
90
+ }) => {
91
+ const call = useLivestreamCall(props);
92
+ const { useIsCallLive } = useCallStateHooks();
93
+ const isLive = useIsCallLive();
94
+
95
+ if (!call) return null;
96
+
97
+ if (isLive) {
98
+ return <LivestreamLayout {...props.layoutProps} />;
99
+ }
100
+
101
+ return <BackstageLayout {...props.backstageProps} />;
102
+ };
103
+
104
+ const useLivestreamCall = (props: {
105
+ joinBehavior?: 'asap' | 'live';
106
+ onError?: (error: any) => void;
107
+ }) => {
108
+ const call = useCall();
109
+ const { useIsCallLive, useOwnCapabilities } = useCallStateHooks();
110
+ const canJoinLive = useIsCallLive();
111
+ const canJoinEarly = useCanJoinEearly();
112
+ const canJoinBackstage =
113
+ useOwnCapabilities()?.includes('join-backstage') ?? false;
114
+ const canJoinAsap = canJoinLive || canJoinEarly || canJoinBackstage;
115
+ const joinBehavior = props.joinBehavior ?? 'asap';
116
+ const canJoin =
117
+ (joinBehavior === 'asap' && canJoinAsap) ||
118
+ (joinBehavior === 'live' && canJoinLive);
119
+ const onError = useEffectEvent(props.onError);
120
+
121
+ useEffect(() => {
122
+ if (call && call.state.callingState === CallingState.IDLE && canJoin) {
123
+ call.join().catch((e) => {
124
+ console.error('Failed to join call', e);
125
+ onError(e);
126
+ });
127
+ }
128
+ }, [call, canJoin, onError]);
129
+
130
+ return call;
131
+ };
132
+
133
+ const useCanJoinEearly = () => {
134
+ const { useCallStartsAt, useCallSettings } = useCallStateHooks();
135
+ const startsAt = useCallStartsAt();
136
+ const settings = useCallSettings();
137
+ const joinAheadTimeSeconds = settings?.backstage.join_ahead_time_seconds;
138
+ const [canJoinEarly, setCanJoinEearly] = useState(() =>
139
+ checkCanJoinEarly(startsAt, joinAheadTimeSeconds),
140
+ );
141
+
142
+ useEffect(() => {
143
+ if (!canJoinEarly) {
144
+ const handle = setInterval(() => {
145
+ setCanJoinEearly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
146
+ }, 1000);
147
+
148
+ return () => clearInterval(handle);
149
+ }
150
+ }, [canJoinEarly, startsAt, joinAheadTimeSeconds]);
151
+ };
152
+
153
+ const checkCanJoinEarly = (
154
+ startsAt: Date | undefined,
155
+ joinAheadTimeSeconds: number | undefined,
156
+ ) => {
157
+ if (!startsAt) {
158
+ return false;
159
+ }
160
+
161
+ return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
162
+ };