Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/desktop/src/routes/(window-chrome)/(main).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
Suspense,
} from "solid-js";
import { createStore, reconcile } from "solid-js/store";

import Mode from "~/components/Mode";
import Tooltip from "~/components/Tooltip";
import { identifyUser, trackEvent } from "~/utils/analytics";
Expand All @@ -37,6 +36,9 @@ import {
type RecordingMode,
type ScreenCaptureTarget,
} from "~/utils/tauri";
import IconCapLogoFull from "~icons/cap/logo-full";
import IconCapLogoFullDark from "~icons/cap/logo-full-dark";
import IconLucideBug from "~icons/lucide/bug";

function getWindowSize() {
return {
Expand Down
103 changes: 60 additions & 43 deletions apps/desktop/src/routes/(window-chrome)/settings/feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,53 +130,70 @@ export default function FeedbackTab() {
</p>
}
>
{(diag) => (
<div class="space-y-3 text-sm">
<Show when={diag().macosVersion}>
{(ver) => (
<div class="space-y-1">
<p class="text-gray-11 font-medium">Operating System</p>
<p class="text-gray-10 bg-gray-2 px-2 py-1.5 rounded font-mono text-xs">
{ver().displayName}
</p>
</div>
)}
</Show>

<div class="space-y-1">
<p class="text-gray-11 font-medium">Capture Support</p>
<div class="flex gap-2 flex-wrap">
<span
class={`px-2 py-1 rounded text-xs ${
diag().screenCaptureSupported
? "bg-green-500/20 text-green-400"
: "bg-red-500/20 text-red-400"
}`}
>
Screen Capture:{" "}
{diag().screenCaptureSupported
? "Supported"
: "Not Supported"}
</span>
</div>
</div>
{(diag) => {
const d = diag();
const osVersion =
"macosVersion" in d
? d.macosVersion
: "windowsVersion" in d
? d.windowsVersion
: null;
const captureSupported =
"screenCaptureSupported" in d
? d.screenCaptureSupported
: "graphicsCaptureSupported" in d
? d.graphicsCaptureSupported
: false;
return (
<div class="space-y-3 text-sm">
<Show when={osVersion}>
{(ver) => (
<div class="space-y-1">
<p class="text-gray-11 font-medium">
Operating System
</p>
<p class="text-gray-10 bg-gray-2 px-2 py-1.5 rounded font-mono text-xs">
{(ver() as { displayName: string }).displayName}
</p>
</div>
)}
</Show>

<Show when={diag().availableEncoders.length > 0}>
<div class="space-y-1">
<p class="text-gray-11 font-medium">Available Encoders</p>
<div class="flex gap-1.5 flex-wrap">
<For each={diag().availableEncoders}>
{(encoder) => (
<span class="px-2 py-1 bg-gray-2 rounded text-xs text-gray-10 font-mono">
{encoder}
</span>
)}
</For>
<p class="text-gray-11 font-medium">Capture Support</p>
<div class="flex gap-2 flex-wrap">
<span
class={`px-2 py-1 rounded text-xs ${
captureSupported
? "bg-green-500/20 text-green-400"
: "bg-red-500/20 text-red-400"
}`}
>
Screen Capture:{" "}
{captureSupported ? "Supported" : "Not Supported"}
</span>
</div>
</div>
</Show>
</div>
)}

<Show when={d.availableEncoders.length > 0}>
<div class="space-y-1">
<p class="text-gray-11 font-medium">
Available Encoders
</p>
<div class="flex gap-1.5 flex-wrap">
<For each={d.availableEncoders}>
{(encoder) => (
<span class="px-2 py-1 bg-gray-2 rounded text-xs text-gray-10 font-mono">
{encoder}
</span>
)}
</For>
</div>
</div>
</Show>
</div>
);
}}
</Show>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button } from "@cap/ui-solid";
import { useNavigate } from "@solidjs/router";
import { For, onMount } from "solid-js";
import IconLucideDatabase from "~icons/lucide/database";

import "@total-typescript/ts-reset/filter-boolean";
import { authStore } from "~/store";
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src/utils/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ uploadProgressEvent: "upload-progress-event"

/** user-defined types **/

export type AllGpusInfo = { gpus: GpuInfoDiag[]; primaryGpuIndex: number | null; isMultiGpuSystem: boolean; hasDiscreteGpu: boolean }
export type Annotation = { id: string; type: AnnotationType; x: number; y: number; width: number; height: number; strokeColor: string; strokeWidth: number; fillColor: string; opacity: number; rotation: number; text: string | null; maskType?: MaskType | null; maskLevel?: number | null }
export type AnnotationType = "arrow" | "circle" | "rectangle" | "text" | "mask"
export type AppTheme = "system" | "light" | "dark"
Expand Down Expand Up @@ -431,6 +432,7 @@ quality: number | null;
* Whether to prioritize speed over quality (default: false)
*/
fast: boolean | null }
export type GpuInfoDiag = { vendor: string; description: string; dedicatedVideoMemoryMb: number; adapterIndex: number; isSoftwareAdapter: boolean; isBasicRenderDriver: boolean; supportsHardwareEncoding: boolean }
export type HapticPattern = "alignment" | "levelChange" | "generic"
export type HapticPerformanceTime = "default" | "now" | "drawCompleted"
export type Hotkey = { code: string; meta: boolean; ctrl: boolean; alt: boolean; shift: boolean }
Expand All @@ -443,7 +445,6 @@ export type JsonValue<T> = [T]
export type LogicalBounds = { position: LogicalPosition; size: LogicalSize }
export type LogicalPosition = { x: number; y: number }
export type LogicalSize = { width: number; height: number }
export type MacOSVersionInfo = { displayName: string }
export type MainWindowRecordingStartBehaviour = "close" | "minimise"
export type MaskKeyframes = { position?: MaskVectorKeyframe[]; size?: MaskVectorKeyframe[]; intensity?: MaskScalarKeyframe[] }
export type MaskKind = "sensitive" | "highlight"
Expand Down Expand Up @@ -486,6 +487,7 @@ export type RecordingStatus = "pending" | "recording"
export type RecordingStopped = null
export type RecordingTargetMode = "display" | "window" | "area"
export type RenderFrameEvent = { frame_number: number; fps: number; resolution_base: XY<number> }
export type RenderingStatus = { isUsingSoftwareRendering: boolean; isUsingBasicRenderDriver: boolean; hardwareEncodingAvailable: boolean; warningMessage: string | null }
export type RequestOpenRecordingPicker = { target_mode: RecordingTargetMode | null }
export type RequestOpenSettings = { page: string }
export type RequestScreenCapturePrewarm = { force?: boolean }
Expand All @@ -506,7 +508,7 @@ export type StartRecordingInputs = { capture_target: ScreenCaptureTarget; captur
export type StereoMode = "stereo" | "monoL" | "monoR"
export type StudioRecordingMeta = { segment: SingleSegment } | { inner: MultipleSegments }
export type StudioRecordingStatus = { status: "InProgress" } | { status: "NeedsRemux" } | { status: "Failed"; error: string } | { status: "Complete" }
export type SystemDiagnostics = { macosVersion: MacOSVersionInfo | null; availableEncoders: string[]; screenCaptureSupported: boolean }
export type SystemDiagnostics = { windowsVersion: WindowsVersionInfo | null; gpuInfo: GpuInfoDiag | null; allGpus: AllGpusInfo | null; renderingStatus: RenderingStatus; availableEncoders: string[]; graphicsCaptureSupported: boolean; d3D11VideoProcessorAvailable: boolean }
export type TargetUnderCursor = { display_id: DisplayId | null; window: WindowUnderCursor | null }
export type TextSegment = { start: number; end: number; enabled?: boolean; content?: string; center?: XY<number>; size?: XY<number>; fontFamily?: string; fontSize?: number; fontWeight?: number; italic?: boolean; color?: string; fadeDuration?: number }
export type TimelineConfiguration = { segments: TimelineSegment[]; zoomSegments: ZoomSegment[]; sceneSegments?: SceneSegment[]; maskSegments?: MaskSegment[]; textSegments?: TextSegment[] }
Expand All @@ -523,6 +525,7 @@ export type VideoUploadInfo = { id: string; link: string; config: S3UploadMeta }
export type WindowExclusion = { bundleIdentifier?: string | null; ownerName?: string | null; windowTitle?: string | null }
export type WindowId = string
export type WindowUnderCursor = { id: WindowId; app_name: string; bounds: LogicalBounds }
export type WindowsVersionInfo = { major: number; minor: number; build: number; displayName: string; meetsRequirements: boolean; isWindows11: boolean }
export type XY<T> = { x: T; y: T }
export type ZoomMode = "auto" | { manual: { x: number; y: number } }
export type ZoomSegment = { start: number; end: number; amount: number; mode: ZoomMode }
Expand Down
17 changes: 15 additions & 2 deletions crates/enc-ffmpeg/src/mux/segmented_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ impl SegmentedVideoEncoder {

let mut output = format::output_as(&manifest_path, "dash")?;

let init_seg_path = base_path.join(INIT_SEGMENT_NAME);
let media_seg_pattern = base_path.join("segment_$Number%03d$.m4s");

#[cfg(windows)]
let init_seg_str = init_seg_path.to_string_lossy().replace('\\', "/");
#[cfg(windows)]
let media_seg_str = media_seg_pattern.to_string_lossy().replace('\\', "/");

#[cfg(not(windows))]
let init_seg_str = init_seg_path.to_string_lossy().to_string();
#[cfg(not(windows))]
let media_seg_str = media_seg_pattern.to_string_lossy().to_string();

unsafe {
let opts = output.as_mut_ptr();

Expand All @@ -157,8 +170,8 @@ impl SegmentedVideoEncoder {
ffmpeg::ffi::av_opt_set((*opts).priv_data, k.as_ptr(), v.as_ptr(), 0);
};

set_opt("init_seg_name", INIT_SEGMENT_NAME);
set_opt("media_seg_name", "segment_$Number%03d$.m4s");
set_opt("init_seg_name", &init_seg_str);
set_opt("media_seg_name", &media_seg_str);
set_opt(
"seg_duration",
&config.segment_duration.as_secs_f64().to_string(),
Expand Down
20 changes: 10 additions & 10 deletions crates/recording/src/capture_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ use crate::{

#[cfg(target_os = "macos")]
use crate::output_pipeline::{MacOSFragmentedM4SMuxer, MacOSFragmentedM4SMuxerConfig};
#[cfg(windows)]
use crate::output_pipeline::{WindowsFragmentedM4SMuxer, WindowsFragmentedM4SMuxerConfig};
use anyhow::anyhow;
#[cfg(windows)]
use cap_enc_ffmpeg::h264::H264Preset;
use cap_timestamp::Timestamps;
use std::{path::PathBuf, sync::Arc};

Expand Down Expand Up @@ -136,11 +140,9 @@ impl MakeCapturePipeline for screen_capture::Direct3DCapture {
output_path: PathBuf,
start_time: Timestamps,
fragmented: bool,
_shared_pause_state: Option<SharedPauseState>,
shared_pause_state: Option<SharedPauseState>,
encoder_preferences: EncoderPreferences,
) -> anyhow::Result<OutputPipeline> {
let d3d_device = screen_capture.d3d_device.clone();

if fragmented {
let fragments_dir = output_path
.parent()
Expand All @@ -150,17 +152,15 @@ impl MakeCapturePipeline for screen_capture::Direct3DCapture {
OutputPipeline::builder(fragments_dir)
.with_video::<screen_capture::VideoSource>(screen_capture)
.with_timestamps(start_time)
.build::<WindowsSegmentedMuxer>(WindowsSegmentedMuxerConfig {
pixel_format: screen_capture::Direct3DCapture::PIXEL_FORMAT.as_dxgi(),
d3d_device,
bitrate_multiplier: 0.15f32,
frame_rate: 30u32,
output_size: None,
encoder_preferences,
.build::<WindowsFragmentedM4SMuxer>(WindowsFragmentedM4SMuxerConfig {
segment_duration: std::time::Duration::from_secs(3),
preset: H264Preset::Ultrafast,
output_size: None,
shared_pause_state,
})
.await
} else {
let d3d_device = screen_capture.d3d_device.clone();
OutputPipeline::builder(output_path.clone())
.with_video::<screen_capture::VideoSource>(screen_capture)
.with_timestamps(start_time)
Expand Down
5 changes: 5 additions & 0 deletions crates/recording/src/output_pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ pub use win_segmented::*;
mod win_segmented_camera;
#[cfg(windows)]
pub use win_segmented_camera::*;

#[cfg(windows)]
mod win_fragmented_m4s;
#[cfg(windows)]
pub use win_fragmented_m4s::*;
Loading