@limrun/ui 0.4.0-rc.10 → 0.4.0-rc.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limrun/ui",
3
- "version": "0.4.0-rc.10",
3
+ "version": "0.4.0-rc.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -35,11 +35,28 @@
35
35
 
36
36
  .rc-video {
37
37
  position: absolute;
38
- width: auto;
38
+ /* Size is controlled by inline style; defaults are for frameless mode */
39
+ width: 100%;
40
+ height: 100%;
39
41
  outline: none;
40
42
  pointer-events: none;
41
43
  cursor: none;
42
44
  background-color: black;
45
+ object-fit: contain;
46
+ object-position: center center;
47
+ inset: 0;
48
+
49
+ top: 50%;
50
+ left: 50%;
51
+ transform: translate(-50%, -50%);
52
+ right: auto;
53
+ bottom: auto;
54
+ }
55
+
56
+ .rc-video-ios-stretch {
57
+ /* iOS-only: intentionally stretch to fill the target screen box */
58
+ object-fit: fill;
59
+ z-index: 30;
43
60
  }
44
61
 
45
62
  .rc-video-frameless {
@@ -47,6 +64,7 @@
47
64
  height: 100%;
48
65
  top: auto;
49
66
  left: auto;
67
+ width: 100%;
50
68
  }
51
69
 
52
70
  .rc-video-loading {
@@ -96,8 +96,8 @@ type DeviceConfig = {
96
96
  loadingLogo: string;
97
97
  loadingLogoSize: string;
98
98
  videoPosition: {
99
- portrait: { top: number; left: number; height: number; };
100
- landscape: { top: number; left: number; width: number; };
99
+ portrait: { heightMultiplier?: number; widthMultiplier?: number; };
100
+ landscape: { heightMultiplier?: number; widthMultiplier?: number; };
101
101
  };
102
102
  frame: {
103
103
  image: string;
@@ -118,8 +118,8 @@ const deviceConfig: Record<DevicePlatform, DeviceConfig> = {
118
118
  loadingLogoSize: '20%',
119
119
  // Video position as percentage of frame dimensions
120
120
  videoPosition: {
121
- portrait: { top: 1.61, left: 3.6, height: 96.78 },
122
- landscape: { top: 3.9, left: 1.61, width: 96.78 },
121
+ portrait: { heightMultiplier: 0.9678 },
122
+ landscape: { widthMultiplier: 0.9678 },
123
123
  },
124
124
  },
125
125
  android: {
@@ -132,8 +132,8 @@ const deviceConfig: Record<DevicePlatform, DeviceConfig> = {
132
132
  loadingLogoSize: '40%',
133
133
  // Video position as percentage of frame dimensions
134
134
  videoPosition: {
135
- portrait: { top: 2.1, left: 4.5, height: 96.2 },
136
- landscape: { top: 5, left: 2.25, width: 95.9 },
135
+ portrait: { heightMultiplier: 0.967 },
136
+ landscape: { widthMultiplier: 0.962 },
137
137
  },
138
138
  },
139
139
  };
@@ -819,9 +819,9 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
819
819
  useEffect(() => {
820
820
  const video = videoRef.current;
821
821
  const frame = frameRef.current;
822
-
822
+
823
823
  if (!video) return;
824
-
824
+
825
825
  // If no frame, no positioning needed
826
826
  if (!showFrame || !frame) {
827
827
  setVideoStyle({});
@@ -831,34 +831,25 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
831
831
  const updateVideoPosition = () => {
832
832
  const frameWidth = frame.clientWidth;
833
833
  const frameHeight = frame.clientHeight;
834
-
834
+
835
835
  if (frameWidth === 0 || frameHeight === 0) return;
836
-
836
+
837
837
  // Determine landscape based on video's intrinsic dimensions
838
838
  const landscape = video.videoWidth > video.videoHeight;
839
839
  setIsLandscape(landscape);
840
-
840
+
841
841
  const pos = landscape ? config.videoPosition.landscape : config.videoPosition.portrait;
842
-
843
- // Calculate position in pixels based on frame dimensions
844
- const topPx = (pos.top / 100) * frameHeight;
845
- const leftPx = (pos.left / 100) * frameWidth;
846
-
847
- let newStyle: React.CSSProperties = {
848
- top: `${topPx}px`,
849
- left: `${leftPx}px`,
850
- };
851
-
852
- if ('height' in pos) {
853
- const heightPx = (pos.height / 100) * frameHeight;
854
- newStyle.height = `${heightPx}px`;
855
- newStyle.borderRadius = `${frameWidth * config.videoBorderRadiusMultiplier}px`;
856
- } else if ('width' in pos) {
857
- const widthPx = (pos.width / 100) * frameWidth;
858
- newStyle.width = `${widthPx}px`;
859
- newStyle.borderRadius = `${frameHeight * config.videoBorderRadiusMultiplier}px`;
842
+ let newStyle: React.CSSProperties = {};
843
+ if (pos.heightMultiplier) {
844
+ newStyle.height = `${frameHeight * pos.heightMultiplier}px`;
845
+ // Let the other dimension follow the video stream's intrinsic aspect ratio.
846
+ newStyle.width = 'auto';
847
+ } else if (pos.widthMultiplier) {
848
+ newStyle.width = `${frameWidth * pos.widthMultiplier}px`;
849
+ // Let the other dimension follow the video stream's intrinsic aspect ratio.
850
+ newStyle.height = 'auto';
860
851
  }
861
-
852
+ newStyle.borderRadius = `${landscape ? frameHeight * config.videoBorderRadiusMultiplier : frameWidth * config.videoBorderRadiusMultiplier}px`;
862
853
  setVideoStyle(newStyle);
863
854
  };
864
855
 
@@ -868,19 +859,25 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
868
859
 
869
860
  resizeObserver.observe(frame);
870
861
  resizeObserver.observe(video);
871
-
862
+
872
863
  // Also update when the frame image loads
873
864
  frame.addEventListener('load', updateVideoPosition);
874
-
865
+
875
866
  // Update when video metadata loads (to get correct intrinsic dimensions)
876
867
  video.addEventListener('loadedmetadata', updateVideoPosition);
877
868
 
869
+ // IMPORTANT: When the WebRTC stream changes orientation, the intrinsic video size
870
+ // (videoWidth/videoHeight) can change without re-firing 'loadedmetadata'.
871
+ // The <video> element emits 'resize' in that case.
872
+ video.addEventListener('resize', updateVideoPosition);
873
+
878
874
  // Initial calculation
879
875
  updateVideoPosition();
880
876
 
881
877
  return () => {
882
878
  resizeObserver.disconnect();
883
879
  video.removeEventListener('loadedmetadata', updateVideoPosition);
880
+ video.removeEventListener('resize', updateVideoPosition);
884
881
  frame.removeEventListener('load', updateVideoPosition);
885
882
  };
886
883
  }, [config, showFrame]);
@@ -1026,6 +1023,7 @@ export const RemoteControl = forwardRef<RemoteControlHandle, RemoteControlProps>
1026
1023
  className={clsx(
1027
1024
  'rc-video',
1028
1025
  !showFrame && 'rc-video-frameless',
1026
+ showFrame && platform === 'ios' && 'rc-video-ios-stretch',
1029
1027
  !videoLoaded && 'rc-video-loading',
1030
1028
  )}
1031
1029
  style={{
package/src/demo.tsx CHANGED
@@ -3,7 +3,7 @@ import { createRoot } from 'react-dom/client';
3
3
  import { RemoteControl, RemoteControlHandle } from './components/remote-control';
4
4
 
5
5
  function Demo() {
6
- const [url, setUrl] = useState('wss://eu-hel1-5-staging.limrun.dev/v1/android_eustg_01kd8ayyfhfk8tq0a0ryap6tet/endpointWebSocket');
6
+ const [url, setUrl] = useState('ws://localhost:8833/signaling');
7
7
  const [token, setToken] = useState('lim_44530d73085dc139b2ebe1a07658bd11af2ec422ddd2ceb7');
8
8
  const [platform, setPlatform] = useState<'ios' | 'android'>('ios');
9
9
  const [isConnected, setIsConnected] = useState(false);