Skip to content

0.14.0

Choose a tag to compare

@arkavo-com arkavo-com released this 31 Oct 00:54
b42f4e5

ArkavoKit 0.14.0

0.14.0 also carries everything merged in #57, which was never tagged as 0.13.0. Those notes are under "From #57" below.

Avatar placement slots

  • AvatarSlot (slot: CGRect in normalized output coordinates, anchor: .bottomLeft | .bottomRight | .bottomCenter) says where an avatar layer goes in the output.
  • AvatarSlotLayout.frame(slot:visibleRect:textureSize:output:) is the shared slot math (the compositor passes each layer texture's own size): the visible part of the texture (visibleRect, normalized; AvatarSlotLayout.fullTexture is all of it) is scaled uniformly to fit the slot and pinned to the anchor. A zero-area slot draws nothing.
  • CompositorManager and RecordingSession gain avatarSlot, avatarVisibleRect, museSlot and museVisibleRect. They are safe to set from any thread; the next composed frame uses them. setLayerPlacement(avatarSlot:avatarVisibleRect:museSlot:museVisibleRect:) sets all four in one step, so no frame pairs a new slot with an old rect.

Behavior changes

  • Screens of any shape are fitted with black bars instead of being stretched. A fit within a pixel of the output on an axis fills that axis, so near-16:9 screens get no hairline bars.
  • Overlays, the watermark and the scene overlay are placed in output space, over the bars.
  • Avatar and Muse layers have no rounded mask.
  • pipPosition and pipScale place cameras only.
  • Muse's position no longer depends on pipPosition.
  • An avatar-only frame is opaque: full-stage transparency composes as black, as recordings and viewers already got.
  • VideoEncoder.duration is 0 for a stream-only session.
  • The 30 fps driver composes one frame at a time; a tick that lands while a frame is still composing is skipped.

Recording and streaming together

  • Stopping a recording keeps a live stream's video, audio, screen and cameras running; only the file finishes. Sources are released by whichever of recording and stream stops last.
  • Pausing a recording pauses only its file, not the stream.
  • Once a display switch returns, no frame from the replaced or stopped display is delivered.
  • A second startRecording while one is starting gets that start's outcome instead of returning early.
  • Recording again mid-stream reuses the stream's microphone, and a recording that starts mid-stream keeps the stream's mix.
  • Overlapping desktop-audio starts open one capture; desktop audio that stopped by itself (screen sharing ended) starts again with the next recording.
  • A stream that starts while a recording stops, or while a failed start releases its sources, gets its screen, cameras and audio back, keeps its mix, and starts its frames once.
  • AudioRouter.startAll() starts every source even when one fails, then throws the first error, so a failing desktop capture no longer leaves the microphone or Muse's voice unstarted.
  • ScreenAudioSource.start() throws RecorderError.screenCaptureUnavailable (to the caller and to any start that joined it) when its capture stops by itself while opening. A stop() that came after the last start still wins and is not an error.
  • ScreenAudioSource.stop() during an opening waits for that capture to be stopped before it returns, at most ScreenAudioSource.openingStopWaitLimit (2 s), so a stop can't hang behind a screen-capture prompt. A stop that times out still has the capture stopped once it opens.

Migrating

  • Set avatarSlot and museSlot (and their visible rects) on the session. The defaults are Gameplay right ((0.71, 0.38, 0.28, 0.62), .bottomRight) and Gameplay left ((0.01, 0.38, 0.28, 0.62), .bottomLeft), each with the full texture visible.
  • With no recording running, stopStreaming() stops the screen capture and cameras too, as stopRecording() already did. Restart previews after either stop.
  • onFrame handlers must not block on the main actor: starting and stopping wait for a frame handover already in flight.

From #57

  • RecordingSession.setComposedPreviewEnabled(_:) emits composed preview frames, at most one per second, without starting audio or encoding.
  • ScreenCaptureManager.onCaptureError / RecordingSession.screenCaptureFailureHandler (@MainActor (any Error) -> Void): when the session can't attach a newly selected display, the previous display keeps capturing and the error is reported here. startCapture(displayID:) returns once the session work is queued; screenCaptureUnavailable is still thrown synchronously.
  • AudioMixer.drain() and AudioMixer(prefillSeconds:) (default defaultPrefillSeconds, 60 ms): a non-clock source plays once it holds 60 ms or its oldest sample has waited that long, instead of being zero-filled mid-stream (the crackle in Creator#127). Non-clock sources land about 60 ms behind the clock.
  • Desktop audio excludes the host app's own output.
  • Stopping a recording keeps audio still buffered in the mixer; a recording's audio track can't go backwards (late audio is dropped and counted in droppedMedia.audio.outOfOrder).
  • Screen capture session work runs on a private serial queue in caller order; startCaptureAndWaitForFirstFrame ends promptly with CancellationError when its capture is stopped or replaced.

Validation

swift test: XCTest 230 + 77, Swift Testing 178 + 162, 0 failures; 3 skipped (no RTMP server; no ARKAVO_TOKEN, twice). Each task was reviewed by a task reviewer and the ArkavoKit maintainer session.

PRs: #57 and #58.

🤖 Generated with Claude Code