diff --git a/js/app/components/mobile/desktop-ui.tsx b/js/app/components/mobile/desktop-ui.tsx
index 7050fbba..37dbf2ef 100644
--- a/js/app/components/mobile/desktop-ui.tsx
+++ b/js/app/components/mobile/desktop-ui.tsx
@@ -1,4 +1,5 @@
import {
+ PlayerStatus,
PlayerUI,
PortalHost,
Toast,
@@ -26,6 +27,7 @@ import {
MuteOverlay,
TopControlBar,
} from "./desktop-ui/index";
+import { PlayPauseIndicator } from "./desktop-ui/play-pause-indicator";
import { useResponsiveLayout } from "./useResponsiveLayout";
const { h, layout, position, w, px, py, r, p } = zero;
@@ -72,6 +74,7 @@ export function DesktopUi({
const fullscreen = usePlayerStore((state) => state.fullscreen);
const setFullscreen = usePlayerStore((state) => state.setFullscreen);
const selectedRendition = usePlayerStore((state) => state.selectedRendition);
+ const status = usePlayerStore((state) => state.status);
const safeAreaInsets = embedded
? { ...originalSafeAreaInsets, top: 0 }
@@ -96,12 +99,13 @@ export function DesktopUi({
if (selectedRendition === "audio") return;
if (ingest !== null) return;
+ if (status === PlayerStatus.PAUSE) return;
fadeTimeout.current = setTimeout(() => {
fadeOpacity.value = withTiming(0, { duration: 400 });
setIsControlsVisible(false);
}, FADE_OUT_DELAY);
- }, [fadeOpacity, selectedRendition, ingest]);
+ }, [fadeOpacity, selectedRendition, ingest, status]);
const onPlayerHover = useCallback(() => {
resetFadeTimer();
@@ -225,8 +229,8 @@ export function DesktopUi({
collapsable={false}
>
-
+
)}
-
-
-
-
-
-
{isSelfAndNotLive && (
+
+
+
+
+
{fullscreen && }
>
);
diff --git a/js/app/components/mobile/desktop-ui/index.ts b/js/app/components/mobile/desktop-ui/index.ts
index 91c372da..3b8f0ba8 100644
--- a/js/app/components/mobile/desktop-ui/index.ts
+++ b/js/app/components/mobile/desktop-ui/index.ts
@@ -2,5 +2,6 @@ export { BottomControlBar } from "./bottom-controls";
export { KebabMenu } from "./kebab";
export { LiveBubble } from "./live-bubble";
export { MuteOverlay } from "./mute-overlay";
+export { PlayPauseIndicator } from "./play-pause-indicator";
export { TopControlBar } from "./top-controls";
export { VolumeSlider } from "./volume-slider";
diff --git a/js/app/components/mobile/desktop-ui/mute-overlay.tsx b/js/app/components/mobile/desktop-ui/mute-overlay.tsx
index 09ed23ff..565647da 100644
--- a/js/app/components/mobile/desktop-ui/mute-overlay.tsx
+++ b/js/app/components/mobile/desktop-ui/mute-overlay.tsx
@@ -35,6 +35,7 @@ export function MuteOverlay() {
h.percent[100],
w.percent[100],
]}
+ pointerEvents="box-none"
>
{
diff --git a/js/app/components/mobile/desktop-ui/play-pause-indicator.tsx b/js/app/components/mobile/desktop-ui/play-pause-indicator.tsx
new file mode 100644
index 00000000..1827414d
--- /dev/null
+++ b/js/app/components/mobile/desktop-ui/play-pause-indicator.tsx
@@ -0,0 +1,90 @@
+import { PlayerStatus, usePlayerStore } from "@streamplace/components";
+import { Pause, Play } from "lucide-react-native";
+import { useCallback, useEffect, useRef } from "react";
+import Animated, {
+ useAnimatedStyle,
+ useSharedValue,
+ withSequence,
+ withTiming,
+} from "react-native-reanimated";
+
+export function PlayPauseIndicator() {
+ const status = usePlayerStore((x) => x.status);
+ const prevStatus = useRef(status);
+ const opacity = useSharedValue(0);
+ const scale = useSharedValue(0.5);
+
+ const animate = useCallback(
+ (toPlaying: boolean) => {
+ opacity.value = 1;
+ opacity.value = withSequence(
+ withTiming(1, { duration: 100 }),
+ withTiming(1, { duration: 400 }),
+ withTiming(0, { duration: 300 }),
+ );
+ if (toPlaying) {
+ scale.value = 0.8;
+ scale.value = withSequence(
+ withTiming(1, { duration: 100 }),
+ withTiming(1, { duration: 400 }),
+ withTiming(1.3, { duration: 300 }),
+ );
+ } else {
+ scale.value = 1;
+ scale.value = withSequence(
+ withTiming(0.8, { duration: 100 }),
+ withTiming(0.8, { duration: 500 }),
+ );
+ }
+ },
+ [opacity, scale],
+ );
+
+ useEffect(() => {
+ if (status !== prevStatus.current) {
+ const wasPlaying = prevStatus.current === PlayerStatus.PLAYING;
+ const isPlaying = status === PlayerStatus.PLAYING;
+ const isPaused = status === PlayerStatus.PAUSE;
+
+ if (isPlaying || isPaused) {
+ animate(isPlaying);
+ }
+ prevStatus.current = status;
+ }
+ }, [status, animate]);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ opacity: opacity.value,
+ transform: [{ scale: scale.value }],
+ }));
+
+ const isPlaying = status === PlayerStatus.PLAYING;
+
+ return (
+
+ {isPlaying ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/js/components/src/components/mobile-player/ui/autoplay-button.tsx b/js/components/src/components/mobile-player/ui/autoplay-button.tsx
index 069558d3..8636e930 100644
--- a/js/components/src/components/mobile-player/ui/autoplay-button.tsx
+++ b/js/components/src/components/mobile-player/ui/autoplay-button.tsx
@@ -50,6 +50,7 @@ export function AutoplayButton() {
h.percent[100],
w.percent[100],
]}
+ pointerEvents="box-none"
>
x.status);
- const togglePlayPause = usePlayerStore((x) => x.togglePlayPause);
- const { theme, zero: zt } = useTheme();
const opacity = useSharedValue(0);
useEffect(() => {
@@ -28,60 +18,33 @@ export function ViewerLoadingOverlay() {
}
}, [status, opacity]);
- const animatedStyle = useAnimatedStyle(() => {
- return {
- opacity: opacity.value,
- };
- });
+ const animatedStyle = useAnimatedStyle(() => ({
+ opacity: opacity.value,
+ }));
if (status === PlayerStatus.PLAYING) {
return ;
}
- if (status === PlayerStatus.SUSPEND) {
- return null; // No overlay when stopped
+ if (status === PlayerStatus.SUSPEND || status === PlayerStatus.PAUSE) {
+ return null;
}
- const isPaused = status === PlayerStatus.PAUSE;
-
return (
- <>
-
- {!isPaused && }
-
- {isPaused && (
-
-
-
- )}
- >
+
+
+
);
}