@stream-io/video-react-native-sdk 1.13.3 → 1.14.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.
- package/CHANGELOG.md +15 -0
- package/android/gradle.properties +1 -1
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.js +23 -29
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +187 -29
- package/dist/commonjs/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js +1 -1
- package/dist/commonjs/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js +111 -0
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -0
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamPlayer.js +5 -6
- package/dist/commonjs/components/Livestream/LivestreamPlayer/LivestreamPlayer.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamTopView/DurationBadge.js +32 -28
- package/dist/commonjs/components/Livestream/LivestreamTopView/DurationBadge.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamTopView/FollowerCount.js +36 -36
- package/dist/commonjs/components/Livestream/LivestreamTopView/FollowerCount.js.map +1 -1
- package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js +21 -15
- package/dist/commonjs/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js +70 -4
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js +143 -0
- package/dist/commonjs/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -0
- package/dist/commonjs/icons/LivestreamControls.js +73 -0
- package/dist/commonjs/icons/LivestreamControls.js.map +1 -0
- package/dist/commonjs/icons/Maximize.js +52 -0
- package/dist/commonjs/icons/Maximize.js.map +1 -0
- package/dist/commonjs/icons/index.js +11 -0
- package/dist/commonjs/icons/index.js.map +1 -1
- package/dist/commonjs/index.js +12 -0
- package/dist/commonjs/index.js.map +1 -1
- package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js +75 -0
- package/dist/commonjs/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -0
- package/dist/commonjs/providers/NoiseCancellation/index.js +17 -0
- package/dist/commonjs/providers/NoiseCancellation/index.js.map +1 -0
- package/dist/commonjs/providers/NoiseCancellation/lib.js +34 -0
- package/dist/commonjs/providers/NoiseCancellation/lib.js.map +1 -0
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.js +27 -33
- package/dist/module/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js +187 -30
- package/dist/module/components/Livestream/LivestreamControls/ViewerLivestreamControls.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js +1 -1
- package/dist/module/components/Livestream/LivestreamLayout/LivestreamLayout.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js +104 -0
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamEnded.js.map +1 -0
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamPlayer.js +5 -6
- package/dist/module/components/Livestream/LivestreamPlayer/LivestreamPlayer.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamTopView/DurationBadge.js +33 -29
- package/dist/module/components/Livestream/LivestreamTopView/DurationBadge.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamTopView/FollowerCount.js +35 -35
- package/dist/module/components/Livestream/LivestreamTopView/FollowerCount.js.map +1 -1
- package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js +20 -14
- package/dist/module/components/Livestream/LivestreamTopView/LiveIndicator.js.map +1 -1
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js +73 -7
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLivestream.js.map +1 -1
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js +136 -0
- package/dist/module/components/Livestream/ViewerLivestream/ViewerLobby.js.map +1 -0
- package/dist/module/icons/LivestreamControls.js +62 -0
- package/dist/module/icons/LivestreamControls.js.map +1 -0
- package/dist/module/icons/Maximize.js +43 -0
- package/dist/module/icons/Maximize.js.map +1 -0
- package/dist/module/icons/index.js +1 -0
- package/dist/module/icons/index.js.map +1 -1
- package/dist/module/index.js +1 -0
- package/dist/module/index.js.map +1 -1
- package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js +67 -0
- package/dist/module/providers/NoiseCancellation/NoiseCancellationProvider.js.map +1 -0
- package/dist/module/providers/NoiseCancellation/index.js +2 -0
- package/dist/module/providers/NoiseCancellation/index.js.map +1 -0
- package/dist/module/providers/NoiseCancellation/lib.js +26 -0
- package/dist/module/providers/NoiseCancellation/lib.js.map +1 -0
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamControls/ViewerLivestreamControls.d.ts +7 -0
- package/dist/typescript/components/Livestream/LivestreamControls/ViewerLivestreamControls.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamEnded.d.ts +3 -0
- package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamEnded.d.ts.map +1 -0
- package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamPlayer.d.ts +13 -1
- package/dist/typescript/components/Livestream/LivestreamPlayer/LivestreamPlayer.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamTopView/DurationBadge.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamTopView/FollowerCount.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/LivestreamTopView/LiveIndicator.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLivestream.d.ts +9 -1
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLivestream.d.ts.map +1 -1
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLobby.d.ts +8 -0
- package/dist/typescript/components/Livestream/ViewerLivestream/ViewerLobby.d.ts.map +1 -0
- package/dist/typescript/icons/LivestreamControls.d.ts +12 -0
- package/dist/typescript/icons/LivestreamControls.d.ts.map +1 -0
- package/dist/typescript/icons/Maximize.d.ts +10 -0
- package/dist/typescript/icons/Maximize.d.ts.map +1 -0
- package/dist/typescript/icons/index.d.ts +1 -0
- package/dist/typescript/icons/index.d.ts.map +1 -1
- package/dist/typescript/index.d.ts +1 -0
- package/dist/typescript/index.d.ts.map +1 -1
- package/dist/typescript/providers/NoiseCancellation/NoiseCancellationProvider.d.ts +34 -0
- package/dist/typescript/providers/NoiseCancellation/NoiseCancellationProvider.d.ts.map +1 -0
- package/dist/typescript/providers/NoiseCancellation/index.d.ts +2 -0
- package/dist/typescript/providers/NoiseCancellation/index.d.ts.map +1 -0
- package/dist/typescript/providers/NoiseCancellation/lib.d.ts +8 -0
- package/dist/typescript/providers/NoiseCancellation/lib.d.ts.map +1 -0
- package/dist/typescript/version.d.ts +1 -1
- package/expo-config-plugin/dist/index.js +2 -0
- package/expo-config-plugin/dist/withAndroidPermissions.js +1 -0
- package/expo-config-plugin/dist/withAppDelegate.js +26 -7
- package/expo-config-plugin/dist/withMainApplication.js +24 -0
- package/package.json +10 -5
- package/src/components/Livestream/LivestreamControls/ViewerLeaveStreamButton.tsx +30 -48
- package/src/components/Livestream/LivestreamControls/ViewerLivestreamControls.tsx +260 -43
- package/src/components/Livestream/LivestreamLayout/LivestreamLayout.tsx +1 -1
- package/src/components/Livestream/LivestreamPlayer/LivestreamEnded.tsx +130 -0
- package/src/components/Livestream/LivestreamPlayer/LivestreamPlayer.tsx +15 -5
- package/src/components/Livestream/LivestreamTopView/DurationBadge.tsx +35 -38
- package/src/components/Livestream/LivestreamTopView/FollowerCount.tsx +40 -47
- package/src/components/Livestream/LivestreamTopView/LiveIndicator.tsx +22 -14
- package/src/components/Livestream/ViewerLivestream/ViewerLivestream.tsx +107 -10
- package/src/components/Livestream/ViewerLivestream/ViewerLobby.tsx +171 -0
- package/src/icons/LivestreamControls.tsx +51 -0
- package/src/icons/Maximize.tsx +48 -0
- package/src/icons/index.tsx +1 -0
- package/src/index.ts +1 -0
- package/src/providers/NoiseCancellation/NoiseCancellationProvider.tsx +147 -0
- package/src/providers/NoiseCancellation/index.ts +1 -0
- package/src/providers/NoiseCancellation/lib.ts +37 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useCall, useI18n } from '@stream-io/video-react-bindings';
|
|
3
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
4
|
+
import { useTheme } from '../../../contexts';
|
|
5
|
+
import { ListRecordingsResponse } from '@stream-io/video-client';
|
|
6
|
+
import {
|
|
7
|
+
FlatList,
|
|
8
|
+
Pressable,
|
|
9
|
+
View,
|
|
10
|
+
Text,
|
|
11
|
+
StyleSheet,
|
|
12
|
+
Linking,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
|
|
15
|
+
export const CallEndedView = () => {
|
|
16
|
+
const { t } = useI18n();
|
|
17
|
+
const call = useCall();
|
|
18
|
+
const [recordingsResponse, setRecordingsResponse] = useState<
|
|
19
|
+
ListRecordingsResponse | undefined
|
|
20
|
+
>(undefined);
|
|
21
|
+
|
|
22
|
+
const styles = useStyles();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let isCanceled = false;
|
|
26
|
+
const fetchRecordings = async () => {
|
|
27
|
+
if (recordingsResponse == null) {
|
|
28
|
+
try {
|
|
29
|
+
const callRecordingsResponse = await call?.queryRecordings();
|
|
30
|
+
if (!isCanceled) {
|
|
31
|
+
setRecordingsResponse(callRecordingsResponse);
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log('Error fetching recordings:', error);
|
|
35
|
+
if (!isCanceled) {
|
|
36
|
+
setRecordingsResponse(undefined);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
fetchRecordings();
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
isCanceled = true;
|
|
46
|
+
};
|
|
47
|
+
}, [call, recordingsResponse]);
|
|
48
|
+
|
|
49
|
+
const openUrl = (url: string) => {
|
|
50
|
+
Linking.canOpenURL(url).then((supported) => {
|
|
51
|
+
if (supported) {
|
|
52
|
+
Linking.openURL(url);
|
|
53
|
+
} else {
|
|
54
|
+
console.log('Cannot open URL:', url);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const showRecordings =
|
|
60
|
+
recordingsResponse && recordingsResponse.recordings.length > 0;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<View style={styles.container}>
|
|
64
|
+
<Text style={styles.title}>{t('The livestream has ended.')}</Text>
|
|
65
|
+
|
|
66
|
+
{showRecordings && (
|
|
67
|
+
<>
|
|
68
|
+
<Text style={styles.subtitle}>{t('Watch recordings:')}</Text>
|
|
69
|
+
<View style={styles.recordingsContainer}>
|
|
70
|
+
<FlatList
|
|
71
|
+
data={recordingsResponse.recordings}
|
|
72
|
+
keyExtractor={(item) => item.session_id}
|
|
73
|
+
renderItem={({ item }) => (
|
|
74
|
+
<Pressable
|
|
75
|
+
style={styles.recordingButton}
|
|
76
|
+
onPress={() => openUrl(item.url)}
|
|
77
|
+
>
|
|
78
|
+
<Text style={styles.recordingText}>
|
|
79
|
+
{item.url.substring(0, 70)}...
|
|
80
|
+
</Text>
|
|
81
|
+
</Pressable>
|
|
82
|
+
)}
|
|
83
|
+
/>
|
|
84
|
+
</View>
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
</View>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const useStyles = () => {
|
|
92
|
+
const { theme } = useTheme();
|
|
93
|
+
return useMemo(
|
|
94
|
+
() =>
|
|
95
|
+
StyleSheet.create({
|
|
96
|
+
container: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
backgroundColor: theme.colors.sheetPrimary,
|
|
99
|
+
justifyContent: 'center',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
padding: theme.variants.spacingSizes.md,
|
|
102
|
+
},
|
|
103
|
+
title: {
|
|
104
|
+
fontSize: theme.variants.fontSizes.lg,
|
|
105
|
+
marginBottom: theme.variants.spacingSizes.md,
|
|
106
|
+
color: theme.colors.textPrimary,
|
|
107
|
+
fontWeight: 'bold',
|
|
108
|
+
},
|
|
109
|
+
subtitle: {
|
|
110
|
+
fontSize: theme.variants.fontSizes.md,
|
|
111
|
+
marginBottom: theme.variants.spacingSizes.md,
|
|
112
|
+
color: theme.colors.textPrimary,
|
|
113
|
+
fontWeight: 'bold',
|
|
114
|
+
},
|
|
115
|
+
recordingButton: {
|
|
116
|
+
padding: theme.variants.spacingSizes.sm,
|
|
117
|
+
width: '100%',
|
|
118
|
+
},
|
|
119
|
+
recordingText: {
|
|
120
|
+
color: theme.colors.textSecondary,
|
|
121
|
+
fontSize: theme.variants.fontSizes.md,
|
|
122
|
+
},
|
|
123
|
+
recordingsContainer: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
[theme],
|
|
129
|
+
);
|
|
130
|
+
};
|
|
@@ -21,12 +21,26 @@ export type LivestreamPlayerProps = {
|
|
|
21
21
|
* **Default** [ViewerLivestream](https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/src/components/Livestream/ViewerLivestream/ViewerLivestream.tsx)
|
|
22
22
|
*/
|
|
23
23
|
ViewerLivestream?: React.ComponentType<ViewerLivestreamProps>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Determines when the viewer joins the call.
|
|
27
|
+
*
|
|
28
|
+
* `"asap"` behavior means joining the call as soon as it is possible
|
|
29
|
+
* (either the `join_ahead_time_seconds` setting allows it, or the user
|
|
30
|
+
* has a the capability to join backstage).
|
|
31
|
+
*
|
|
32
|
+
* `"live"` behavior means joining the call when it goes live.
|
|
33
|
+
*
|
|
34
|
+
* @default "asap"
|
|
35
|
+
*/
|
|
36
|
+
joinBehavior?: 'asap' | 'live';
|
|
24
37
|
};
|
|
25
38
|
|
|
26
39
|
export const LivestreamPlayer = ({
|
|
27
40
|
callType,
|
|
28
41
|
callId,
|
|
29
42
|
ViewerLivestream = DefaultViewerLivestream,
|
|
43
|
+
joinBehavior = 'asap',
|
|
30
44
|
}: LivestreamPlayerProps) => {
|
|
31
45
|
const client = useStreamVideoClient();
|
|
32
46
|
|
|
@@ -38,10 +52,6 @@ export const LivestreamPlayer = ({
|
|
|
38
52
|
}
|
|
39
53
|
const myCall = client.call(callType, callId);
|
|
40
54
|
setCall(myCall);
|
|
41
|
-
myCall.join().catch((e) => {
|
|
42
|
-
const logger = getLogger(['LivestreamPlayer']);
|
|
43
|
-
logger('error', 'Error joining call:', e);
|
|
44
|
-
});
|
|
45
55
|
return () => {
|
|
46
56
|
if (myCall.state.callingState !== CallingState.LEFT) {
|
|
47
57
|
myCall.leave().catch((e) => {
|
|
@@ -71,7 +81,7 @@ export const LivestreamPlayer = ({
|
|
|
71
81
|
|
|
72
82
|
return (
|
|
73
83
|
<StreamCall call={call}>
|
|
74
|
-
<ViewerLivestream />
|
|
84
|
+
<ViewerLivestream joinBehavior={joinBehavior} />
|
|
75
85
|
</StreamCall>
|
|
76
86
|
);
|
|
77
87
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { useTheme } from '../../../contexts';
|
|
4
|
-
import { ShieldBadge } from '../../../icons';
|
|
5
4
|
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
6
5
|
import {
|
|
7
6
|
type CallSessionResponse,
|
|
@@ -19,6 +18,7 @@ export type DurationBadgeProps = {
|
|
|
19
18
|
* The HostDurationBadge component displays the duration while the live stream is active.
|
|
20
19
|
*/
|
|
21
20
|
export const DurationBadge = ({ mode }: DurationBadgeProps) => {
|
|
21
|
+
const styles = useStyles();
|
|
22
22
|
const { useCallSession } = useCallStateHooks();
|
|
23
23
|
const session = useCallSession();
|
|
24
24
|
|
|
@@ -33,11 +33,7 @@ export const DurationBadge = ({ mode }: DurationBadgeProps) => {
|
|
|
33
33
|
|
|
34
34
|
const call = useCall();
|
|
35
35
|
const {
|
|
36
|
-
theme: {
|
|
37
|
-
colors,
|
|
38
|
-
variants: { iconSizes },
|
|
39
|
-
durationBadge,
|
|
40
|
-
},
|
|
36
|
+
theme: { colors, durationBadge },
|
|
41
37
|
} = useTheme();
|
|
42
38
|
|
|
43
39
|
// for host
|
|
@@ -127,18 +123,7 @@ export const DurationBadge = ({ mode }: DurationBadgeProps) => {
|
|
|
127
123
|
durationBadge.container,
|
|
128
124
|
]}
|
|
129
125
|
>
|
|
130
|
-
<View
|
|
131
|
-
style={[
|
|
132
|
-
styles.icon,
|
|
133
|
-
{
|
|
134
|
-
height: iconSizes.xs,
|
|
135
|
-
width: iconSizes.xs,
|
|
136
|
-
},
|
|
137
|
-
durationBadge.icon,
|
|
138
|
-
]}
|
|
139
|
-
>
|
|
140
|
-
<ShieldBadge />
|
|
141
|
-
</View>
|
|
126
|
+
<View style={[styles.dot, durationBadge.icon]} />
|
|
142
127
|
<Text
|
|
143
128
|
style={[
|
|
144
129
|
styles.label,
|
|
@@ -152,22 +137,34 @@ export const DurationBadge = ({ mode }: DurationBadgeProps) => {
|
|
|
152
137
|
);
|
|
153
138
|
};
|
|
154
139
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
140
|
+
const useStyles = () => {
|
|
141
|
+
const { theme } = useTheme();
|
|
142
|
+
return useMemo(
|
|
143
|
+
() =>
|
|
144
|
+
StyleSheet.create({
|
|
145
|
+
container: {
|
|
146
|
+
paddingHorizontal: theme.variants.spacingSizes.sm,
|
|
147
|
+
paddingVertical: theme.variants.spacingSizes.sm,
|
|
148
|
+
borderRadius: theme.variants.borderRadiusSizes.sm,
|
|
149
|
+
flexDirection: 'row',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
},
|
|
153
|
+
dot: {
|
|
154
|
+
backgroundColor: theme.colors.iconWarning,
|
|
155
|
+
marginRight: theme.variants.spacingSizes.xs,
|
|
156
|
+
borderRadius: 90,
|
|
157
|
+
height: 10,
|
|
158
|
+
width: 10,
|
|
159
|
+
},
|
|
160
|
+
label: {
|
|
161
|
+
textAlign: 'center',
|
|
162
|
+
fontSize: theme.variants.fontSizes.md,
|
|
163
|
+
fontWeight: '600',
|
|
164
|
+
flexShrink: 1,
|
|
165
|
+
paddingLeft: theme.variants.spacingSizes.xs,
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
[theme],
|
|
169
|
+
);
|
|
170
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { useTheme } from '../../../contexts';
|
|
4
4
|
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
@@ -13,62 +13,55 @@ export type FollowerCountProps = {};
|
|
|
13
13
|
* The FollowerCount component that displays the number of participants while in the call.
|
|
14
14
|
*/
|
|
15
15
|
export const FollowerCount = ({}: FollowerCountProps) => {
|
|
16
|
+
const styles = useStyles();
|
|
16
17
|
const {
|
|
17
|
-
theme: {
|
|
18
|
-
colors,
|
|
19
|
-
variants: { iconSizes },
|
|
20
|
-
followerCount,
|
|
21
|
-
},
|
|
18
|
+
theme: { followerCount },
|
|
22
19
|
} = useTheme();
|
|
20
|
+
|
|
23
21
|
const { useParticipantCount } = useCallStateHooks();
|
|
24
22
|
const totalParticipants = useParticipantCount();
|
|
23
|
+
|
|
25
24
|
return (
|
|
26
|
-
<View
|
|
27
|
-
style={[
|
|
28
|
-
styles.container,
|
|
29
|
-
{ backgroundColor: colors.sheetTertiary },
|
|
30
|
-
followerCount.container,
|
|
31
|
-
]}
|
|
32
|
-
>
|
|
33
|
-
<View
|
|
34
|
-
style={[
|
|
35
|
-
styles.icon,
|
|
36
|
-
{ height: iconSizes.xs, width: iconSizes.xs },
|
|
37
|
-
followerCount.icon,
|
|
38
|
-
]}
|
|
39
|
-
>
|
|
25
|
+
<View style={[styles.container, followerCount.container]}>
|
|
26
|
+
<View style={[styles.icon, followerCount.icon]}>
|
|
40
27
|
<Eye />
|
|
41
28
|
</View>
|
|
42
|
-
<Text
|
|
43
|
-
style={[
|
|
44
|
-
styles.label,
|
|
45
|
-
{ color: colors.textPrimary },
|
|
46
|
-
followerCount.label,
|
|
47
|
-
]}
|
|
48
|
-
>
|
|
29
|
+
<Text style={[styles.label, followerCount.label]}>
|
|
49
30
|
{totalParticipants}
|
|
50
31
|
</Text>
|
|
51
32
|
</View>
|
|
52
33
|
);
|
|
53
34
|
};
|
|
54
35
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
36
|
+
const useStyles = () => {
|
|
37
|
+
const { theme } = useTheme();
|
|
38
|
+
return useMemo(
|
|
39
|
+
() =>
|
|
40
|
+
StyleSheet.create({
|
|
41
|
+
container: {
|
|
42
|
+
paddingHorizontal: theme.variants.spacingSizes.sm,
|
|
43
|
+
paddingVertical: 4,
|
|
44
|
+
borderTopRightRadius: 4,
|
|
45
|
+
borderBottomRightRadius: 4,
|
|
46
|
+
flexDirection: 'row',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
backgroundColor: theme.colors.sheetTertiary,
|
|
50
|
+
},
|
|
51
|
+
icon: {
|
|
52
|
+
height: theme.variants.iconSizes.sm,
|
|
53
|
+
width: theme.variants.iconSizes.sm,
|
|
54
|
+
},
|
|
55
|
+
label: {
|
|
56
|
+
fontSize: theme.variants.fontSizes.md,
|
|
57
|
+
fontWeight: '600',
|
|
58
|
+
flexShrink: 1,
|
|
59
|
+
textAlign: 'center',
|
|
60
|
+
includeFontPadding: false,
|
|
61
|
+
marginLeft: theme.variants.spacingSizes.xs,
|
|
62
|
+
color: theme.colors.textPrimary,
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
[theme],
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { useTheme } from '../../../contexts';
|
|
4
4
|
import { useI18n } from '@stream-io/video-react-bindings';
|
|
@@ -12,6 +12,7 @@ export type LiveIndicatorProps = {};
|
|
|
12
12
|
* The LiveIndicator component displays whether the live stream is live or not.
|
|
13
13
|
*/
|
|
14
14
|
export const LiveIndicator = ({}: LiveIndicatorProps) => {
|
|
15
|
+
const styles = useStyles();
|
|
15
16
|
const {
|
|
16
17
|
theme: { colors, typefaces, liveIndicator },
|
|
17
18
|
} = useTheme();
|
|
@@ -38,16 +39,23 @@ export const LiveIndicator = ({}: LiveIndicatorProps) => {
|
|
|
38
39
|
);
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
const useStyles = () => {
|
|
43
|
+
const { theme } = useTheme();
|
|
44
|
+
return useMemo(
|
|
45
|
+
() =>
|
|
46
|
+
StyleSheet.create({
|
|
47
|
+
container: {
|
|
48
|
+
paddingHorizontal: theme.variants.spacingSizes.sm,
|
|
49
|
+
paddingVertical: theme.variants.spacingSizes.sm,
|
|
50
|
+
borderTopLeftRadius: theme.variants.borderRadiusSizes.sm,
|
|
51
|
+
borderBottomLeftRadius: theme.variants.borderRadiusSizes.sm,
|
|
52
|
+
justifyContent: 'center',
|
|
53
|
+
},
|
|
54
|
+
label: {
|
|
55
|
+
textAlign: 'center',
|
|
56
|
+
includeFontPadding: false,
|
|
57
|
+
},
|
|
58
|
+
}),
|
|
59
|
+
[theme],
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import React, { useEffect, useMemo } from 'react';
|
|
2
|
-
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
3
2
|
import { StyleSheet, View } from 'react-native';
|
|
4
3
|
import InCallManager from 'react-native-incall-manager';
|
|
5
4
|
import { useTheme } from '../../../contexts';
|
|
6
|
-
import {
|
|
7
|
-
ViewerLivestreamTopView as DefaultViewerLivestreamTopView,
|
|
8
|
-
type ViewerLivestreamTopViewProps,
|
|
9
|
-
} from '../LivestreamTopView/ViewerLivestreamTopView';
|
|
5
|
+
import { type ViewerLivestreamTopViewProps } from '../LivestreamTopView/ViewerLivestreamTopView';
|
|
10
6
|
import {
|
|
11
7
|
ViewerLivestreamControls as DefaultViewerLivestreamControls,
|
|
12
8
|
type ViewerLivestreamControlsProps,
|
|
@@ -16,12 +12,14 @@ import {
|
|
|
16
12
|
LivestreamLayout as DefaultLivestreamLayout,
|
|
17
13
|
type LivestreamLayoutProps,
|
|
18
14
|
} from '../LivestreamLayout';
|
|
19
|
-
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
15
|
+
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
20
16
|
import {
|
|
21
17
|
FloatingParticipantView as DefaultFloatingParticipantView,
|
|
22
18
|
type FloatingParticipantViewProps,
|
|
23
19
|
} from '../../Participant';
|
|
24
|
-
import { hasVideo } from '@stream-io/video-client';
|
|
20
|
+
import { CallingState, hasVideo } from '@stream-io/video-client';
|
|
21
|
+
import { CallEndedView } from '../LivestreamPlayer/LivestreamEnded';
|
|
22
|
+
import { ViewerLobby } from './ViewerLobby';
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
25
|
* Props for the ViewerLivestream component.
|
|
@@ -45,13 +43,21 @@ export type ViewerLivestreamProps = ViewerLivestreamTopViewProps &
|
|
|
45
43
|
* Component to customize the FloatingParticipantView when screen is shared.
|
|
46
44
|
*/
|
|
47
45
|
FloatingParticipantView?: React.ComponentType<FloatingParticipantViewProps> | null;
|
|
46
|
+
/**
|
|
47
|
+
* Determines when the viewer joins the call.
|
|
48
|
+
*
|
|
49
|
+
* `"asap"` behavior means joining the call as soon as it is possible
|
|
50
|
+
* (either the `join_ahead_time_seconds` setting allows it, or the user
|
|
51
|
+
* has a the capability to join backstage).
|
|
52
|
+
*/
|
|
53
|
+
joinBehavior?: 'asap' | 'live';
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
/**
|
|
51
57
|
* The ViewerLivestream component renders the UI for the Viewer's live stream.
|
|
52
58
|
*/
|
|
53
59
|
export const ViewerLivestream = ({
|
|
54
|
-
ViewerLivestreamTopView
|
|
60
|
+
ViewerLivestreamTopView,
|
|
55
61
|
ViewerLivestreamControls = DefaultViewerLivestreamControls,
|
|
56
62
|
LivestreamLayout = DefaultLivestreamLayout,
|
|
57
63
|
FloatingParticipantView = DefaultFloatingParticipantView,
|
|
@@ -60,12 +66,24 @@ export const ViewerLivestream = ({
|
|
|
60
66
|
DurationBadge,
|
|
61
67
|
ViewerLeaveStreamButton,
|
|
62
68
|
onLeaveStreamHandler,
|
|
69
|
+
joinBehavior,
|
|
63
70
|
}: ViewerLivestreamProps) => {
|
|
64
71
|
const styles = useStyles();
|
|
72
|
+
const call = useCall();
|
|
65
73
|
const {
|
|
66
74
|
theme: { viewerLivestream },
|
|
67
75
|
} = useTheme();
|
|
68
|
-
const {
|
|
76
|
+
const {
|
|
77
|
+
useHasOngoingScreenShare,
|
|
78
|
+
useParticipants,
|
|
79
|
+
useCallCallingState,
|
|
80
|
+
useCallEndedAt,
|
|
81
|
+
useIsCallLive,
|
|
82
|
+
useOwnCapabilities,
|
|
83
|
+
} = useCallStateHooks();
|
|
84
|
+
const canJoinLive = useIsCallLive();
|
|
85
|
+
const callingState = useCallCallingState();
|
|
86
|
+
const endedAt = useCallEndedAt();
|
|
69
87
|
const hasOngoingScreenShare = useHasOngoingScreenShare();
|
|
70
88
|
const [currentSpeaker] = useParticipants();
|
|
71
89
|
const floatingParticipant =
|
|
@@ -73,6 +91,11 @@ export const ViewerLivestream = ({
|
|
|
73
91
|
currentSpeaker &&
|
|
74
92
|
hasVideo(currentSpeaker) &&
|
|
75
93
|
currentSpeaker;
|
|
94
|
+
const [hasLeft, setHasLeft] = useState(false);
|
|
95
|
+
|
|
96
|
+
const canJoinEarly = useCanJoinEarly();
|
|
97
|
+
const canJoinBackstage =
|
|
98
|
+
useOwnCapabilities()?.includes('join-backstage') ?? false;
|
|
76
99
|
|
|
77
100
|
const [topViewHeight, setTopViewHeight] = React.useState<number>();
|
|
78
101
|
const [controlsHeight, setControlsHeight] = React.useState<number>();
|
|
@@ -83,6 +106,12 @@ export const ViewerLivestream = ({
|
|
|
83
106
|
return () => InCallManager.stop();
|
|
84
107
|
}, []);
|
|
85
108
|
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (callingState === CallingState.LEFT) {
|
|
111
|
+
setHasLeft(true);
|
|
112
|
+
}
|
|
113
|
+
}, [callingState]);
|
|
114
|
+
|
|
86
115
|
const topViewProps: ViewerLivestreamTopViewProps = {
|
|
87
116
|
LiveIndicator,
|
|
88
117
|
FollowerCount,
|
|
@@ -92,6 +121,41 @@ export const ViewerLivestream = ({
|
|
|
92
121
|
},
|
|
93
122
|
};
|
|
94
123
|
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const handleJoinCall = async () => {
|
|
126
|
+
try {
|
|
127
|
+
await call?.join();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error('Failed to join call', error);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const canJoinAsap = canJoinLive || canJoinEarly || canJoinBackstage;
|
|
134
|
+
const join = joinBehavior ?? 'asap';
|
|
135
|
+
const canJoin =
|
|
136
|
+
(join === 'asap' && canJoinAsap) || (join === 'live' && canJoinLive);
|
|
137
|
+
|
|
138
|
+
if (call && callingState === CallingState.IDLE && canJoin && !hasLeft) {
|
|
139
|
+
handleJoinCall();
|
|
140
|
+
}
|
|
141
|
+
}, [
|
|
142
|
+
canJoinLive,
|
|
143
|
+
call,
|
|
144
|
+
canJoinBackstage,
|
|
145
|
+
canJoinEarly,
|
|
146
|
+
joinBehavior,
|
|
147
|
+
callingState,
|
|
148
|
+
hasLeft,
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
if (endedAt != null) {
|
|
152
|
+
return <CallEndedView />;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!canJoinLive || callingState !== CallingState.JOINED) {
|
|
156
|
+
return <ViewerLobby isLive={canJoinLive} />;
|
|
157
|
+
}
|
|
158
|
+
|
|
95
159
|
return (
|
|
96
160
|
<View style={[styles.container, viewerLivestream.container]}>
|
|
97
161
|
{ViewerLivestreamTopView && <ViewerLivestreamTopView {...topViewProps} />}
|
|
@@ -124,6 +188,39 @@ export const ViewerLivestream = ({
|
|
|
124
188
|
);
|
|
125
189
|
};
|
|
126
190
|
|
|
191
|
+
const useCanJoinEarly = () => {
|
|
192
|
+
const { useCallStartsAt, useCallSettings } = useCallStateHooks();
|
|
193
|
+
const startsAt = useCallStartsAt();
|
|
194
|
+
const settings = useCallSettings();
|
|
195
|
+
const joinAheadTimeSeconds = settings?.backstage.join_ahead_time_seconds;
|
|
196
|
+
const [canJoinEarly, setCanJoinEarly] = useState(() =>
|
|
197
|
+
checkCanJoinEarly(startsAt, joinAheadTimeSeconds),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (!canJoinEarly) {
|
|
202
|
+
const handle = setInterval(() => {
|
|
203
|
+
setCanJoinEarly(checkCanJoinEarly(startsAt, joinAheadTimeSeconds));
|
|
204
|
+
}, 1000);
|
|
205
|
+
|
|
206
|
+
return () => clearInterval(handle);
|
|
207
|
+
}
|
|
208
|
+
}, [canJoinEarly, startsAt, joinAheadTimeSeconds]);
|
|
209
|
+
|
|
210
|
+
return canJoinEarly;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const checkCanJoinEarly = (
|
|
214
|
+
startsAt: Date | undefined,
|
|
215
|
+
joinAheadTimeSeconds: number | undefined,
|
|
216
|
+
) => {
|
|
217
|
+
if (!startsAt) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Date.now() >= +startsAt - (joinAheadTimeSeconds ?? 0) * 1000;
|
|
222
|
+
};
|
|
223
|
+
|
|
127
224
|
const useStyles = () => {
|
|
128
225
|
const { theme } = useTheme();
|
|
129
226
|
return useMemo(
|