The gap
The native host has no Video primitive. CraftNativeScreen.swift switches on ActivityIndicator, FlatList, G, Icon, Image, Modal, Picker, ScrollView, Slider, Svg, SvgText, Switch, Text, TextInput — there is no video view, and the same is true of the Android host.
The capability flags enableVideoRecording and enableAudioRecording exist, so the gap is specifically playback, not capture.
What it blocks
A screen that plays video cannot be a native screen, so it stays in the WebView even when everything else about it is expressible natively.
In HQ.training this is the workout player — resources/views/m/workout/[id].stx:
<video :if="exercisePlay.kind === 'file'" x-src="exercisePlay.url"
@loadedmetadata="playDemonstration($event)"
autoplay controls playsinline preload="metadata">An exercise demonstration plays when its sheet opens. The videos are mirrored onto our own server and served over HTTP range requests, so this is ordinary file playback, not a third-party embed.
Four other screens use <video> the same way: the store product page, the purchased-library player, the coach exercise bank, and purchases.
What would be enough
A Video node mapping to AVPlayerViewController (or an AVPlayerLayer in a plain view) on iOS and ExoPlayer/VideoView on Android, with the props that <video> already uses here:
srccontrolsautoplayplaysinline— matters on iOS, where video otherwise goes fullscreenpreload- a
loadedmetadata-equivalent event, which is what the player uses to seek to the demonstration's start
Range-request support is the thing most likely to be fiddly: iOS is particular about it, and it is what changes when a file is served from our own origin rather than a CDN.
Not part of this
Third-party embeds (YouTube, Vimeo) are out of scope — those need a web view by nature, and a native screen that needs one is a screen that should stay web for now.