@@ -8,7 +8,12 @@ import {
88 type FormEvent ,
99} from "react" ;
1010
11- import { ApiError , accessTokenFromLocation , pairBrowser } from "../api/client" ;
11+ import {
12+ ApiError ,
13+ accessTokenFromLocation ,
14+ fetchHealth ,
15+ pairBrowser ,
16+ } from "../api/client" ;
1217import {
1318 bootSimulator ,
1419 dismissKeyboard ,
@@ -18,6 +23,7 @@ import {
1823 pressHome ,
1924 rotateLeft ,
2025 rotateRight ,
26+ setSimulatorVideoCodec ,
2127 simulatorControlSocketUrl ,
2228 shutdownSimulator ,
2329 toggleAppearance ,
@@ -32,13 +38,18 @@ import type {
3238 ChromeProfile ,
3339 SimulatorMetadata ,
3440 TouchPhase ,
41+ VideoCodecMode ,
3542} from "../api/types" ;
3643import { AccessibilityInspector } from "../features/accessibility/AccessibilityInspector" ;
3744import { useKeyboardInput } from "../features/input/useKeyboardInput" ;
3845import { usePointerInput } from "../features/input/usePointerInput" ;
3946import { simulatorRuntimeLabel } from "../features/simulators/simulatorDisplay" ;
4047import { useSimulatorList } from "../features/simulators/useSimulatorList" ;
41- import { sendWebRtcControlMessage } from "../features/stream/streamWorkerClient" ;
48+ import {
49+ initialStreamTransportMode ,
50+ sendWebRtcControlMessage ,
51+ type StreamTransportMode ,
52+ } from "../features/stream/streamWorkerClient" ;
4253import { useLiveStream } from "../features/stream/useLiveStream" ;
4354import { DebugPanel } from "../features/toolbar/DebugPanel" ;
4455import { Toolbar } from "../features/toolbar/Toolbar" ;
@@ -85,6 +96,8 @@ const REACT_NATIVE_ACCESSIBILITY_REFRESH_MS = 500;
8596const DEFAULT_ACCESSIBILITY_MAX_DEPTH = 10 ;
8697const LOGICAL_INSPECTOR_MAX_DEPTH = 80 ;
8798const AUTH_REQUIRED_MESSAGE = "SimDeck API access token is required." ;
99+ const STREAM_TRANSPORT_STORAGE_KEY = "simdeck.streamTransport" ;
100+ const VIDEO_CODEC_STORAGE_KEY = "simdeck.videoCodec" ;
88101
89102clearLegacyVolatileUiState ( ) ;
90103
@@ -163,6 +176,32 @@ type SimulatorTransition = {
163176 udid : string ;
164177} ;
165178
179+ function isVideoCodecMode ( value : unknown ) : value is VideoCodecMode {
180+ return value === "hevc" || value === "h264" || value === "h264-software" ;
181+ }
182+
183+ function readStoredTransportMode ( ) : StreamTransportMode {
184+ if ( typeof window === "undefined" ) {
185+ return "auto" ;
186+ }
187+ if ( new URLSearchParams ( window . location . search ) . has ( "transport" ) ) {
188+ return initialStreamTransportMode ( ) ;
189+ }
190+ const stored = window . localStorage . getItem ( STREAM_TRANSPORT_STORAGE_KEY ) ;
191+ if ( stored === "auto" || stored === "webtransport" || stored === "webrtc" ) {
192+ return stored ;
193+ }
194+ return initialStreamTransportMode ( ) ;
195+ }
196+
197+ function readStoredVideoCodec ( ) : VideoCodecMode {
198+ if ( typeof window === "undefined" ) {
199+ return "h264-software" ;
200+ }
201+ const stored = window . localStorage . getItem ( VIDEO_CODEC_STORAGE_KEY ) ;
202+ return isVideoCodecMode ( stored ) ? stored : "h264-software" ;
203+ }
204+
166205export function AppShell ( ) {
167206 const [ initialUiState ] = useState ( readPersistedUiState ) ;
168207 const [ initialSelectedUDID ] = useState (
@@ -200,6 +239,11 @@ export function AppShell() {
200239 initialViewportState . rotationQuarterTurns ,
201240 ) ;
202241 const [ streamStamp , setStreamStamp ] = useState ( Date . now ( ) ) ;
242+ const [ streamSettingsRevision , setStreamSettingsRevision ] = useState ( 0 ) ;
243+ const [ streamTransportMode , setStreamTransportMode ] =
244+ useState < StreamTransportMode > ( readStoredTransportMode ) ;
245+ const [ videoCodec , setVideoCodec ] =
246+ useState < VideoCodecMode > ( readStoredVideoCodec ) ;
203247 const [ viewMode , setViewMode ] = useState < ViewMode > (
204248 initialViewportState . viewMode ,
205249 ) ;
@@ -333,6 +377,8 @@ export function AppShell() {
333377 } = useLiveStream ( {
334378 canvasElement : streamCanvasElement ,
335379 simulator : selectedSimulator ,
380+ streamRevision : streamSettingsRevision ,
381+ transportMode : streamTransportMode ,
336382 } ) ;
337383 const shouldRenderChrome =
338384 selectedSimulator != null && shouldRenderNativeChrome ( selectedSimulator ) ;
@@ -391,6 +437,33 @@ export function AppShell() {
391437 ) ;
392438 } , [ accessibilityPreferredSource ] ) ;
393439
440+ useEffect ( ( ) => {
441+ window . localStorage . setItem (
442+ STREAM_TRANSPORT_STORAGE_KEY ,
443+ streamTransportMode ,
444+ ) ;
445+ } , [ streamTransportMode ] ) ;
446+
447+ useEffect ( ( ) => {
448+ window . localStorage . setItem ( VIDEO_CODEC_STORAGE_KEY , videoCodec ) ;
449+ } , [ videoCodec ] ) ;
450+
451+ useEffect ( ( ) => {
452+ let cancelled = false ;
453+ fetchHealth ( )
454+ . then ( ( health ) => {
455+ if ( ! cancelled && isVideoCodecMode ( health . videoCodec ) ) {
456+ setVideoCodec ( health . videoCodec ) ;
457+ }
458+ } )
459+ . catch ( ( ) => {
460+ // Non-critical: stream setup still fetches health and reports errors.
461+ } ) ;
462+ return ( ) => {
463+ cancelled = true ;
464+ } ;
465+ } , [ streamSettingsRevision ] ) ;
466+
394467 useEffect ( ( ) => {
395468 if ( simulatorTransition == null ) {
396469 return ;
@@ -1133,6 +1206,28 @@ export function AppShell() {
11331206 ) ;
11341207 }
11351208
1209+ function handleSelectTransportMode ( mode : StreamTransportMode ) {
1210+ setStreamTransportMode ( mode ) ;
1211+ setStreamSettingsRevision ( ( current ) => current + 1 ) ;
1212+ setStreamStamp ( Date . now ( ) ) ;
1213+ }
1214+
1215+ function handleSelectVideoCodec ( codec : VideoCodecMode ) {
1216+ if ( ! selectedSimulator ) {
1217+ return ;
1218+ }
1219+ const udid = selectedSimulator . udid ;
1220+ setVideoCodec ( codec ) ;
1221+ setStreamSettingsRevision ( ( current ) => current + 1 ) ;
1222+ setStreamStamp ( Date . now ( ) ) ;
1223+ void runAction ( async ( ) => {
1224+ const response = await setSimulatorVideoCodec ( udid , codec ) ;
1225+ setVideoCodec ( response . videoCodec ) ;
1226+ setStreamSettingsRevision ( ( current ) => current + 1 ) ;
1227+ setStreamStamp ( Date . now ( ) ) ;
1228+ } , false ) ;
1229+ }
1230+
11361231 async function submitPairing ( event : FormEvent < HTMLFormElement > ) {
11371232 event . preventDefault ( ) ;
11381233 const code = pairingCode . trim ( ) ;
@@ -1194,6 +1289,7 @@ export function AppShell() {
11941289 isLoading = { isLoading }
11951290 menuOpen = { menuOpen }
11961291 menuRef = { menuRef }
1292+ onChangeVideoCodec = { handleSelectVideoCodec }
11971293 onBoot = { ( ) => {
11981294 if ( ! selectedSimulator ) {
11991295 return ;
@@ -1287,11 +1383,14 @@ export function AppShell() {
12871383 onToggleTouchOverlay = { ( ) =>
12881384 setTouchOverlayVisible ( ( current ) => ! current )
12891385 }
1386+ onChangeTransportMode = { handleSelectTransportMode }
12901387 search = { search }
12911388 selectedSimulator = { selectedSimulator }
12921389 selectedSimulatorIdentifier = { selectedSimulatorDetail }
12931390 setSelectedUDID = { setSelectedUDID }
1391+ streamTransportMode = { streamTransportMode }
12941392 touchOverlayVisible = { touchOverlayVisible }
1393+ videoCodec = { videoCodec }
12951394 />
12961395 < SimulatorViewport
12971396 accessibilityHoveredId = { accessibilityHoveredId }
0 commit comments