@@ -82,42 +82,75 @@ export default function ProductSearch() {
8282 const filterCameras = async ( devices : MediaDeviceInfo [ ] ) : Promise < MediaDeviceInfo [ ] > => {
8383 const filtered : MediaDeviceInfo [ ] = [ ] ;
8484
85+ // First, try to get proper camera permissions to ensure device labels are available
86+ let permissionGranted = false ;
87+ try {
88+ const stream = await navigator . mediaDevices . getUserMedia ( { video : true } ) ;
89+ stream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
90+ permissionGranted = true ;
91+
92+ // Re-enumerate devices after permission is granted to get proper labels
93+ const devicesWithLabels = await navigator . mediaDevices . enumerateDevices ( ) ;
94+ const videoDevices = devicesWithLabels . filter ( device => device . kind === 'videoinput' ) ;
95+
96+ // Use the devices with proper labels if available
97+ if ( videoDevices . length > 0 ) {
98+ devices = videoDevices ;
99+ }
100+ } catch {
101+ // Permission denied or not available, continue with existing devices
102+ }
103+
85104 for ( const device of devices ) {
86105 try {
87- // Get camera capabilities to determine facing mode
88- const stream = await navigator . mediaDevices . getUserMedia ( {
89- video : { deviceId : device . deviceId }
90- } ) ;
91-
92- const track = stream . getVideoTracks ( ) [ 0 ] ;
93- const capabilities = track . getCapabilities ( ) ;
94- const settings = track . getSettings ( ) ;
95-
96- // Stop the stream immediately
97- stream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
98-
99- // Check for facing mode or camera type in label
100- const label = device . label . toLowerCase ( ) ;
101- const facingMode = settings . facingMode || capabilities . facingMode ;
102-
103- // Include back camera (main camera)
104- if ( facingMode === 'environment' ||
105- label . includes ( 'back' ) ||
106- label . includes ( 'rear' ) ||
107- ( ! label . includes ( 'front' ) && ! label . includes ( 'selfie' ) && devices . indexOf ( device ) === 0 ) ) {
108- filtered . push ( device ) ;
109- }
110- // Include front camera
111- else if ( facingMode === 'user' ||
112- label . includes ( 'front' ) ||
113- label . includes ( 'selfie' ) ) {
114- filtered . push ( device ) ;
115- }
116- // Include ultrawide camera
117- else if ( label . includes ( 'ultra' ) ||
118- label . includes ( 'wide' ) ||
119- label . includes ( '0.5' ) ) {
120- filtered . push ( device ) ;
106+ // Get camera capabilities to determine facing mode (only if permission granted)
107+ if ( permissionGranted ) {
108+ const stream = await navigator . mediaDevices . getUserMedia ( {
109+ video : { deviceId : device . deviceId }
110+ } ) ;
111+
112+ const track = stream . getVideoTracks ( ) [ 0 ] ;
113+ const capabilities = track . getCapabilities ( ) ;
114+ const settings = track . getSettings ( ) ;
115+
116+ // Stop the stream immediately
117+ stream . getTracks ( ) . forEach ( track => track . stop ( ) ) ;
118+
119+ // Check for facing mode or camera type in label
120+ const label = device . label . toLowerCase ( ) ;
121+ const facingMode = settings . facingMode || capabilities . facingMode ;
122+
123+ // Include back camera (main camera)
124+ if ( facingMode === 'environment' ||
125+ label . includes ( 'back' ) ||
126+ label . includes ( 'rear' ) ||
127+ ( ! label . includes ( 'front' ) && ! label . includes ( 'selfie' ) && devices . indexOf ( device ) === 0 ) ) {
128+ filtered . push ( device ) ;
129+ }
130+ // Include front camera
131+ else if ( facingMode === 'user' ||
132+ label . includes ( 'front' ) ||
133+ label . includes ( 'selfie' ) ) {
134+ filtered . push ( device ) ;
135+ }
136+ // Include ultrawide camera
137+ else if ( label . includes ( 'ultra' ) ||
138+ label . includes ( 'wide' ) ||
139+ label . includes ( '0.5' ) ) {
140+ filtered . push ( device ) ;
141+ }
142+ } else {
143+ // Fallback: use label-based filtering without stream access
144+ const label = device . label . toLowerCase ( ) ;
145+ if ( label . includes ( 'back' ) ||
146+ label . includes ( 'rear' ) ||
147+ label . includes ( 'front' ) ||
148+ label . includes ( 'selfie' ) ||
149+ label . includes ( 'ultra' ) ||
150+ label . includes ( 'wide' ) ||
151+ ( ! label . includes ( 'front' ) && devices . indexOf ( device ) === 0 ) ) {
152+ filtered . push ( device ) ;
153+ }
121154 }
122155 } catch {
123156 // If we can't get capabilities, use label-based filtering
@@ -134,6 +167,24 @@ export default function ProductSearch() {
134167 }
135168 }
136169
170+ // Mobile device fallback: if we have multiple devices but filtering returned few,
171+ // and no permissions were granted, include more devices
172+ if ( filtered . length < 2 && devices . length >= 2 && ! permissionGranted ) {
173+ // On mobile, typically the first device is back camera, second is front
174+ filtered . length = 0 ; // Clear filtered array
175+ filtered . push ( devices [ 0 ] ) ; // Back camera (usually)
176+ if ( devices . length > 1 ) {
177+ filtered . push ( devices [ 1 ] ) ; // Front camera (usually)
178+ }
179+ // Add any additional cameras that might be ultrawide
180+ for ( let i = 2 ; i < devices . length ; i ++ ) {
181+ const label = devices [ i ] . label . toLowerCase ( ) ;
182+ if ( label . includes ( 'ultra' ) || label . includes ( 'wide' ) || label . includes ( '0.5' ) ) {
183+ filtered . push ( devices [ i ] ) ;
184+ }
185+ }
186+ }
187+
137188 // If no cameras matched our criteria, fall back to first available camera
138189 if ( filtered . length === 0 && devices . length > 0 ) {
139190 filtered . push ( devices [ 0 ] ) ;
0 commit comments