Live video on the AT Protocol

Show specific error if camera or mic is missing + add 'open settings' button

+50 -5
+50 -5
js/components/src/components/mobile-player/video.native.tsx
··· 1 1 import { useVideoPlayer, VideoPlayerEvents, VideoView } from "expo-video"; 2 + import { ArrowRight } from "lucide-react-native"; 2 3 import { useCallback, useEffect, useRef, useState } from "react"; 3 - import { LayoutChangeEvent } from "react-native"; 4 + import { LayoutChangeEvent, Linking } from "react-native"; 4 5 import { 5 6 MediaStream, 6 7 RTCView, 7 8 RTCView as RTCViewIngest, 8 9 } from "react-native-webrtc"; 9 10 import { 11 + Button, 10 12 IngestMediaSource, 11 13 PlayerStatus as IngestPlayerStatus, 12 14 PlayerProtocol, ··· 17 19 useStreamplaceStore, 18 20 View, 19 21 } from "../.."; 20 - import { borderRadius, colors, p } from "../../lib/theme/atoms"; 22 + import { 23 + borderRadius, 24 + colors, 25 + fontWeight, 26 + gap, 27 + h, 28 + layout, 29 + m, 30 + p, 31 + } from "../../lib/theme/atoms"; 21 32 import { srcToUrl } from "./shared"; 22 33 import useWebRTC, { useWebRTCIngest } from "./use-webrtc"; 23 34 import { mediaDevices, WebRTCMediaStream } from "./webrtc-primitives.native"; ··· 301 312 }) 302 313 .then((stream: WebRTCMediaStream) => { 303 314 setLocalMediaStream(stream); 315 + 316 + let errs: string[] = []; 317 + if (stream.getAudioTracks().length === 0) { 318 + console.warn("No audio tracks found in user media stream"); 319 + errs.push("microphone"); 320 + } 321 + if (stream.getVideoTracks().length === 0) { 322 + console.warn("No video tracks found in user media stream"); 323 + errs.push("camera"); 324 + } 325 + if (errs.length > 0) { 326 + setError( 327 + new Error( 328 + `We could not access your ${errs.join(" and ")}. To stream, you need to give us permission to access these.`, 329 + ), 330 + ); 331 + } else { 332 + setError(null); 333 + } 304 334 }) 305 335 .catch((e: any) => { 306 336 console.error("error getting user media", e); 307 337 setError( 308 338 new Error( 309 - "We could not access your camera or microphone. Please check your permissions.", 339 + "We could not access your camera or microphone. To stream, you need to give us permission to access these.", 310 340 ), 311 341 ); 312 342 }); ··· 334 364 return ( 335 365 <View 336 366 backgroundColor={colors.destructive[900]} 337 - style={[p[4], { borderRadius: borderRadius.md }]} 367 + style={[p[4], m[4], gap.all[2], { borderRadius: borderRadius.md }]} 338 368 > 339 369 <View> 340 - <Text>Error encountered!</Text> 370 + <Text style={[fontWeight.semibold]} size="2xl"> 371 + Error encountered! 372 + </Text> 341 373 </View> 342 374 <Text>{error.message}</Text> 375 + {error.message.includes( 376 + "To stream, you need to give us permission to access these.", 377 + ) && ( 378 + <Button 379 + onPress={Linking.openSettings} 380 + style={[h[10]]} 381 + variant="secondary" 382 + > 383 + <View style={[layout.flex.row, gap.all[1]]}> 384 + <Text>Open Settings</Text> <ArrowRight color="white" size="18" /> 385 + </View> 386 + </Button> 387 + )} 343 388 </View> 344 389 ); 345 390 }