@venomousone/rn-videokit 0.1.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 (145) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +248 -0
  3. package/lib/module/components/VideoKit.js +347 -0
  4. package/lib/module/components/VideoKit.js.map +1 -0
  5. package/lib/module/components/controls/FullscreenButton.js +38 -0
  6. package/lib/module/components/controls/FullscreenButton.js.map +1 -0
  7. package/lib/module/components/controls/Icon.js +71 -0
  8. package/lib/module/components/controls/Icon.js.map +1 -0
  9. package/lib/module/components/controls/PlayPauseButton.js +33 -0
  10. package/lib/module/components/controls/PlayPauseButton.js.map +1 -0
  11. package/lib/module/components/controls/Scrubber.js +146 -0
  12. package/lib/module/components/controls/Scrubber.js.map +1 -0
  13. package/lib/module/components/controls/SpeedButton.js +96 -0
  14. package/lib/module/components/controls/SpeedButton.js.map +1 -0
  15. package/lib/module/components/controls/TimeDisplay.js +28 -0
  16. package/lib/module/components/controls/TimeDisplay.js.map +1 -0
  17. package/lib/module/components/controls/VolumeButton.js +31 -0
  18. package/lib/module/components/controls/VolumeButton.js.map +1 -0
  19. package/lib/module/components/core/VideoPlayer.js +114 -0
  20. package/lib/module/components/core/VideoPlayer.js.map +1 -0
  21. package/lib/module/components/core/VideoPlayerContext.js +119 -0
  22. package/lib/module/components/core/VideoPlayerContext.js.map +1 -0
  23. package/lib/module/components/core/index.js +5 -0
  24. package/lib/module/components/core/index.js.map +1 -0
  25. package/lib/module/components/index.js +14 -0
  26. package/lib/module/components/index.js.map +1 -0
  27. package/lib/module/components/overlays/BufferingOverlay.js +24 -0
  28. package/lib/module/components/overlays/BufferingOverlay.js.map +1 -0
  29. package/lib/module/components/overlays/DoubleTapSeek.js +95 -0
  30. package/lib/module/components/overlays/DoubleTapSeek.js.map +1 -0
  31. package/lib/module/components/overlays/ErrorOverlay.js +60 -0
  32. package/lib/module/components/overlays/ErrorOverlay.js.map +1 -0
  33. package/lib/module/components/overlays/GestureIndicator.js +118 -0
  34. package/lib/module/components/overlays/GestureIndicator.js.map +1 -0
  35. package/lib/module/components/overlays/LoadingPoster.js +22 -0
  36. package/lib/module/components/overlays/LoadingPoster.js.map +1 -0
  37. package/lib/module/hooks/index.js +6 -0
  38. package/lib/module/hooks/index.js.map +1 -0
  39. package/lib/module/hooks/useVideoBrightness.js +33 -0
  40. package/lib/module/hooks/useVideoBrightness.js.map +1 -0
  41. package/lib/module/hooks/useVideoControls.js +64 -0
  42. package/lib/module/hooks/useVideoControls.js.map +1 -0
  43. package/lib/module/hooks/useVideoOrientation.js +24 -0
  44. package/lib/module/hooks/useVideoOrientation.js.map +1 -0
  45. package/lib/module/hooks/useVideoPlayer.js +59 -0
  46. package/lib/module/hooks/useVideoPlayer.js.map +1 -0
  47. package/lib/module/hooks/useVideoVolume.js +42 -0
  48. package/lib/module/hooks/useVideoVolume.js.map +1 -0
  49. package/lib/module/index.js +7 -0
  50. package/lib/module/index.js.map +1 -0
  51. package/lib/module/package.json +1 -0
  52. package/lib/module/types/index.js +4 -0
  53. package/lib/module/types/index.js.map +1 -0
  54. package/lib/module/utils/clamp.js +8 -0
  55. package/lib/module/utils/clamp.js.map +1 -0
  56. package/lib/module/utils/formatTime.js +12 -0
  57. package/lib/module/utils/formatTime.js.map +1 -0
  58. package/lib/module/utils/index.js +5 -0
  59. package/lib/module/utils/index.js.map +1 -0
  60. package/lib/typescript/package.json +1 -0
  61. package/lib/typescript/src/components/VideoKit.d.ts +3 -0
  62. package/lib/typescript/src/components/VideoKit.d.ts.map +1 -0
  63. package/lib/typescript/src/components/controls/FullscreenButton.d.ts +6 -0
  64. package/lib/typescript/src/components/controls/FullscreenButton.d.ts.map +1 -0
  65. package/lib/typescript/src/components/controls/Icon.d.ts +10 -0
  66. package/lib/typescript/src/components/controls/Icon.d.ts.map +1 -0
  67. package/lib/typescript/src/components/controls/PlayPauseButton.d.ts +2 -0
  68. package/lib/typescript/src/components/controls/PlayPauseButton.d.ts.map +1 -0
  69. package/lib/typescript/src/components/controls/Scrubber.d.ts +7 -0
  70. package/lib/typescript/src/components/controls/Scrubber.d.ts.map +1 -0
  71. package/lib/typescript/src/components/controls/SpeedButton.d.ts +2 -0
  72. package/lib/typescript/src/components/controls/SpeedButton.d.ts.map +1 -0
  73. package/lib/typescript/src/components/controls/TimeDisplay.d.ts +2 -0
  74. package/lib/typescript/src/components/controls/TimeDisplay.d.ts.map +1 -0
  75. package/lib/typescript/src/components/controls/VolumeButton.d.ts +2 -0
  76. package/lib/typescript/src/components/controls/VolumeButton.d.ts.map +1 -0
  77. package/lib/typescript/src/components/core/VideoPlayer.d.ts +14 -0
  78. package/lib/typescript/src/components/core/VideoPlayer.d.ts.map +1 -0
  79. package/lib/typescript/src/components/core/VideoPlayerContext.d.ts +48 -0
  80. package/lib/typescript/src/components/core/VideoPlayerContext.d.ts.map +1 -0
  81. package/lib/typescript/src/components/core/index.d.ts +3 -0
  82. package/lib/typescript/src/components/core/index.d.ts.map +1 -0
  83. package/lib/typescript/src/components/index.d.ts +6 -0
  84. package/lib/typescript/src/components/index.d.ts.map +1 -0
  85. package/lib/typescript/src/components/overlays/BufferingOverlay.d.ts +2 -0
  86. package/lib/typescript/src/components/overlays/BufferingOverlay.d.ts.map +1 -0
  87. package/lib/typescript/src/components/overlays/DoubleTapSeek.d.ts +5 -0
  88. package/lib/typescript/src/components/overlays/DoubleTapSeek.d.ts.map +1 -0
  89. package/lib/typescript/src/components/overlays/ErrorOverlay.d.ts +6 -0
  90. package/lib/typescript/src/components/overlays/ErrorOverlay.d.ts.map +1 -0
  91. package/lib/typescript/src/components/overlays/GestureIndicator.d.ts +9 -0
  92. package/lib/typescript/src/components/overlays/GestureIndicator.d.ts.map +1 -0
  93. package/lib/typescript/src/components/overlays/LoadingPoster.d.ts +6 -0
  94. package/lib/typescript/src/components/overlays/LoadingPoster.d.ts.map +1 -0
  95. package/lib/typescript/src/hooks/index.d.ts +4 -0
  96. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  97. package/lib/typescript/src/hooks/useVideoBrightness.d.ts +5 -0
  98. package/lib/typescript/src/hooks/useVideoBrightness.d.ts.map +1 -0
  99. package/lib/typescript/src/hooks/useVideoControls.d.ts +11 -0
  100. package/lib/typescript/src/hooks/useVideoControls.d.ts.map +1 -0
  101. package/lib/typescript/src/hooks/useVideoOrientation.d.ts +6 -0
  102. package/lib/typescript/src/hooks/useVideoOrientation.d.ts.map +1 -0
  103. package/lib/typescript/src/hooks/useVideoPlayer.d.ts +7 -0
  104. package/lib/typescript/src/hooks/useVideoPlayer.d.ts.map +1 -0
  105. package/lib/typescript/src/hooks/useVideoVolume.d.ts +6 -0
  106. package/lib/typescript/src/hooks/useVideoVolume.d.ts.map +1 -0
  107. package/lib/typescript/src/index.d.ts +6 -0
  108. package/lib/typescript/src/index.d.ts.map +1 -0
  109. package/lib/typescript/src/types/index.d.ts +96 -0
  110. package/lib/typescript/src/types/index.d.ts.map +1 -0
  111. package/lib/typescript/src/utils/clamp.d.ts +2 -0
  112. package/lib/typescript/src/utils/clamp.d.ts.map +1 -0
  113. package/lib/typescript/src/utils/formatTime.d.ts +2 -0
  114. package/lib/typescript/src/utils/formatTime.d.ts.map +1 -0
  115. package/lib/typescript/src/utils/index.d.ts +3 -0
  116. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  117. package/package.json +191 -0
  118. package/src/components/VideoKit.tsx +415 -0
  119. package/src/components/controls/FullscreenButton.tsx +29 -0
  120. package/src/components/controls/Icon.tsx +71 -0
  121. package/src/components/controls/PlayPauseButton.tsx +25 -0
  122. package/src/components/controls/Scrubber.tsx +157 -0
  123. package/src/components/controls/SpeedButton.tsx +86 -0
  124. package/src/components/controls/TimeDisplay.tsx +21 -0
  125. package/src/components/controls/VolumeButton.tsx +23 -0
  126. package/src/components/core/VideoPlayer.tsx +148 -0
  127. package/src/components/core/VideoPlayerContext.tsx +133 -0
  128. package/src/components/core/index.ts +5 -0
  129. package/src/components/index.ts +25 -0
  130. package/src/components/overlays/BufferingOverlay.tsx +21 -0
  131. package/src/components/overlays/DoubleTapSeek.tsx +91 -0
  132. package/src/components/overlays/ErrorOverlay.tsx +49 -0
  133. package/src/components/overlays/GestureIndicator.tsx +114 -0
  134. package/src/components/overlays/LoadingPoster.tsx +21 -0
  135. package/src/hooks/index.ts +3 -0
  136. package/src/hooks/useVideoBrightness.ts +34 -0
  137. package/src/hooks/useVideoControls.ts +65 -0
  138. package/src/hooks/useVideoOrientation.ts +22 -0
  139. package/src/hooks/useVideoPlayer.ts +69 -0
  140. package/src/hooks/useVideoVolume.ts +36 -0
  141. package/src/index.ts +15 -0
  142. package/src/types/index.ts +137 -0
  143. package/src/utils/clamp.ts +4 -0
  144. package/src/utils/formatTime.ts +9 -0
  145. package/src/utils/index.ts +2 -0
@@ -0,0 +1,114 @@
1
+ import React, { forwardRef, useImperativeHandle, useCallback } from 'react';
2
+ import { StyleSheet, Text, View } from 'react-native';
3
+ import Animated, {
4
+ useSharedValue,
5
+ useAnimatedStyle,
6
+ withTiming,
7
+ } from 'react-native-reanimated';
8
+ import { scheduleOnRN } from 'react-native-worklets';
9
+
10
+ export type GestureIndicatorType = 'brightness' | 'volume';
11
+
12
+ export interface GestureIndicatorHandle {
13
+ show: (type: GestureIndicatorType, value: number) => void;
14
+ update: (value: number) => void;
15
+ hide: () => void;
16
+ }
17
+
18
+ export const GestureIndicator = forwardRef<GestureIndicatorHandle>((_, ref) => {
19
+ const opacity = useSharedValue(0);
20
+ const fillHeight = useSharedValue(0);
21
+
22
+ // JS state via ref to avoid re-renders
23
+ const typeRef = React.useRef<GestureIndicatorType>('brightness');
24
+ const [type, setType] = React.useState<GestureIndicatorType>('brightness');
25
+ const [value, setValue] = React.useState(0);
26
+ const hideTimerRef = React.useRef<ReturnType<typeof setTimeout>>(undefined);
27
+
28
+ const scheduleAutoHide = useCallback(() => {
29
+ clearTimeout(hideTimerRef.current);
30
+ hideTimerRef.current = setTimeout(() => {
31
+ opacity.value = withTiming(0, { duration: 300 });
32
+ }, 1200);
33
+ }, [opacity]);
34
+
35
+ useImperativeHandle(ref, () => ({
36
+ show: (t, v) => {
37
+ typeRef.current = t;
38
+ scheduleOnRN(() => {
39
+ setType(t);
40
+ setValue(v);
41
+ });
42
+ opacity.value = withTiming(1, { duration: 150 });
43
+ fillHeight.value = withTiming(v, { duration: 100 });
44
+ scheduleAutoHide();
45
+ },
46
+ update: (v) => {
47
+ scheduleOnRN(() => setValue(v));
48
+ fillHeight.value = withTiming(v, { duration: 80 });
49
+ scheduleAutoHide();
50
+ },
51
+ hide: () => {
52
+ clearTimeout(hideTimerRef.current);
53
+ opacity.value = withTiming(0, { duration: 200 });
54
+ },
55
+ }));
56
+
57
+ const containerStyle = useAnimatedStyle(() => ({
58
+ opacity: opacity.value,
59
+ }));
60
+
61
+ const fillStyle = useAnimatedStyle(() => ({
62
+ height: `${fillHeight.value * 100}%`,
63
+ }));
64
+
65
+ const icon = type === 'brightness' ? '☀️' : value === 0 ? '🔇' : '🔊';
66
+
67
+ return (
68
+ <Animated.View
69
+ style={[styles.container, containerStyle]}
70
+ pointerEvents="none"
71
+ >
72
+ <Text style={styles.icon}>{icon}</Text>
73
+ <View style={styles.track}>
74
+ <Animated.View style={[styles.fill, fillStyle]} />
75
+ </View>
76
+ <Text style={styles.value}>{Math.round(value * 100)}%</Text>
77
+ </Animated.View>
78
+ );
79
+ });
80
+
81
+ const styles = StyleSheet.create({
82
+ container: {
83
+ position: 'absolute',
84
+ top: '50%',
85
+ left: '50%',
86
+ transform: [{ translateX: -28 }, { translateY: -80 }],
87
+ alignItems: 'center',
88
+ gap: 6,
89
+ backgroundColor: 'rgba(0,0,0,0.65)',
90
+ borderRadius: 12,
91
+ paddingVertical: 10,
92
+ paddingHorizontal: 12,
93
+ width: 56,
94
+ },
95
+ icon: { fontSize: 18 },
96
+ track: {
97
+ width: 4,
98
+ height: 80,
99
+ backgroundColor: 'rgba(255,255,255,0.25)',
100
+ borderRadius: 2,
101
+ justifyContent: 'flex-end',
102
+ overflow: 'hidden',
103
+ },
104
+ fill: {
105
+ width: '100%',
106
+ backgroundColor: '#fff',
107
+ borderRadius: 2,
108
+ },
109
+ value: {
110
+ color: '#fff',
111
+ fontSize: 11,
112
+ fontWeight: '600',
113
+ },
114
+ });
@@ -0,0 +1,21 @@
1
+ import { Image, StyleSheet, View } from 'react-native';
2
+ import { useVideoStore } from '../core/VideoPlayerContext';
3
+
4
+ interface Props {
5
+ uri?: string;
6
+ }
7
+
8
+ export function LoadingPoster({ uri }: Props) {
9
+ const status = useVideoStore((s) => s.status);
10
+ if (!uri || status !== 'idle') return null;
11
+
12
+ return (
13
+ <View style={StyleSheet.absoluteFill}>
14
+ <Image
15
+ source={{ uri }}
16
+ style={StyleSheet.absoluteFill}
17
+ resizeMode="cover"
18
+ />
19
+ </View>
20
+ );
21
+ }
@@ -0,0 +1,3 @@
1
+ export { useVideoPlayer } from './useVideoPlayer';
2
+ export { useVideoControls } from './useVideoControls';
3
+ export { useVideoOrientation } from './useVideoOrientation';
@@ -0,0 +1,34 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import {
3
+ getBrightnessLevel,
4
+ setBrightnessLevel,
5
+ } from '@reeq/react-native-device-brightness';
6
+ import { clamp } from '../utils/clamp';
7
+
8
+ export function useVideoBrightness() {
9
+ const current = useRef<number | null>(null);
10
+
11
+ const init = useCallback(() => {
12
+ try {
13
+ current.current = getBrightnessLevel();
14
+ } catch (e) {
15
+ console.warn('[VideoKit] brightness init failed:', e);
16
+ }
17
+ }, []);
18
+
19
+ const adjust = useCallback((delta: number) => {
20
+ try {
21
+ // lazy init — read current brightness on first adjust
22
+ if (current.current === null) {
23
+ current.current = getBrightnessLevel();
24
+ }
25
+ const next = clamp(current.current + delta, 0, 1);
26
+ current.current = next;
27
+ setBrightnessLevel(next);
28
+ } catch (e) {
29
+ console.warn('[VideoKit] brightness adjust failed:', e);
30
+ }
31
+ }, []);
32
+
33
+ return { initBrightness: init, adjustBrightness: adjust };
34
+ }
@@ -0,0 +1,65 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import {
3
+ useVideoStore,
4
+ useVideoStoreApi,
5
+ } from '../components/core/VideoPlayerContext';
6
+
7
+ /**
8
+ * Manages controls visibility with an auto-hide timer.
9
+ * Call `showControls()` on any user interaction to reset the timer.
10
+ */
11
+ export function useVideoControls(autoHideDelayMs = 3000) {
12
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
13
+ const status = useVideoStore((s) => s.status);
14
+ const {} = useVideoStore((s) => s);
15
+ const controlsVisible = useVideoStore((s) => s.controlsVisible);
16
+ const store = useVideoStoreApi();
17
+
18
+ const clearTimer = useCallback(() => {
19
+ if (timerRef.current) {
20
+ clearTimeout(timerRef.current);
21
+ timerRef.current = null;
22
+ }
23
+ }, []);
24
+
25
+ const scheduleHide = useCallback(() => {
26
+ clearTimer();
27
+ timerRef.current = setTimeout(() => {
28
+ store.getState().setControlsVisible(false);
29
+ }, autoHideDelayMs);
30
+ }, [clearTimer, autoHideDelayMs, store]);
31
+
32
+ const showControls = useCallback(() => {
33
+ store.getState().setControlsVisible(true);
34
+ if (status === 'playing') {
35
+ scheduleHide();
36
+ }
37
+ }, [store, status, scheduleHide]);
38
+
39
+ const toggleControls = useCallback(() => {
40
+ store.getState().setControlsVisible(!controlsVisible);
41
+ }, [controlsVisible, store]);
42
+
43
+ const keepControlsVisible = useCallback(() => {
44
+ // Call this during scrub to cancel auto-hide
45
+ clearTimer();
46
+ store.getState().setControlsVisible(true);
47
+ }, [clearTimer, store]);
48
+
49
+ // When playback starts, begin the hide countdown
50
+ useEffect(() => {
51
+ if (status === 'playing' && controlsVisible) {
52
+ clearTimer();
53
+ scheduleHide();
54
+ } else if (status !== 'playing' && controlsVisible) {
55
+ clearTimer();
56
+ scheduleHide();
57
+ } else {
58
+ clearTimer();
59
+ scheduleHide();
60
+ }
61
+ return clearTimer;
62
+ }, [status, controlsVisible, scheduleHide, clearTimer]);
63
+
64
+ return { controlsVisible, showControls, keepControlsVisible, toggleControls };
65
+ }
@@ -0,0 +1,22 @@
1
+ import { useEffect } from 'react';
2
+ import Orientation from 'react-native-orientation-locker';
3
+ import { useVideoStore } from '../components/core/VideoPlayerContext';
4
+
5
+ /**
6
+ * Locks/unlocks screen orientation in response to fullscreen state.
7
+ * Fullscreen → landscape; exit → portrait.
8
+ */
9
+ export function useVideoOrientation() {
10
+ const isFullscreen = useVideoStore((s) => s.isFullscreen);
11
+
12
+ useEffect(() => {
13
+ if (isFullscreen) {
14
+ Orientation.lockToLandscape();
15
+ } else {
16
+ Orientation.lockToPortrait();
17
+ }
18
+ return () => {
19
+ Orientation.unlockAllOrientations();
20
+ };
21
+ }, [isFullscreen]);
22
+ }
@@ -0,0 +1,69 @@
1
+ import { useCallback } from 'react';
2
+ import { useVideoStore } from '../components/core/VideoPlayerContext';
3
+ import { useVideoPlayerContext } from '../components/core/VideoPlayerContext';
4
+ import type { VideoPlayerAPI, VideoPlayerState } from '../types';
5
+
6
+ /**
7
+ * Headless hook. Use this when you want to build your own UI
8
+ * while delegating all playback state to rn-videokit.
9
+ */
10
+ export function useVideoPlayer(): VideoPlayerState & VideoPlayerAPI {
11
+ const { videoRef } = useVideoPlayerContext();
12
+
13
+ const status = useVideoStore((s) => s.status);
14
+ const currentTime = useVideoStore((s) => s.currentTime);
15
+ const duration = useVideoStore((s) => s.duration);
16
+ const buffered = useVideoStore((s) => s.buffered);
17
+ const muted = useVideoStore((s) => s.muted);
18
+ const speed = useVideoStore((s) => s.speed);
19
+ const isFullscreen = useVideoStore((s) => s.isFullscreen);
20
+ const error = useVideoStore((s) => s.error);
21
+
22
+ const { setMuted, setSpeed, setFullscreen, setStatus } = useVideoStore(
23
+ (s) => s
24
+ );
25
+
26
+ const play = useCallback(() => {
27
+ setStatus('playing');
28
+ }, [setStatus]);
29
+
30
+ const pause = useCallback(() => {
31
+ setStatus('paused');
32
+ }, [setStatus]);
33
+
34
+ const seek = useCallback(
35
+ (seconds: number) => {
36
+ videoRef?.current?.seek(seconds);
37
+ },
38
+ [videoRef]
39
+ );
40
+
41
+ const enterFullscreen = useCallback(
42
+ () => setFullscreen(true),
43
+ [setFullscreen]
44
+ );
45
+ const exitFullscreen = useCallback(
46
+ () => setFullscreen(false),
47
+ [setFullscreen]
48
+ );
49
+
50
+ return {
51
+ // State
52
+ status,
53
+ currentTime,
54
+ duration,
55
+ buffered,
56
+ muted,
57
+ speed,
58
+ isFullscreen,
59
+ error,
60
+ // API
61
+ play,
62
+ pause,
63
+ seek,
64
+ setMuted,
65
+ setSpeed,
66
+ enterFullscreen,
67
+ exitFullscreen,
68
+ };
69
+ }
@@ -0,0 +1,36 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { VolumeManager } from 'react-native-volume-manager';
3
+ import { clamp } from '../utils/clamp';
4
+
5
+ export function useVideoVolume() {
6
+ const currentVolume = useRef<number>(0.5);
7
+
8
+ const init = useCallback(async () => {
9
+ try {
10
+ const { volume } = await VolumeManager.getVolume();
11
+ currentVolume.current = volume;
12
+ } catch (e) {
13
+ console.warn('[VideoKit] volume init failed:', e);
14
+ }
15
+ }, []);
16
+
17
+ const adjustVolume = useCallback(async (delta: number) => {
18
+ try {
19
+ const next = clamp(currentVolume.current + delta, 0, 1);
20
+ currentVolume.current = next;
21
+ await VolumeManager.setVolume(next);
22
+ } catch (e) {
23
+ console.warn('[VideoKit] volume adjust failed:', e);
24
+ }
25
+ }, []);
26
+
27
+ useEffect(() => {
28
+ init();
29
+ const sub = VolumeManager.addVolumeListener(({ volume }) => {
30
+ currentVolume.current = volume;
31
+ });
32
+ return () => sub.remove();
33
+ }, [init]);
34
+
35
+ return { initVolume: init, adjustVolume, currentVolume };
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { VideoKit } from './components/VideoKit';
2
+ export { useVideoPlayer } from './hooks/useVideoPlayer';
3
+ export { useVideoStore } from './components/core/VideoPlayerContext';
4
+ export { VideoPlayerProvider } from './components/core/VideoPlayerContext';
5
+
6
+ export type {
7
+ VideoKitProps,
8
+ VideoKitTheme,
9
+ VideoKitControlsConfig,
10
+ VideoSource,
11
+ VideoPlayerAPI,
12
+ VideoPlayerState,
13
+ VideoProgress,
14
+ PlaybackStatus,
15
+ } from './types';
@@ -0,0 +1,137 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { VideoSrc } from 'react-native-video';
3
+
4
+ // ─── Source ──────────────────────────────────────────────────────────────────
5
+
6
+ export type VideoSource =
7
+ | VideoSrc
8
+ | { uri: string; headers?: Record<string, string> }
9
+ | number; // require('./local.mp4')
10
+
11
+ // ─── Theme ───────────────────────────────────────────────────────────────────
12
+
13
+ export interface VideoKitTheme {
14
+ /** Primary accent color (scrubber thumb, active icons). Default: '#ffffff' */
15
+ accentColor?: string;
16
+ /** Controls tint (icons, time text). Default: '#ffffff' */
17
+ controlsTint?: string;
18
+ /** Overlay background. Default: 'rgba(0,0,0,0.45)' */
19
+ overlayColor?: string;
20
+ /** Scrubber track height. Default: 3 */
21
+ trackHeight?: number;
22
+ }
23
+
24
+ // ─── Controls Config ─────────────────────────────────────────────────────────
25
+
26
+ export interface VideoKitControlsConfig {
27
+ /** Show play/pause button. Default: true */
28
+ playPause?: boolean;
29
+ /** Show scrubber. Default: true */
30
+ scrubber?: boolean;
31
+ /** Show time display (current / duration). Default: true */
32
+ time?: boolean;
33
+ /** Show mute/volume button. Default: true */
34
+ volume?: boolean;
35
+ /** Show fullscreen button. Default: true */
36
+ fullscreen?: boolean;
37
+ /** Show playback speed button. Default: true */
38
+ speed?: boolean;
39
+ }
40
+
41
+ // ─── Playback State ──────────────────────────────────────────────────────────
42
+
43
+ export type PlaybackStatus =
44
+ | 'idle'
45
+ | 'loading'
46
+ | 'buffering'
47
+ | 'playing'
48
+ | 'paused'
49
+ | 'error';
50
+
51
+ export interface VideoProgress {
52
+ currentTime: number;
53
+ playableDuration: number;
54
+ seekableDuration: number;
55
+ }
56
+
57
+ // ─── VideoKit Props ──────────────────────────────────────────────────────────
58
+
59
+ export interface VideoKitProps {
60
+ /** Video source — URI object or local require(). Required. */
61
+ source: VideoSource;
62
+
63
+ /** Poster image shown before first frame. */
64
+ poster?: string;
65
+
66
+ /** Start playing on mount. Default: false */
67
+ autoPlay?: boolean;
68
+
69
+ /** Loop playback. Default: false */
70
+ loop?: boolean;
71
+
72
+ /** Mute audio. Default: false */
73
+ muted?: boolean;
74
+
75
+ /** Called every 250 ms while playing. */
76
+ onProgress?: (progress: VideoProgress) => void;
77
+
78
+ /** Called when playback reaches the end. */
79
+ onEnd?: () => void;
80
+
81
+ /** Called when fullscreen toggle is triggered. */
82
+ onFullscreenChange?: (isFullscreen: boolean) => void;
83
+
84
+ /** Called when an error occurs. */
85
+ onError?: (error: Error) => void;
86
+
87
+ /** Called when buffering state changes. */
88
+ onBuffer?: (isBuffering: boolean) => void;
89
+
90
+ /** Fine-grained control over which controls render. */
91
+ controls?: VideoKitControlsConfig | boolean;
92
+
93
+ /** Visual theming overrides. */
94
+ theme?: VideoKitTheme;
95
+
96
+ /** Duration in ms before controls auto-hide. Default: 3000 */
97
+ autoHideDelay?: number;
98
+
99
+ /** Seek amount in seconds for double-tap. Default: 10 */
100
+ doubleTapSeekSeconds?: number;
101
+
102
+ /** Container style. */
103
+ style?: StyleProp<ViewStyle>;
104
+
105
+ /** Test ID for E2E. */
106
+ testID?: string;
107
+
108
+ safeAreaInsets?: {
109
+ top?: number;
110
+ bottom?: number;
111
+ left?: number;
112
+ right?: number;
113
+ };
114
+ }
115
+
116
+ // ─── Headless Hook API ───────────────────────────────────────────────────────
117
+
118
+ export interface VideoPlayerAPI {
119
+ play: () => void;
120
+ pause: () => void;
121
+ seek: (seconds: number) => void;
122
+ setMuted: (muted: boolean) => void;
123
+ setSpeed: (rate: number) => void;
124
+ enterFullscreen: () => void;
125
+ exitFullscreen: () => void;
126
+ }
127
+
128
+ export interface VideoPlayerState {
129
+ status: PlaybackStatus;
130
+ currentTime: number;
131
+ duration: number;
132
+ buffered: number;
133
+ muted: boolean;
134
+ speed: number;
135
+ isFullscreen: boolean;
136
+ error: string | null;
137
+ }
@@ -0,0 +1,4 @@
1
+ export const clamp = (value: number, min: number, max: number): number => {
2
+ 'worklet';
3
+ return Math.min(Math.max(value, min), max);
4
+ };
@@ -0,0 +1,9 @@
1
+ export const formatTime = (seconds: number): string => {
2
+ const h = Math.floor(seconds / 3600);
3
+ const m = Math.floor((seconds % 3600) / 60);
4
+ const s = Math.floor(seconds % 60);
5
+ if (h > 0) {
6
+ return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
7
+ }
8
+ return `${m}:${String(s).padStart(2, '0')}`;
9
+ };
@@ -0,0 +1,2 @@
1
+ export * from './clamp';
2
+ export * from './formatTime';