@streamplace/components 0.7.2 → 0.7.7

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 (91) hide show
  1. package/dist/components/chat/chat-box.js +212 -24
  2. package/dist/components/chat/chat-message.js +5 -5
  3. package/dist/components/chat/chat.js +83 -5
  4. package/dist/components/chat/emoji-suggestions.js +35 -0
  5. package/dist/components/chat/mod-view.js +59 -8
  6. package/dist/components/chat/system-message.js +19 -0
  7. package/dist/components/icons/bluesky-icon.js +9 -0
  8. package/dist/components/keep-awake.js +7 -0
  9. package/dist/components/keep-awake.native.js +16 -0
  10. package/dist/components/mobile-player/fullscreen.js +2 -1
  11. package/dist/components/mobile-player/fullscreen.native.js +3 -3
  12. package/dist/components/mobile-player/player.js +15 -30
  13. package/dist/components/mobile-player/ui/index.js +2 -0
  14. package/dist/components/mobile-player/ui/report-modal.js +90 -0
  15. package/dist/components/mobile-player/ui/streamer-loading-overlay.js +104 -0
  16. package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -1
  17. package/dist/components/mobile-player/ui/viewer-loading-overlay.js +49 -0
  18. package/dist/components/mobile-player/use-webrtc.js +7 -1
  19. package/dist/components/mobile-player/video-retry.js +29 -0
  20. package/dist/components/mobile-player/video.js +84 -9
  21. package/dist/components/mobile-player/video.native.js +24 -10
  22. package/dist/components/share/sharesheet.js +91 -0
  23. package/dist/components/ui/dialog.js +1 -1
  24. package/dist/components/ui/dropdown.js +6 -6
  25. package/dist/components/ui/index.js +2 -0
  26. package/dist/components/ui/primitives/modal.js +0 -1
  27. package/dist/components/ui/resizeable.js +20 -11
  28. package/dist/components/ui/slider.js +5 -0
  29. package/dist/hooks/index.js +1 -0
  30. package/dist/hooks/usePointerDevice.js +71 -0
  31. package/dist/index.js +10 -3
  32. package/dist/lib/system-messages.js +101 -0
  33. package/dist/livestream-store/chat.js +111 -18
  34. package/dist/livestream-store/livestream-store.js +3 -0
  35. package/dist/livestream-store/problems.js +76 -0
  36. package/dist/livestream-store/websocket-consumer.js +39 -4
  37. package/dist/player-store/player-store.js +33 -4
  38. package/dist/streamplace-store/block.js +51 -12
  39. package/dist/streamplace-store/stream.js +44 -23
  40. package/dist/ui/index.js +79 -0
  41. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
  42. package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
  43. package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/67b1eb60 +0 -0
  44. package/node-compile-cache/{v22.15.0-x64-92db9086-0 → v22.15.0-x64-efe9a9df-0}/7c275f90 +0 -0
  45. package/package.json +6 -2
  46. package/src/components/chat/chat-box.tsx +295 -25
  47. package/src/components/chat/chat-message.tsx +6 -7
  48. package/src/components/chat/chat.tsx +192 -41
  49. package/src/components/chat/emoji-suggestions.tsx +94 -0
  50. package/src/components/chat/mod-view.tsx +119 -40
  51. package/src/components/chat/system-message.tsx +38 -0
  52. package/src/components/icons/bluesky-icon.tsx +9 -0
  53. package/src/components/keep-awake.native.tsx +13 -0
  54. package/src/components/keep-awake.tsx +3 -0
  55. package/src/components/mobile-player/fullscreen.native.tsx +12 -3
  56. package/src/components/mobile-player/fullscreen.tsx +10 -3
  57. package/src/components/mobile-player/player.tsx +28 -36
  58. package/src/components/mobile-player/props.tsx +1 -0
  59. package/src/components/mobile-player/ui/index.ts +2 -0
  60. package/src/components/mobile-player/ui/report-modal.tsx +195 -0
  61. package/src/components/mobile-player/ui/streamer-loading-overlay.tsx +154 -0
  62. package/src/components/mobile-player/ui/viewer-context-menu.tsx +31 -3
  63. package/src/components/mobile-player/ui/viewer-loading-overlay.tsx +66 -0
  64. package/src/components/mobile-player/use-webrtc.tsx +10 -2
  65. package/src/components/mobile-player/video-retry.tsx +28 -0
  66. package/src/components/mobile-player/video.native.tsx +24 -10
  67. package/src/components/mobile-player/video.tsx +100 -21
  68. package/src/components/share/sharesheet.tsx +185 -0
  69. package/src/components/ui/dialog.tsx +1 -1
  70. package/src/components/ui/dropdown.tsx +13 -13
  71. package/src/components/ui/index.ts +2 -0
  72. package/src/components/ui/primitives/modal.tsx +0 -1
  73. package/src/components/ui/resizeable.tsx +26 -15
  74. package/src/components/ui/slider.tsx +1 -0
  75. package/src/hooks/index.ts +1 -0
  76. package/src/hooks/usePointerDevice.ts +89 -0
  77. package/src/index.tsx +11 -2
  78. package/src/lib/system-messages.ts +135 -0
  79. package/src/livestream-store/chat.tsx +145 -17
  80. package/src/livestream-store/livestream-state.tsx +10 -0
  81. package/src/livestream-store/livestream-store.tsx +3 -0
  82. package/src/livestream-store/problems.tsx +96 -0
  83. package/src/livestream-store/websocket-consumer.tsx +44 -4
  84. package/src/player-store/player-state.tsx +25 -4
  85. package/src/player-store/player-store.tsx +43 -5
  86. package/src/streamplace-store/block.tsx +55 -13
  87. package/src/streamplace-store/stream.tsx +66 -35
  88. package/src/ui/index.ts +86 -0
  89. package/tsconfig.tsbuildinfo +1 -1
  90. package/node-compile-cache/v22.15.0-x64-92db9086-0/37be0eec +0 -0
  91. package/node-compile-cache/v22.15.0-x64-92db9086-0/56540125 +0 -0
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeepAwake = KeepAwake;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const expo_keep_awake_1 = require("expo-keep-awake");
6
+ const react_1 = require("react");
7
+ function KeepAwake() {
8
+ // useKeepAwake();
9
+ (0, react_1.useEffect)(() => {
10
+ (0, expo_keep_awake_1.activateKeepAwakeAsync)();
11
+ return () => {
12
+ (0, expo_keep_awake_1.deactivateKeepAwake)();
13
+ };
14
+ }, []);
15
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {});
16
+ }
@@ -7,6 +7,7 @@ const react_1 = require("react");
7
7
  const __1 = require("../..");
8
8
  const ui_1 = require("../../components/ui");
9
9
  const video_1 = tslib_1.__importDefault(require("./video"));
10
+ const video_retry_1 = tslib_1.__importDefault(require("./video-retry"));
10
11
  function Fullscreen(props) {
11
12
  const playerId = (0, __1.getFirstPlayerID)();
12
13
  const protocol = (0, __1.usePlayerStore)((x) => x.protocol, playerId);
@@ -69,5 +70,5 @@ function Fullscreen(props) {
69
70
  document.body.removeEventListener("webkitfullscreenchange", listener);
70
71
  };
71
72
  }, []);
72
- return ((0, jsx_runtime_1.jsx)(ui_1.View, { ref: divRef, children: (0, jsx_runtime_1.jsx)(video_1.default, {}) }));
73
+ return ((0, jsx_runtime_1.jsxs)(ui_1.View, { ref: divRef, style: { width: "100%", height: "100%", overflow: "hidden" }, children: [(0, jsx_runtime_1.jsx)(video_retry_1.default, { children: (0, jsx_runtime_1.jsx)(video_1.default, {}) }), props.children] }));
73
74
  }
@@ -119,7 +119,7 @@ function Fullscreen(props) {
119
119
  width: isLandscape ? dimensions.width + 40 : dimensions.width,
120
120
  height: dimensions.height,
121
121
  },
122
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
122
+ ], children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
123
123
  styles.videoContainer,
124
124
  {
125
125
  width: isLandscape ? videoWidth + 40 : videoWidth,
@@ -127,10 +127,10 @@ function Fullscreen(props) {
127
127
  left: leftPosition,
128
128
  top: topPosition,
129
129
  },
130
- ], children: (0, jsx_runtime_1.jsx)(video_native_1.default, {}) }) }));
130
+ ], children: [(0, jsx_runtime_1.jsx)(video_native_1.default, {}), props.children] }) }));
131
131
  }
132
132
  // Normal non-fullscreen mode
133
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(video_native_1.default, {}) }));
133
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(__1.VideoRetry, { children: (0, jsx_runtime_1.jsx)(video_native_1.default, {}) }), props.children] }));
134
134
  }
135
135
  const styles = react_native_1.StyleSheet.create({
136
136
  fullscreenContainer: {
@@ -7,18 +7,23 @@ const tslib_1 = require("tslib");
7
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const react_1 = require("react");
9
9
  const atoms_1 = require("../../lib/theme/atoms");
10
- const livestream_store_1 = require("../../livestream-store");
11
10
  const player_store_1 = require("../../player-store");
12
11
  const streamplace_store_1 = require("../../streamplace-store");
13
12
  const ui_1 = require("../ui");
14
13
  const fullscreen_1 = require("./fullscreen");
14
+ const report_modal_1 = tslib_1.__importDefault(require("./ui/report-modal"));
15
15
  const OFFLINE_THRESHOLD = 10000;
16
16
  exports.PlayerUI = tslib_1.__importStar(require("./ui"));
17
17
  function Player(props) {
18
- const playing = (0, player_store_1.usePlayerStore)((x) => x.status === player_store_1.PlayerStatus.PLAYING);
19
- const setOffline = (0, player_store_1.usePlayerStore)((x) => x.setOffline);
20
18
  const setIngest = (0, player_store_1.usePlayerStore)((x) => x.setIngestConnectionState);
21
19
  const clearControlsTimeout = (0, player_store_1.usePlayerStore)((x) => x.clearControlsTimeout);
20
+ const setReportingURL = (0, player_store_1.usePlayerStore)((x) => x.setReportingURL);
21
+ const reportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.reportModalOpen);
22
+ const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
23
+ const reportSubject = (0, player_store_1.usePlayerStore)((x) => x.reportSubject);
24
+ (0, react_1.useEffect)(() => {
25
+ setReportingURL(props.reportingURL ?? null);
26
+ }, [props.reportingURL]);
22
27
  // Will call back every few seconds to send health updates
23
28
  usePlayerStatus();
24
29
  (0, react_1.useEffect)(() => {
@@ -32,33 +37,13 @@ function Player(props) {
32
37
  clearControlsTimeout();
33
38
  };
34
39
  }, []);
35
- const segment = (0, livestream_store_1.useSegment)();
36
- const [lastCheck, setLastCheck] = (0, react_1.useState)(0);
37
- (0, react_1.useEffect)(() => {
38
- if (playing) {
39
- setOffline(false);
40
- return;
41
- }
42
- if (!segment) {
43
- setOffline(false);
44
- return;
45
- }
46
- const startTime = Date.parse(segment.startTime);
47
- if (!startTime) {
48
- console.error("startTime is not a number", segment.startTime);
49
- return;
50
- }
51
- const timeSinceStart = Date.now() - startTime;
52
- if (timeSinceStart > OFFLINE_THRESHOLD) {
53
- setOffline(true);
54
- return;
55
- }
56
- const handle = setTimeout(() => {
57
- setLastCheck(Date.now());
58
- }, 1000);
59
- return () => clearTimeout(handle);
60
- }, [segment, playing, lastCheck]);
61
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)(ui_1.View, { style: [atoms_1.zIndex[0], atoms_1.flex.values[1], atoms_1.w.percent[100], atoms_1.layout.flex.center], children: (0, jsx_runtime_1.jsx)(fullscreen_1.Fullscreen, { src: props.src }) }) }));
40
+ return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)(ui_1.View, { style: [
41
+ atoms_1.zIndex[0],
42
+ atoms_1.w.percent[100],
43
+ atoms_1.h.percent[100],
44
+ atoms_1.flex.shrink[1],
45
+ atoms_1.layout.flex.center,
46
+ ], children: [(0, jsx_runtime_1.jsx)(report_modal_1.default, { open: reportModalOpen, onOpenChange: setReportModalOpen, subject: reportSubject }), (0, jsx_runtime_1.jsx)(fullscreen_1.Fullscreen, { src: props.src, children: props.children })] }) }));
62
47
  }
63
48
  const POLL_INTERVAL = 5000;
64
49
  function usePlayerStatus() {
@@ -5,5 +5,7 @@ tslib_1.__exportStar(require("./countdown"), exports);
5
5
  tslib_1.__exportStar(require("./input"), exports);
6
6
  tslib_1.__exportStar(require("./metrics"), exports);
7
7
  tslib_1.__exportStar(require("./streamer-context-menu"), exports);
8
+ tslib_1.__exportStar(require("./streamer-loading-overlay"), exports);
8
9
  tslib_1.__exportStar(require("./viewer-context-menu"), exports);
10
+ tslib_1.__exportStar(require("./viewer-loading-overlay"), exports);
9
11
  tslib_1.__exportStar(require("./viewers"), exports);
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReportModal = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const api_1 = require("@atproto/api");
6
+ const lucide_react_native_1 = require("lucide-react-native");
7
+ const react_1 = require("react");
8
+ const react_native_1 = require("react-native");
9
+ const __1 = require("../../..");
10
+ const livestream_store_1 = require("../../../livestream-store");
11
+ const ui_1 = require("../../ui");
12
+ // AT Protocol moderation reason types with proper labels
13
+ const REPORT_REASONS = [
14
+ {
15
+ value: api_1.ComAtprotoModerationDefs.REASONSPAM,
16
+ label: "Spam",
17
+ description: "Excessive unwanted promotion, replies, mentions",
18
+ },
19
+ {
20
+ value: api_1.ComAtprotoModerationDefs.REASONVIOLATION,
21
+ label: "Rule Violation",
22
+ description: "Direct, blatant violation of laws or terms of service",
23
+ },
24
+ {
25
+ value: api_1.ComAtprotoModerationDefs.REASONMISLEADING,
26
+ label: "Misleading Content",
27
+ description: "Misleading identity, affiliation, or content",
28
+ },
29
+ {
30
+ value: api_1.ComAtprotoModerationDefs.REASONSEXUAL,
31
+ label: "Sexual Content",
32
+ description: "Unwanted or mislabeled sexual content",
33
+ },
34
+ {
35
+ value: api_1.ComAtprotoModerationDefs.REASONRUDE,
36
+ label: "Harassment",
37
+ description: "Rude, harassing, explicit, or otherwise unwelcoming behavior",
38
+ },
39
+ {
40
+ value: api_1.ComAtprotoModerationDefs.REASONOTHER,
41
+ label: "Other",
42
+ description: "Reports not falling under another report category",
43
+ },
44
+ ];
45
+ const ReportModal = ({ open, onOpenChange, onSubmit, subject, title = "Report", description = "Why are you submitting this report?", }) => {
46
+ const [selectedReason, setSelectedReason] = (0, react_1.useState)(null);
47
+ const [additionalComments, setAdditionalComments] = (0, react_1.useState)("");
48
+ const [isSubmitting, setIsSubmitting] = (0, react_1.useState)(false);
49
+ const [submitError, setSubmitError] = (0, react_1.useState)(null);
50
+ const submitReport = (0, livestream_store_1.useSubmitReport)();
51
+ const handleCancel = () => {
52
+ setSelectedReason(null);
53
+ setAdditionalComments("");
54
+ setSubmitError(null);
55
+ onOpenChange(false);
56
+ };
57
+ const handleSubmit = async () => {
58
+ if (!selectedReason)
59
+ return;
60
+ setIsSubmitting(true);
61
+ setSubmitError(null);
62
+ try {
63
+ submitReport(subject, selectedReason, additionalComments.trim() || undefined);
64
+ // Reset form and close modal on success
65
+ setSelectedReason(null);
66
+ setAdditionalComments("");
67
+ onOpenChange(false);
68
+ }
69
+ catch (error) {
70
+ console.error("Failed to submit report:", error);
71
+ setSubmitError("Failed to submit report. Please try again.");
72
+ }
73
+ finally {
74
+ setIsSubmitting(false);
75
+ }
76
+ };
77
+ return ((0, jsx_runtime_1.jsxs)(ui_1.Dialog, { open: open, onOpenChange: onOpenChange, title: title, description: description, showCloseButton: true, variant: "default", size: "md", dismissible: false, position: "center", children: [(0, jsx_runtime_1.jsxs)(ui_1.ModalContent, { style: [__1.zero.pb[2]], children: [REPORT_REASONS.map((reason) => ((0, jsx_runtime_1.jsxs)(react_native_1.TouchableOpacity, { onPress: () => setSelectedReason(reason.value), style: [
78
+ __1.zero.layout.flex.row,
79
+ __1.zero.gap.all[2],
80
+ __1.zero.py[3],
81
+ __1.zero.px[3],
82
+ __1.zero.borderRadius[8],
83
+ __1.zero.layout.flex.alignCenter,
84
+ selectedReason === reason.value && {
85
+ backgroundColor: "rgba(0, 122, 255, 0.1)",
86
+ },
87
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { children: selectedReason === reason.value ? (0, jsx_runtime_1.jsx)(lucide_react_native_1.CheckCircle, {}) : (0, jsx_runtime_1.jsx)(lucide_react_native_1.Circle, {}) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [__1.zero.layout.flex.column, __1.zero.gap.all[1], __1.zero.flex[1]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [{ fontWeight: "600" }], children: reason.label }), (0, jsx_runtime_1.jsx)(ui_1.Text, { style: [{ fontSize: 14, color: "rgba(255,255,255,0.7)" }], children: reason.description })] })] }, reason.value))), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [__1.zero.pb[4], __1.zero.mt[4], __1.zero.px[2]], children: [(0, jsx_runtime_1.jsx)(ui_1.Text, { style: [__1.zero.mb[2]], children: "Additional Comments (optional)" }), (0, jsx_runtime_1.jsx)(ui_1.Textarea, { maxLength: 500, numberOfLines: 3, value: additionalComments, onChangeText: setAdditionalComments, placeholder: "Provide additional context for this report..." }), submitError && ((0, jsx_runtime_1.jsx)(ui_1.Text, { style: [__1.zero.mt[2], { color: "red", fontSize: 14 }], children: submitError }))] })] }), (0, jsx_runtime_1.jsxs)(ui_1.DialogFooter, { children: [(0, jsx_runtime_1.jsx)(ui_1.Button, { variant: "secondary", onPress: handleCancel, disabled: isSubmitting, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Cancel" }) }), (0, jsx_runtime_1.jsx)(ui_1.Button, { variant: "primary", onPress: handleSubmit, disabled: !selectedReason || isSubmitting, children: isSubmitting ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(lucide_react_native_1.Loader2, { style: [{ marginRight: 8 }] }), (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Submitting..." })] })) : ((0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Submit" })) })] })] }));
88
+ };
89
+ exports.ReportModal = ReportModal;
90
+ exports.default = exports.ReportModal;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoadingOverlay = LoadingOverlay;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = require("react");
7
+ const react_native_reanimated_1 = tslib_1.__importStar(require("react-native-reanimated"));
8
+ const atoms_1 = require("../../../lib/theme/atoms");
9
+ const defaultMessages = [
10
+ "Creating the stream",
11
+ "Uploading thumbnails",
12
+ "Getting things ready",
13
+ "Doing some magic",
14
+ "Preparing something special",
15
+ "Reticulating splines",
16
+ "Making it nice",
17
+ "Flipping some switches",
18
+ "Adding good vibes",
19
+ "Almost there",
20
+ "Summoning your Persona",
21
+ "Awakening our true selves",
22
+ "Fusion in progress",
23
+ "Equipping the right materia",
24
+ ];
25
+ function LoadingOverlay({ visible, width, height, subtitle, messages = defaultMessages, interval = 3000, }) {
26
+ const [currentIndex, setCurrentIndex] = (0, react_1.useState)(0);
27
+ const [shouldRender, setShouldRender] = (0, react_1.useState)(visible);
28
+ // Animation values
29
+ const translateY = (0, react_native_reanimated_1.useSharedValue)(0);
30
+ const opacity = (0, react_native_reanimated_1.useSharedValue)(1);
31
+ const wholeOpacity = (0, react_native_reanimated_1.useSharedValue)(0);
32
+ // Handle fade-in and fade-out animations
33
+ (0, react_1.useEffect)(() => {
34
+ if (visible) {
35
+ setShouldRender(true); // Ensure the component is mounted
36
+ wholeOpacity.value = (0, react_native_reanimated_1.withTiming)(1, { duration: 500 }); // Fade in
37
+ }
38
+ else {
39
+ wholeOpacity.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 500 }, () => {
40
+ // Unmount after fade-out
41
+ (0, react_native_reanimated_1.runOnJS)(setShouldRender)(false);
42
+ });
43
+ }
44
+ }, [visible]);
45
+ // Cycle messages on a timer
46
+ (0, react_1.useEffect)(() => {
47
+ if (!visible) {
48
+ setCurrentIndex(0);
49
+ return;
50
+ }
51
+ const timeout = setTimeout(() => {
52
+ setCurrentIndex((prev) => (prev + 1) % messages.length);
53
+ }, interval);
54
+ return () => clearTimeout(timeout);
55
+ }, [visible, currentIndex, interval, messages.length]);
56
+ // Trigger animation on each message change
57
+ (0, react_1.useEffect)(() => {
58
+ if (!visible)
59
+ return;
60
+ const fadeDuration = Math.min(interval / 2, 250); // Simplified fade duration
61
+ // Reset animation values
62
+ translateY.value = 20;
63
+ opacity.value = 0;
64
+ // Sequential fade-in and fade-out
65
+ translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: fadeDuration });
66
+ opacity.value = (0, react_native_reanimated_1.withTiming)(1, { duration: fadeDuration }, () => {
67
+ // add a delay for interval - (fadeDuration*2)
68
+ translateY.value = (0, react_native_reanimated_1.withDelay)(interval - fadeDuration * 2, (0, react_native_reanimated_1.withTiming)(-10, { duration: fadeDuration }));
69
+ opacity.value = (0, react_native_reanimated_1.withDelay)(interval - fadeDuration * 2, (0, react_native_reanimated_1.withTiming)(0, { duration: fadeDuration }));
70
+ });
71
+ }, [currentIndex, visible]);
72
+ const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
73
+ return {
74
+ transform: [{ translateY: translateY.value }],
75
+ opacity: opacity.value,
76
+ };
77
+ });
78
+ const wholeAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => ({
79
+ opacity: wholeOpacity.value,
80
+ }));
81
+ if (!shouldRender)
82
+ return null;
83
+ return ((0, jsx_runtime_1.jsxs)(react_native_reanimated_1.default.View, { style: [
84
+ {
85
+ position: "absolute",
86
+ top: 0,
87
+ left: 0,
88
+ width,
89
+ height,
90
+ backgroundColor: "rgba(0,0,0,0.7)",
91
+ alignItems: "center",
92
+ justifyContent: "center",
93
+ zIndex: 1000,
94
+ },
95
+ wholeAnimatedStyle,
96
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.Text, { style: [
97
+ {
98
+ color: "white",
99
+ fontSize: 24,
100
+ fontWeight: "bold",
101
+ },
102
+ animatedStyle,
103
+ ], children: messages[currentIndex] }), (0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.Text, { style: [atoms_1.pt[5], { color: "#a0a0a0" }], children: subtitle })] }));
104
+ }
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ContextMenu = ContextMenu;
4
+ exports.ReportButton = ReportButton;
4
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const dropdown_menu_1 = require("@rn-primitives/dropdown-menu");
5
7
  const lucide_react_native_1 = require("lucide-react-native");
6
8
  const theme_1 = require("../../../lib/theme");
7
9
  const livestream_store_1 = require("../../../livestream-store");
@@ -19,5 +21,22 @@ function ContextMenu() {
19
21
  const setLowLatency = (value) => {
20
22
  setProtocol(value ? player_store_1.PlayerProtocol.WEBRTC : player_store_1.PlayerProtocol.HLS);
21
23
  };
22
- return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Menu, { size: 32, color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsxs)(ui_1.ResponsiveDropdownMenuContent, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Show Debug Info" }) }) })] })] }));
24
+ return ((0, jsx_runtime_1.jsxs)(ui_1.DropdownMenu, { children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuTrigger, { children: (0, jsx_runtime_1.jsx)(lucide_react_native_1.Settings, { color: theme_1.colors.gray[200] }) }), (0, jsx_runtime_1.jsxs)(ui_1.ResponsiveDropdownMenuContent, { side: "top", align: "end", children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Resolution", children: (0, jsx_runtime_1.jsxs)(ui_1.DropdownMenuRadioGroup, { value: quality, onValueChange: setQuality, children: [(0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: "source", children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Source (Original Quality)" }) }), qualities.map((r) => ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuRadioItem, { value: r.name, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: r.name }) })))] }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Advanced", children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: lowLatency, onCheckedChange: () => setLowLatency(!lowLatency), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Low Latency" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuInfo, { description: "Reduces the delay between video and chat for a more real-time experience." }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { children: (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuCheckboxItem, { checked: debugInfo, onCheckedChange: () => setShowDebugInfo(!debugInfo), children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Show Debug Info" }) }) }), (0, jsx_runtime_1.jsx)(ui_1.DropdownMenuGroup, { title: "Report", children: (0, jsx_runtime_1.jsx)(ReportButton, {}) })] })] }));
25
+ }
26
+ function ReportButton() {
27
+ const livestream = (0, livestream_store_1.useLivestreamStore)((x) => x.livestream);
28
+ const setReportModalOpen = (0, player_store_1.usePlayerStore)((x) => x.setReportModalOpen);
29
+ const setReportSubject = (0, player_store_1.usePlayerStore)((x) => x.setReportSubject);
30
+ const { onOpenChange } = (0, dropdown_menu_1.useRootContext)();
31
+ return ((0, jsx_runtime_1.jsx)(ui_1.DropdownMenuItem, { onPress: () => {
32
+ if (!livestream)
33
+ return;
34
+ onOpenChange?.(false);
35
+ setReportModalOpen(true);
36
+ setReportSubject({
37
+ $type: "com.atproto.repo.strongRef",
38
+ uri: livestream.uri,
39
+ cid: livestream.cid,
40
+ });
41
+ }, children: (0, jsx_runtime_1.jsx)(ui_1.Text, { children: "Report Livestream..." }) }));
23
42
  }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ViewerLoadingOverlay = ViewerLoadingOverlay;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const lucide_react_native_1 = require("lucide-react-native");
7
+ const react_1 = require("react");
8
+ const react_native_reanimated_1 = tslib_1.__importStar(require("react-native-reanimated"));
9
+ const __1 = require("../../..");
10
+ function ViewerLoadingOverlay() {
11
+ const status = (0, __1.usePlayerStore)((x) => x.status);
12
+ const theme = (0, __1.useTheme)();
13
+ const opacity = (0, react_native_reanimated_1.useSharedValue)(0);
14
+ (0, react_1.useEffect)(() => {
15
+ if (status === __1.PlayerStatus.PLAYING || status === __1.PlayerStatus.SUSPEND) {
16
+ opacity.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 300 });
17
+ }
18
+ else {
19
+ opacity.value = (0, react_native_reanimated_1.withTiming)(1, { duration: 300 });
20
+ }
21
+ }, [status, opacity]);
22
+ const animatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
23
+ return {
24
+ opacity: opacity.value,
25
+ };
26
+ });
27
+ if (status === __1.PlayerStatus.PLAYING) {
28
+ return (0, jsx_runtime_1.jsx)(__1.KeepAwake, {});
29
+ }
30
+ if (status === __1.PlayerStatus.SUSPEND) {
31
+ return null; // No overlay when stopped
32
+ }
33
+ let spinner = (0, jsx_runtime_1.jsx)(__1.Loader, { size: "large" });
34
+ if (status === __1.PlayerStatus.PAUSE) {
35
+ spinner = (0, jsx_runtime_1.jsx)(lucide_react_native_1.Play, { size: "$12", color: theme.styles.text.primary["color"] });
36
+ }
37
+ return ((0, jsx_runtime_1.jsx)(react_native_reanimated_1.default.View, { style: [
38
+ {
39
+ position: "absolute",
40
+ width: "100%",
41
+ height: "100%",
42
+ zIndex: 998,
43
+ alignItems: "center",
44
+ justifyContent: "center",
45
+ backgroundColor: "rgba(0,0,0,0.3)",
46
+ },
47
+ animatedStyle,
48
+ ], children: spinner }));
49
+ }
@@ -9,6 +9,7 @@ const webrtc_primitives_1 = require("./webrtc-primitives");
9
9
  function useWebRTC(endpoint) {
10
10
  const [mediaStream, setMediaStream] = (0, react_1.useState)(null);
11
11
  const [stuck, setStuck] = (0, react_1.useState)(false);
12
+ const setStatus = (0, __1.usePlayerStore)((x) => x.setStatus);
12
13
  const lastChange = (0, react_1.useRef)(0);
13
14
  (0, react_1.useEffect)(() => {
14
15
  const peerConnection = new webrtc_primitives_1.RTCPeerConnection({
@@ -29,7 +30,10 @@ function useWebRTC(endpoint) {
29
30
  });
30
31
  peerConnection.addEventListener("connectionstatechange", () => {
31
32
  console.log("connection state change", peerConnection.connectionState);
32
- if (peerConnection.connectionState === "closed") {
33
+ if (peerConnection.connectionState === "closed" ||
34
+ peerConnection.connectionState === "failed" ||
35
+ peerConnection.connectionState === "disconnected") {
36
+ console.log("setting stuck to true", peerConnection.connectionState);
33
37
  setStuck(true);
34
38
  }
35
39
  if (peerConnection.connectionState !== "connected") {
@@ -50,6 +54,7 @@ function useWebRTC(endpoint) {
50
54
  if (lastAudioFramesReceived !== audioFramesReceived) {
51
55
  lastAudioFramesReceived = audioFramesReceived;
52
56
  lastChange.current = Date.now();
57
+ setStatus(__1.PlayerStatus.PLAYING);
53
58
  setStuck(false);
54
59
  }
55
60
  }
@@ -58,6 +63,7 @@ function useWebRTC(endpoint) {
58
63
  if (lastFramesReceived !== framesReceived) {
59
64
  lastFramesReceived = framesReceived;
60
65
  lastChange.current = Date.now();
66
+ setStatus(__1.PlayerStatus.PLAYING);
61
67
  setStuck(false);
62
68
  }
63
69
  }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = VideoRetry;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_1 = tslib_1.__importStar(require("react"));
7
+ const __1 = require("../..");
8
+ function VideoRetry(props) {
9
+ const retryTimeoutRef = (0, react_1.useRef)(null);
10
+ const [retries, setRetries] = (0, react_1.useState)(0);
11
+ const playing = (0, __1.usePlayerStore)((x) => x.status === __1.PlayerStatus.PLAYING);
12
+ (0, react_1.useEffect)(() => {
13
+ if (!playing) {
14
+ const jitter = 500 + Math.random() * 1500;
15
+ retryTimeoutRef.current = setTimeout(() => {
16
+ console.log("Retrying video playback...");
17
+ setRetries((prevRetries) => prevRetries + 1);
18
+ }, jitter);
19
+ }
20
+ return () => {
21
+ if (retryTimeoutRef.current) {
22
+ console.log("Clearing retry timeout");
23
+ clearTimeout(retryTimeoutRef.current);
24
+ retryTimeoutRef.current = null;
25
+ }
26
+ };
27
+ }, [!playing]);
28
+ return (0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: props.children }, retries);
29
+ }
@@ -14,6 +14,7 @@ const react_1 = require("react");
14
14
  const __1 = require("../..");
15
15
  const atoms_1 = require("../../lib/theme/atoms");
16
16
  const index_1 = require("../ui/index");
17
+ const loader_1 = require("../ui/loader");
17
18
  const shared_1 = require("./shared");
18
19
  const use_webrtc_1 = tslib_1.__importStar(require("./use-webrtc"));
19
20
  const webrtc_diagnostics_1 = require("./webrtc-diagnostics");
@@ -102,23 +103,56 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
102
103
  };
103
104
  const [firstAttempt, setFirstAttempt] = (0, react_1.useState)(true);
104
105
  const localVideoRef = props.videoRef ?? (0, react_1.useRef)(null);
106
+ // setPipAction comes from Zustand store
107
+ (0, react_1.useEffect)(() => {
108
+ if (typeof x.setPipAction === "function") {
109
+ const fn = () => {
110
+ if (localVideoRef.current) {
111
+ try {
112
+ localVideoRef.current.requestPictureInPicture?.();
113
+ }
114
+ catch (err) {
115
+ console.error("Error requesting Picture-in-Picture:", err);
116
+ }
117
+ }
118
+ else {
119
+ console.log("No video ref available for PiP");
120
+ }
121
+ };
122
+ x.setPipAction(fn);
123
+ }
124
+ // Cleanup on unmount
125
+ return () => {
126
+ if (typeof x.setPipAction === "function") {
127
+ x.setPipAction(undefined);
128
+ }
129
+ };
130
+ }, []);
105
131
  const canPlayThrough = (e) => {
132
+ console.log("canPlayThrough called", {
133
+ firstAttempt,
134
+ videoRef: !!localVideoRef.current,
135
+ });
136
+ setStatus(__1.PlayerStatus.PLAYING);
106
137
  event("canplaythrough")(e);
107
138
  if (firstAttempt && localVideoRef.current) {
108
139
  setFirstAttempt(false);
140
+ console.log("Attempting to play video");
109
141
  localVideoRef.current.play().catch((err) => {
142
+ console.log("error playing video", err.name);
110
143
  if (err.name === "NotAllowedError") {
111
144
  if (localVideoRef.current) {
145
+ console.log("Setting muted and retrying");
112
146
  setMuted(true);
113
147
  localVideoRef.current.muted = true;
114
148
  localVideoRef.current
115
149
  .play()
116
150
  .then(() => {
117
- console.warn("Browser forced video to start muted");
151
+ console.log("Muted play succeeded");
118
152
  setMuteWasForced(true);
119
153
  })
120
154
  .catch((err) => {
121
- console.error("error playing video", err);
155
+ console.error("Muted play also failed", err);
122
156
  });
123
157
  }
124
158
  }
@@ -148,15 +182,24 @@ const VideoElement = (0, react_1.forwardRef)((props, ref) => {
148
182
  ref.current =
149
183
  videoElement;
150
184
  }
151
- // if (localVideoRef && typeof localVideoRef !== "function") {
152
- // localVideoRef.current = videoElement;
153
- // }
185
+ localVideoRef.current = videoElement;
154
186
  };
155
- return ((0, jsx_runtime_1.jsx)("video", { autoPlay: true, playsInline: true, ref: handleVideoRef, controls: false, src: ingest ? undefined : props.url, muted: muted, crossOrigin: "anonymous", onMouseMove: setUserInteraction, onClick: setUserInteraction, onAbort: event("abort"), onCanPlay: event("canplay"), onCanPlayThrough: canPlayThrough, onEmptied: event("emptied"), onEncrypted: event("encrypted"), onEnded: event("ended"), onError: event("error"), onLoadedData: event("loadeddata"), onLoadedMetadata: event("loadedmetadata"), onLoadStart: event("loadstart"), onPause: event("pause"), onPlay: event("play"), onPlaying: event("playing"), onRateChange: event("ratechange"), onSeeked: event("seeked"), onSeeking: event("seeking"), onStalled: event("stalled"), onSuspend: event("suspend"), onVolumeChange: event("volumechange"), onWaiting: event("waiting"), style: {
187
+ const eventLogger = (evType) => (e) => {
188
+ console.log("📺 Video event:", evType);
189
+ const now = new Date();
190
+ if (updateEvents[evType]) {
191
+ x.setStatus(evType);
192
+ }
193
+ console.log("Sending", evType, "status to", url);
194
+ playerEvent(url, now.toISOString(), evType, {});
195
+ };
196
+ return ((0, jsx_runtime_1.jsx)("video", { autoPlay: true, playsInline: true, ref: handleVideoRef, controls: false, src: ingest ? undefined : props.url, muted: muted, crossOrigin: "anonymous", onMouseMove: setUserInteraction, onClick: setUserInteraction, onAbort: event("abort"), onCanPlay: eventLogger, onCanPlayThroughCapture: eventLogger, onCanPlayThrough: canPlayThrough, onEmptied: event("emptied"), onEncrypted: event("encrypted"), onEnded: event("ended"), onError: event("error"), onLoadedData: event("loadeddata"), onLoadedMetadata: event("loadedmetadata"), onLoadStart: event("loadstart"), onPause: event("pause"), onPlay: event("play"), onPlaying: event("playing"), onRateChange: event("ratechange"), onSeeked: event("seeked"), onSeeking: event("seeking"), onStalled: event("stalled"), onSuspend: event("suspend"), onVolumeChange: event("volumechange"), onWaiting: event("waiting"), style: {
156
197
  objectFit: "contain",
157
198
  backgroundColor: "transparent",
158
199
  width: "100%",
159
200
  height: "100%",
201
+ maxWidth: "100%",
202
+ maxHeight: "100%",
160
203
  transform: ingest ? "scaleX(-1)" : undefined,
161
204
  } }));
162
205
  });
@@ -238,6 +281,14 @@ function WebRTCPlayer(props) {
238
281
  }
239
282
  return (0, jsx_runtime_1.jsx)(WebRTCPlayerInner, { url: props.url, videoRef: props.videoRef });
240
283
  }
284
+ const connectionStatusMessages = {
285
+ initializing: "Starting up...",
286
+ connecting: "Connecting...",
287
+ "connection-failed": "Connecting...",
288
+ connected: "Connected",
289
+ reconnecting: "Reconnecting...",
290
+ checking: "Checking connection...",
291
+ };
241
292
  function WebRTCPlayerInner({ videoRef, url, width, height, }) {
242
293
  const [connectionStatus, setConnectionStatus] = (0, react_1.useState)("initializing");
243
294
  const status = (0, __1.usePlayerStore)((x) => x.status);
@@ -260,7 +311,7 @@ function WebRTCPlayerInner({ videoRef, url, width, height, }) {
260
311
  if (stuck && status === __1.PlayerStatus.PLAYING) {
261
312
  setStatus(__1.PlayerStatus.STALLED);
262
313
  }
263
- if (!stuck && mediaStream) {
314
+ if (!stuck && status === __1.PlayerStatus.STALLED) {
264
315
  setStatus(__1.PlayerStatus.PLAYING);
265
316
  }
266
317
  }, [stuck, status, mediaStream]);
@@ -303,7 +354,19 @@ function WebRTCPlayerInner({ videoRef, url, width, height, }) {
303
354
  videoRef.current.srcObject = mediaStream;
304
355
  }, [mediaStream]);
305
356
  if (!mediaStream) {
306
- return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", style: { minWidth: "100%", minHeight: "100%" }, children: (0, jsx_runtime_1.jsxs)(index_1.View, { backgroundColor: atoms_1.colors.primary[800], style: { borderRadius: atoms_1.borderRadius.md }, children: [(0, jsx_runtime_1.jsx)(index_1.View, { children: (0, jsx_runtime_1.jsx)(index_1.Text, { children: "Connecting..." }) }), (0, jsx_runtime_1.jsxs)(index_1.Text, { children: ["Establishing WebRTC connection (", connectionStatus, ")"] })] }) }));
357
+ const isError = connectionStatus === "connection-failed";
358
+ return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", style: {
359
+ minWidth: "100%",
360
+ minHeight: "100%",
361
+ display: "flex",
362
+ alignItems: "center",
363
+ justifyContent: "center",
364
+ }, children: (0, jsx_runtime_1.jsxs)(index_1.View, { style: {
365
+ borderRadius: atoms_1.borderRadius.md,
366
+ padding: 24,
367
+ alignItems: "center",
368
+ gap: 16,
369
+ }, children: [!isError && (0, jsx_runtime_1.jsx)(loader_1.Loader, { size: "large" }), (0, jsx_runtime_1.jsx)(index_1.Text, { size: "lg", weight: "semibold", children: connectionStatusMessages[connectionStatus] || "Connecting..." })] }) }));
307
370
  }
308
371
  return (0, jsx_runtime_1.jsx)(VideoElement, { url: url, ref: videoRef });
309
372
  }
@@ -379,7 +442,19 @@ function WebcamIngestPlayer(props) {
379
442
  videoElement.srcObject = localMediaStream;
380
443
  }, [videoElement, localMediaStream]);
381
444
  if (error) {
382
- return ((0, jsx_runtime_1.jsxs)(index_1.View, { backgroundColor: atoms_1.colors.destructive[900], style: [atoms_1.p[4], { borderRadius: atoms_1.borderRadius.md }], children: [(0, jsx_runtime_1.jsx)(index_1.View, { children: (0, jsx_runtime_1.jsx)(index_1.Text, { size: "xl", weight: "extrabold", children: "Error encountered!" }) }), (0, jsx_runtime_1.jsx)(index_1.Text, { children: error.message })] }));
445
+ return ((0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: "#111", style: {
446
+ minWidth: "100%",
447
+ minHeight: "100%",
448
+ display: "flex",
449
+ alignItems: "center",
450
+ justifyContent: "center",
451
+ }, children: (0, jsx_runtime_1.jsx)(index_1.View, { backgroundColor: atoms_1.colors.destructive[900], style: {
452
+ borderRadius: atoms_1.borderRadius.md,
453
+ padding: 24,
454
+ alignItems: "center",
455
+ gap: 16,
456
+ maxWidth: 400,
457
+ }, children: (0, jsx_runtime_1.jsx)(index_1.Text, { size: "xl", weight: "extrabold", color: "default", children: error.message }) }) }));
383
458
  }
384
459
  return (0, jsx_runtime_1.jsx)(VideoElement, { ...props, ref: handleRef });
385
460
  }