@amityco/react-native-social-uikit 4.0.0-a9cb7bb.0 → 4.0.0-b511cc3d.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.
Files changed (28) hide show
  1. package/lib/commonjs/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/DetailStyle.js +1 -1
  2. package/lib/commonjs/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/DetailStyle.js.map +1 -1
  3. package/lib/commonjs/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/FeedStyle.js +2 -1
  4. package/lib/commonjs/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/FeedStyle.js.map +1 -1
  5. package/lib/commonjs/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.js +93 -67
  6. package/lib/commonjs/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.js.map +1 -1
  7. package/lib/commonjs/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.js +2 -1
  8. package/lib/commonjs/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.js.map +1 -1
  9. package/lib/commonjs/v4/enum/roomStatus.js +1 -1
  10. package/lib/module/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/DetailStyle.js +1 -1
  11. package/lib/module/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/DetailStyle.js.map +1 -1
  12. package/lib/module/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/FeedStyle.js +2 -1
  13. package/lib/module/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/FeedStyle.js.map +1 -1
  14. package/lib/module/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.js +94 -68
  15. package/lib/module/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.js.map +1 -1
  16. package/lib/module/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.js +2 -1
  17. package/lib/module/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.js.map +1 -1
  18. package/lib/module/v4/enum/roomStatus.js +1 -1
  19. package/lib/typescript/src/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.d.ts.map +1 -1
  20. package/lib/typescript/src/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.d.ts +1 -0
  21. package/lib/typescript/src/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.d.ts.map +1 -1
  22. package/lib/typescript/src/v4/enum/roomStatus.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/DetailStyle.tsx +1 -1
  25. package/src/v4/PublicApi/Components/AmityPostEngagementActionsComponent/Components/FeedStyle.tsx +1 -1
  26. package/src/v4/PublicApi/Pages/AmityLivestreamPlayerPage/AmityLivestreamPlayerPage.tsx +150 -87
  27. package/src/v4/PublicApi/Pages/AmityLivestreamPlayerPage/styles.ts +1 -0
  28. package/src/v4/enum/roomStatus.ts +1 -1
@@ -1,9 +1,8 @@
1
1
  import React, { useEffect, useState, useRef } from 'react';
2
- import { View, TouchableOpacity } from 'react-native';
2
+ import { View, TouchableOpacity, Platform } from 'react-native';
3
3
  import { useStyles } from './styles';
4
4
  import LiveStreamEndThumbnail from '../../../component/LivestreamContent/LivestreamEndedThumbnail';
5
5
  import { SvgXml } from 'react-native-svg';
6
- import { RoomRepository } from '@amityco/ts-sdk-react-native';
7
6
  import { close } from '../../../assets/icons';
8
7
  import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
9
8
  import { NativeStackNavigationProp } from '@react-navigation/native-stack';
@@ -20,6 +19,7 @@ import {
20
19
  usePostSubscription,
21
20
  useRoomSubscription,
22
21
  } from '../../../../v4/hook/index';
22
+ import { RoomRepository } from '@amityco/ts-sdk-react-native';
23
23
 
24
24
  function AmityLiveStreamPlayerPage() {
25
25
  const { styles, theme } = useStyles();
@@ -29,18 +29,21 @@ function AmityLiveStreamPlayerPage() {
29
29
 
30
30
  const [reconnecting, setReconnecting] = useState(false);
31
31
  const [room, setRoom] = useState<Amity.Room | null>(null);
32
+ const [videoError, setVideoError] = useState(false);
32
33
  const [error, setError] = useState<Error | null>(null);
34
+ const [, setPlayerInitialized] = useState(false);
35
+ const [videoKey, setVideoKey] = useState(0);
36
+ const [isPaused, setIsPaused] = useState(false);
33
37
  const [wasLive, setWasLive] = useState(false);
34
- const [showEndThumbnail, setShowEndThumbnail] = useState(false);
35
- const { roomId, post } = route.params;
36
- const { client } = useAuth();
37
- const videoRef = useRef<any>(null);
38
- const isProgrammaticDismiss = useRef(false);
39
38
 
39
+ const { roomId, post } = route.params;
40
40
  const { subscribedPost } = usePostSubscription(post?.postId);
41
-
42
41
  useRoomSubscription({ room });
43
42
 
43
+ const { client } = useAuth();
44
+ const videoRef = useRef<any>(null);
45
+ const isStreamEnding = useRef(false);
46
+
44
47
  useEffect(() => {
45
48
  const unsubscribe = RoomRepository.getRoom(
46
49
  roomId,
@@ -54,22 +57,16 @@ function AmityLiveStreamPlayerPage() {
54
57
  }, [roomId]);
55
58
 
56
59
  useEffect(() => {
57
- if (room?.isDeleted || subscribedPost?.isDeleted) {
58
- navigation.replace('PostDetail', { postId: subscribedPost?.postId });
60
+ if (room?.status === RoomStatus.live) {
61
+ setWasLive(true);
59
62
  }
60
- }, [room?.isDeleted, subscribedPost, navigation]);
63
+ }, [room?.status]);
61
64
 
62
65
  useEffect(() => {
63
- const isTerminated =
64
- room?.moderation?.terminateLabels &&
65
- room?.moderation?.terminateLabels?.length > 0;
66
- const isLiveOrEnded =
67
- room?.status === RoomStatus.live || room?.status === RoomStatus.ended;
68
-
69
- if (isLiveOrEnded && isTerminated) {
70
- navigation.replace('LivestreamTerminated', { type: 'viewer' });
66
+ if (room?.isDeleted || subscribedPost?.isDeleted) {
67
+ navigation.replace('PostDetail', { postId: subscribedPost?.postId });
71
68
  }
72
- }, [room?.moderation?.terminateLabels, room?.status, navigation]);
69
+ }, [room?.isDeleted, subscribedPost, navigation]);
73
70
 
74
71
  useEffect(() => {
75
72
  const unsubscribe = NetInfo.addEventListener((state) => {
@@ -78,44 +75,94 @@ function AmityLiveStreamPlayerPage() {
78
75
  return () => unsubscribe();
79
76
  }, []);
80
77
 
81
- // Track if user was watching live
82
78
  useEffect(() => {
83
- if (room?.status === RoomStatus.live) {
84
- setWasLive(true);
85
- }
86
- }, [room?.status]);
79
+ if (!room?.status) return;
87
80
 
88
- // Dismiss fullscreen player when stream ends
89
- useEffect(() => {
90
- const shouldShowEndThumbnail =
91
- room?.status === RoomStatus.ended ||
92
- (room?.status === RoomStatus.recorded && wasLive);
93
-
94
- if (shouldShowEndThumbnail && videoRef.current) {
95
- isProgrammaticDismiss.current = true;
96
- videoRef.current?.dismissFullscreenPlayer();
97
- // Delay showing end thumbnail to allow fullscreen dismiss to complete
81
+ const shouldEnd =
82
+ room.status === RoomStatus.ended ||
83
+ (room.status === RoomStatus.recorded && wasLive);
84
+
85
+ if (!shouldEnd || isStreamEnding.current) return;
86
+
87
+ isStreamEnding.current = true;
88
+ setIsPaused(true);
89
+
90
+ if (Platform.OS === 'ios') {
91
+ // iOS: ONLY dismiss fullscreen. DO NOT touch key. DO NOT unmount.
92
+ requestAnimationFrame(() => {
93
+ videoRef.current?.dismissFullscreenPlayer?.();
94
+ });
95
+ } else {
96
+ // Android: HARD destroy
98
97
  setTimeout(() => {
99
- setShowEndThumbnail(true);
100
- }, 300);
101
- } else if (shouldShowEndThumbnail && !videoRef.current) {
102
- // If video ref is already null, show end thumbnail immediately
103
- setShowEndThumbnail(true);
104
- } else if (!shouldShowEndThumbnail) {
105
- setShowEndThumbnail(false);
98
+ setVideoKey((prev) => prev + 1);
99
+ }, 50);
106
100
  }
107
101
  }, [room?.status, wasLive]);
108
102
 
109
- // Start in fullscreen mode on iOS
110
103
  useEffect(() => {
111
- if (videoRef.current && room && room.status !== RoomStatus.ended) {
112
- const timer = setTimeout(() => {
113
- videoRef.current?.presentFullscreenPlayer();
114
- }, 100);
115
- return () => clearTimeout(timer);
104
+ let timer: ReturnType<typeof setTimeout> | null = null;
105
+
106
+ if (
107
+ videoRef.current &&
108
+ room &&
109
+ room.status === RoomStatus.live &&
110
+ !videoError &&
111
+ Platform.OS === 'ios'
112
+ ) {
113
+ const isTerminated =
114
+ room?.moderation?.terminateLabels &&
115
+ room?.moderation?.terminateLabels?.length > 0;
116
+
117
+ if (!isTerminated) {
118
+ timer = setTimeout(() => {
119
+ videoRef.current?.presentFullscreenPlayer();
120
+ }, 100);
121
+ }
116
122
  }
117
- return undefined;
118
- }, [room]);
123
+
124
+ return () => {
125
+ if (timer !== null) {
126
+ clearTimeout(timer);
127
+ }
128
+ };
129
+ }, [room, videoError]);
130
+
131
+ const isTerminated =
132
+ room?.moderation?.terminateLabels &&
133
+ room?.moderation?.terminateLabels?.length > 0;
134
+
135
+ const shouldShowEndThumbnail =
136
+ room?.status === RoomStatus.ended ||
137
+ (room?.status === RoomStatus.recorded && wasLive) ||
138
+ isTerminated;
139
+
140
+ useEffect(() => {
141
+ if (!room?.status) return;
142
+
143
+ const shouldEnd =
144
+ room.status === RoomStatus.ended ||
145
+ (room.status === RoomStatus.recorded && wasLive);
146
+
147
+ if (!shouldEnd || isStreamEnding.current) return;
148
+
149
+ isStreamEnding.current = true;
150
+ setIsPaused(true);
151
+
152
+ if (Platform.OS === 'ios') {
153
+ // iOS: just dismiss fullscreen, DO NOT destroy immediately
154
+ if (videoRef.current) {
155
+ try {
156
+ videoRef.current.dismissFullscreenPlayer?.();
157
+ } catch {}
158
+ }
159
+ } else {
160
+ // Android: HARD destroy
161
+ setTimeout(() => {
162
+ setVideoKey((prev) => prev + 1);
163
+ }, 50);
164
+ }
165
+ }, [room?.status, wasLive]);
119
166
 
120
167
  if (!room || error) {
121
168
  return (
@@ -129,21 +176,19 @@ function AmityLiveStreamPlayerPage() {
129
176
  navigation.goBack();
130
177
  };
131
178
 
132
- const handleFullscreenDismiss = () => {
133
- // Only navigate back if user manually dismissed, not programmatically
134
- if (!isProgrammaticDismiss.current) {
135
- closePlayer();
136
- }
137
- isProgrammaticDismiss.current = false;
138
- };
179
+ const videoUrl =
180
+ room.status === RoomStatus.recorded
181
+ ? room.recordedPlaybackInfos[0]?.url
182
+ : room.livePlaybackUrl;
139
183
 
140
184
  return (
141
185
  <SafeAreaView style={styles.container}>
142
- {showEndThumbnail ? (
186
+ {shouldShowEndThumbnail ? (
143
187
  <>
144
188
  <View style={styles.steamEndContainer}>
145
189
  <LiveStreamEndThumbnail />
146
190
  </View>
191
+
147
192
  <TouchableOpacity style={styles.closeButton} onPress={closePlayer}>
148
193
  <SvgXml
149
194
  xml={close()}
@@ -165,35 +210,53 @@ function AmityLiveStreamPlayerPage() {
165
210
  </View>
166
211
  </View>
167
212
  )}
168
- <Video
169
- ref={videoRef}
170
- source={{
171
- uri:
172
- room.status === RoomStatus.recorded
173
- ? room.recordedPlaybackInfos[0]?.url
174
- : room.livePlaybackUrl,
175
- headers: {
176
- Authorization: `Bearer ${client.token.accessToken}`,
177
- },
178
-
179
- type: 'm3u8',
180
- }}
181
- style={styles.container}
182
- resizeMode="contain"
183
- controls={room.status === RoomStatus.recorded}
184
- fullscreen={true}
185
- fullscreenOrientation="landscape"
186
- paused={false}
187
- muted={false}
188
- volume={1.0}
189
- audioOutput="speaker"
190
- playInBackground={false}
191
- playWhenInactive={false}
192
- onError={(e) => {
193
- console.log('Video Player Error: ', e);
194
- }}
195
- onFullscreenPlayerDidDismiss={handleFullscreenDismiss}
196
- />
213
+
214
+ {!shouldShowEndThumbnail && (
215
+ <Video
216
+ key={
217
+ Platform.OS === 'android' ? `${videoUrl}-${videoKey}` : videoUrl
218
+ }
219
+ ref={videoRef}
220
+ source={{
221
+ uri:
222
+ room.status === RoomStatus.recorded
223
+ ? room.recordedPlaybackInfos[0]?.url
224
+ : room.livePlaybackUrl,
225
+ headers: {
226
+ Authorization: `Bearer ${client.token.accessToken}`,
227
+ },
228
+ }}
229
+ style={styles.container}
230
+ resizeMode="contain"
231
+ controls={room?.status === RoomStatus.recorded && !wasLive}
232
+ fullscreen={
233
+ Platform.OS === 'ios' && room.status !== RoomStatus.recorded
234
+ }
235
+ fullscreenOrientation="landscape"
236
+ paused={isPaused}
237
+ muted={false}
238
+ volume={1.0}
239
+ audioOutput="speaker"
240
+ playInBackground={false}
241
+ playWhenInactive={false}
242
+ repeat={false}
243
+ onLoad={() => {
244
+ setPlayerInitialized(true);
245
+ if (room.status === RoomStatus.recorded) {
246
+ setIsPaused(false);
247
+ }
248
+ }}
249
+ onError={(e) => {
250
+ console.log('Video Error: ', e);
251
+ setVideoError(true);
252
+ }}
253
+ onFullscreenPlayerDidDismiss={() => {
254
+ if (Platform.OS === 'ios') {
255
+ closePlayer();
256
+ }
257
+ }}
258
+ />
259
+ )}
197
260
  </View>
198
261
  )}
199
262
 
@@ -17,6 +17,7 @@ export const useStyles = () => {
17
17
  steamEndContainer: {
18
18
  flex: 1,
19
19
  justifyContent: 'center',
20
+ backgroundColor: '#000000',
20
21
  },
21
22
  indicator: {
22
23
  left: 16,
@@ -3,5 +3,5 @@ export const RoomStatus = {
3
3
  live: 'live' as Amity.RoomStatus,
4
4
  recorded: 'recorded' as Amity.RoomStatus,
5
5
  ended: 'ended' as Amity.RoomStatus,
6
- waiting_reconnect: 'waiting_reconnect' as Amity.RoomStatus,
6
+ waiting_reconnect: 'waitingReconnect' as Amity.RoomStatus,
7
7
  };