Skip to content

feat(windows): canvas-media, WebGPU video frames and MediaElementAudioSourceNode - #157

Merged
triniwiz merged 7 commits into
v3-v8from
feat/windows-canvas-media
Sep 27, 2026
Merged

triniwiz merged 7 commits into
v3-v8from
feat/windows-canvas-media

Conversation

@triniwiz

Copy link
Copy Markdown
Member

canvas-media on NativeScript Windows, plus the two integrations it enables: video frames that reach WebGPU without the CPU, and MediaElementAudioSourceNode.

canvas-media on Media Foundation

  • New Node-API module crates/canvas-media-napi (canvasmedia.node) over a WinRT MediaPlayer.
    • MediaPlayer raises its events on Media Foundation threads, where the Windows runtime can't run JS delegates. So the module subscribes to them natively and forwards them to JS through threadsafe functions.
  • Video: frame-server mode. Each frame is copied into a ring of BGRA textures, presented in the view through a XAML SurfaceImageSource, and read back only when a canvas asks.
    • Canvas uses covered: 2D drawImage, WebGL texImage2D / texImage3D / texSubImage3D, VideoFrame, and WebGPU.
    • controls: WinUI's transport controls over the frames, using the stock template with its presenter kept for layout but invisible. Canvases keep getting frames while controls are shown.
  • HTMLMediaElement semantics in player-windows.ts: readyState, play() promises (with AbortError / NotSupportedError), timeupdate, seeking/seeked, volumechange, <Source> fallback, and canPlayType.
  • Exit safety: a MediaPlayer released during process exit (after ExitProcess has killed Media Foundation's threads) spins forever in the graphics driver and leaves an unkillable process. The module now closes live players from an env cleanup hook, and keeps nothing releasable in thread-local storage.

WebGPU: video frames without the CPU

wgpu's DX12 backend supports external textures.

  • Sharing: canvas-media hands out a CanvasD3DSharedFrame: a shareable texture plus ready/release fences, by NT handle. canvas-c opens it on wgpu's D3D12 device and brackets the reading submit with the fork's add_wait_fence / add_signal_fence.
  • copyExternalImageToTexture(video) stays on the GPU.
  • importExternalTexture(video) now works on Windows.
  • Fallback: a frame from another adapter (hybrid GPUs) is refused, and the CPU path is used.
  • GPUDevice.__frameDevice (the MTLDevice on Apple, the adapter LUID on Windows) replaces __metalDevice where videos are asked for GPU frames.

MediaElementAudioSourceNode

  • A real tap, like iOS (MTAudioProcessingTap) and Android (ExoPlayer AudioProcessor):
    • canvasmedia.node is also the in-process WinRT server of an IBasicAudioEffect class, which canvas-media's plugin.targets registers in the app manifest;
    • canvas-media installs the effect in every player before any source is set, passing audio through;
    • createMediaElementSource routes it: the element goes silent, and its audio, at the element's volume, plays through the graph.
  • Consumer: audiocontext.node reads the tap as a media-stream source.

Also

  • CI: canvas-svg, audio-context and canvas-media now share one napi-windows matrix job, one entry per module. The earlier canvas-svg fixes (no cache on manual runs, default-branch checkout) now apply to all of them. The cache is keyed on git tree hashes.
  • Typings: fillText's maxWidth is optional, and deleteShader takes a WebGLShader (or null).
  • Modules: rebuilt x64/arm64 canvasnative.node, canvasmedia.node and audiocontext.node.

Testing

  • apps/demo on Windows, new media spec suite: 11/11. It covers events, drawImage, texImage2D, VideoFrame, zero-copy copyExternalImageToTexture, importExternalTexture, seeking, errors, audio and createMediaElementSource.
  • Full canvas-spec: 337/339. The two webgpu.video failures come from @nativescript/core's ~/ resolution on Windows: knownFolders.currentApp() is the package root, not app/.
  • Node-API suites: canvas-media 3/3, audio-context 11/11, canvas-napi WebGPU + canvas sources 25/25.

…bGLShader

- CanvasRenderingContext2D.fillText(text, x, y, maxWidth?): maxWidth is optional, as on the web
  (strokeText already was).
- WebGLRenderingContext.deleteShader(shader: WebGLShader | null) was declared as taking a
  WebGLRenderbuffer; null is a no-op, as in WebGL (it threw on `shader.native`).
canvas-media's Video, Audio and VideoFrame on NativeScript Windows, over a WinRT MediaPlayer.

- crates/canvas-media-napi (canvasmedia.node): NSCMediaPlayerBridge subscribes the player's events
  natively (Media Foundation raises them on its own threads, where the runtime cannot run JS
  delegates) and delivers them through threadsafe functions. Video players run in frame-server
  mode: each frame is copied (CopyFrameToVideoSurface) into a ring of BGRA textures on the module's
  D3D11 device, presented into the view's XAML SurfaceImageSource and read back (RGBA) on demand.
  Frames are also shareable with WebGPU (gpuFrame(): NT handles plus ready / release fences) and
  the audio can be tapped (createAudioTap(): an IBasicAudioEffect the module serves as an in-process
  WinRT class, registered in the app manifest by plugin.targets).
- Exit safety: a MediaPlayer released during process exit (after ExitProcess has killed Media
  Foundation's threads, e.g. from a thread-local destructor) spins forever in the graphics driver
  and leaves an unkillable process. An env cleanup hook closes every live player while its threads
  run, nothing releasable lives in TLS, and no frame copy starts after close().
- player-windows.ts: HTMLMediaElement state and events over the player (readyState, play()
  promises with AbortError / NotSupportedError, timeupdate while playing, seeking / seeked,
  volumechange, <Source> fallback in order, ms-appx: / file / http sources, canPlayType).
- Video: frames shown by an Image; 2D drawImage, WebGL texImage2D / texImage3D / texSubImage3D,
  WebGPU copyExternalImageToTexture (ImageAsset) and VideoFrame read the current frame once per
  decoded frame. `controls`: WinUI's transport controls over the frames (the stock
  MediaPlayerElement template with its presenter kept for layout but invisible: in frame-server
  mode it only paints black), so canvases keep getting frames.
- Audio: WinUI's compact transport controls.
- Packaging: platforms/windows plugin.props / plugin.targets, build-napi.sh canvas-media-napi,
  `make windows-media`, the PR workflow's artifact placement.
- CI: canvas-svg, audio-context and canvas-media share one napi-windows matrix job (one entry per
  module), with the earlier canvas-svg fixes applied to all: no cache on manual runs, the default
  branch checked out on them, a source key from git tree hashes (Cargo.lock is untracked), and a
  timeout on the Node-API tests.
- demo: a `media` spec suite on Windows (events, drawImage, texImage2D, VideoFrame,
  copyExternalImageToTexture, importExternalTexture, seeking, errors, audio,
  createMediaElementSource).
wgpu's DX12 backend supports external textures, so importExternalTexture now works on Windows and
copyExternalImageToTexture(video) stays on the GPU, over the frames canvas-media shares.

- canvas-c gpu_shared_frame: a CanvasD3DSharedFrame (`nativeTexture`) names a shareable BGRA
  texture and two shared fences by NT handle. The texture is opened on wgpu's D3D12 device
  (OpenSharedHandle, texture_from_raw; cached by id), only ever sampled so it stays in COMMON
  between submits, and the fork's add_wait_fence / add_signal_fence bracket the submit that reads
  it: wait for the producer's copy, signal when the read is done so the producer may reuse it. A
  frame from another adapter (hybrid GPUs) is refused, and the callers take the CPU path.
- copyExternalImageToTexture: the existing blit, factored into blit_texture (any source and
  destination texture).
- importExternalTexture: the frame is drawn at once into a plane of our own, so the external
  texture's later uses never read the producer's texture.
- canvas-napi: GPUDevice.__getAdapterLuid(); canvas-c
  canvas_native_webgpu_device_get_adapter_luid.
- packages/canvas: GPUDevice.__frameDevice (the MTLDevice on Apple, the adapter LUID on Windows)
  replaces __metalDevice where videos are asked for GPU frames.
createMediaElementSource(element) on Windows, over the tap canvas-media puts on the element's
MediaPlayer (as MTAudioProcessingTap does on iOS and an ExoPlayer AudioProcessor on Android).

- canvas-media installs the tap (an audio effect in the player's pipeline) before any source is set,
  passing the audio through; createMediaElementSource routes it: the element goes silent and its
  decoded audio, at the element's volume (silent when muted), plays through the graph. It works
  for <audio> and <video>, and for canvas-polyfill's elements. disposeMediaElementSource gives the
  element its output back.
- audiocontext.node: createMediaElementSourceFromTap(address) reads the tap through the
  AudioTapSource canvas-media's module exposes (retained for as long as the node lives), as a
  MediaStreamTrack source: pulled on the render thread, silence when the element is paused.
  Realtime contexts only, as on the web.
…d audiocontext modules

Release builds (release-napi) with the shared video frames, the audio tap and the exit-safe player teardown, so npm_release.yml ships them until the napi-windows / canvas-windows CI jobs provide them.
@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: bd1e991e-ba64-4cc0-b4ac-f01c017f3e71

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Drops comments that restate the next lines or a member's name, keeping the whys: threading, the exit-time release hazard, D3D12 resource states, cross-module struct layouts, the frame/tap contracts and platform quirks.
@triniwiz
triniwiz merged commit 7322ff1 into v3-v8 Sep 27, 2026
10 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant