最近、生成AI(ChatGPTやClaudeなど)を使ってプログラミングをするのが当たり前になりました。しかし、「AIが書いたコードが動かない」「エラーを指摘してもループに陥る」ということはよくあります。
今回私が作成した画面録画ツール「LiteRec」もそうでした。ライブラリ「ScreenRecorderLib」を使い、AIと格闘しながらも、最終的に公式サンプルを「教師」にすることで解決した開発記録を共有します。
紹介するツールはもちろん使えますし、ソースコードも公開しているので、生成AIで自分好みにカスタマイズすることも可能です。
ツールが欲しい方はもちろんのこと、「ScreenRecorderLib」の具体的な使い方が知りたい方、生成AIとのペアプログラミングで今一つしっくりいってない方は、是非ご一読ください。
1. 今回開発したツール:LiteRec(ライトレック)

「LiteRec」は、余計な機能を削ぎ落とした軽量・高速な画面録画ツールです。
- コンセプト: 起動した瞬間に録画ボタンが押せる、究極のシンプルさ。
- 主な機能: モニタ/ウィンドウ選択、内部音声録音、ドラッグ&ドロップでの保存先指定。
- こだわり: ScreenRecorderLibの性能をフルに活かした、コマ落ちの少ないキャプチャ。
2. 開発環境と使用ライブラリ
今回の開発で使用した構成は以下の通りです。
- 開発ツール:Visual Studio 2026 Community版
- 開発言語:C#
- フレームワーク: .NET 10 / WPF
- 使用ライブラリ: ScreenRecorderLib (v6.6.0)
- 開発パートナー: ChatGPT / Gemini 3 Pro
3. AIとの格闘記録:最新ライブラリの罠

開発中、AIの「知ったかぶり」によって数時間のタイムロスが発生しました。同じ沼にハマらないための記録です。
最初のきっかけ
Windows11で画面に表示されている動画を、リアルタイムでキャプチャ(音声含む)したい。
まずはPythonで。
このプロンプトから始まりました。
Gemini君の回答は、Pythonでは非常に難易度が高く、FFmpegなどの外部ツールが必要とのこと。
C#だったら?
C#(.NET)の場合、Windows OSとの親和性が非常に高く、Pythonよりもはるかに高性能かつ安定的にできるとのこと。そして、ScreenRecorderLibを使うことを提案されました。
AIがついた「嘘」
取り急ぎ、Gemini君の支持通り、ScreenRecorderLibをNugetから入手。この時は特にバージョンを指定されなかったので、最新版をいストールしました。

インストールできたので、さっそく提示されたソースコードを貼り付けて実行してみると、次のエラーが発生。

- バージョンダウンの罠
エラーを伝えると「最新版は不安定なので、ライブラリのバージョンを下げてください」という、解決にならないアドバイスをされました。試しにバージョンダウンしても予想通り結果は変わらず・・・・ - ChatGPTに解決を依頼
解決の兆しが見えないので、ソースコードをChatGPT君に張り付けて「治してくれ」とお願いしたら、なんとか治してくれました。 - キャプチャ対象固定問題が発生
治ったとの喜びもつかの間、今度は録画対象を別のモニタやウィンドウに変えても何故か切り替わらず、メインディスプレイが録画される。Gemini君はChatGPT君に解決を求めても、存在しないプロパティを修正し続ける「無限ループ」に再び突入。
解決策:公式サンプルをAIに読み込ませた
結局、解決の鍵は「公式リポジトリの最新サンプルソース」でした。
GitHubから最新のサンプルコードをコピーし、AIにこう伝えました。
この最新の公式サンプル(v6系)を解析して、今の私のコードをこの書き方に書き換えてください。
これで一発解決。AIは「学習データ」にない最新仕様でも、目の前のソースコードを理解する能力は非常に高いため、この「カンニングペーパー作戦」が最強です。
4. 実装のキモ:再利用可能なコード・スニペット
① v5系での正しいソース指定(C#)
AIが間違えやすい「録画対象の指定」部分です。ここに行きつくまでが苦労しました。
// 録画ソース(モニタ または ウィンドウ)を格納する変数を定義
RecordingSourceBase? source = null;
if (selectedItem.Type == "Monitor")
{
// モニタの場合:DisplayRecordingSource を作成
source = new DisplayRecordingSource(selectedItem.DeviceName);
}
else if (selectedItem.Type == "Window")
{
// ウィンドウの場合:WindowRecordingSource を作成
source = new WindowRecordingSource(selectedItem.WindowHandle);
}
// 作成したソースをオプションリストに追加
if (source != null)
{
options.SourceOptions = new SourceOptions
{
// v5ではここにリストとして渡す必要があります
RecordingSources = new List<RecordingSourceBase> { source }
};
}画面デザイン(XAML)
Windows 11風の少し角が丸い、影付きのボタンスタイルです。
XAMLのソースコード
<Window x:Class="LiteRec.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LiteRec"
mc:Ignorable="d"
Title="LiteRec" Height="290" Width="460"
ResizeMode="CanMinimize" Topmost="True"
WindowStartupLocation="CenterScreen"
Background="#F3F3F3"
FontSize="14"
FontFamily="Segoe UI">
<Window.Resources>
<SolidColorBrush x:Key="AccentBrush" Color="#D13438"/>
<SolidColorBrush x:Key="AccentMouseOverBrush" Color="#E81123"/>
<SolidColorBrush x:Key="TextPrimary" Color="#1E1E1E"/>
<SolidColorBrush x:Key="TextSecondary" Color="#5D5D5D"/>
<SolidColorBrush x:Key="ControlBackground" Color="#FFFFFF"/>
<SolidColorBrush x:Key="ControlBorder" Color="#E0E0E0"/>
<Style TargetType="Control" x:Key="ModernInputStyle">
<Setter Property="Background" Value="{StaticResource ControlBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ControlBorder}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,6"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ScrollViewer x:Name="PART_ContentHost" Margin="0" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="15,8"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
CornerRadius="8"
Padding="{TemplateBinding Padding}">
<Border.Effect>
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.15" BlurRadius="5"/>
</Border.Effect>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="{StaticResource AccentMouseOverBrush}"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Margin" Value="0,1,0,-1"/>
<Setter TargetName="border" Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="0" Opacity="0" BlurRadius="0"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="#D0D0D0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#F9F9F9"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="24">
<StackPanel>
<TextBlock Text="録画対象" Foreground="{StaticResource TextSecondary}" FontSize="12" Margin="0,0,0,6"/>
<ComboBox x:Name="uxSourceCombo" Height="36" Margin="0,0,0,16" VerticalContentAlignment="Center"
BorderBrush="{StaticResource ControlBorder}" Background="White">
<ComboBox.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="4"/>
</Style>
</ComboBox.Resources>
</ComboBox>
<TextBlock Text="保存先フォルダ" Foreground="{StaticResource TextSecondary}" FontSize="12" Margin="0,0,0,6"/>
<Grid Margin="0,0,0,24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="40"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="uxOutputPathBox" IsReadOnly="True" Height="36"
VerticalContentAlignment="Center" BorderBrush="{StaticResource ControlBorder}"
AllowDrop="True"
PreviewDragOver="uxOutputPathBox_PreviewDragOver"
PreviewDrop="uxOutputPathBox_PreviewDrop"
Padding="10,0">
<TextBox.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="4"/>
</Style>
</TextBox.Resources>
</TextBox>
<Button x:Name="uxBrowseButton" Grid.Column="2"
Style="{StaticResource SecondaryButtonStyle}" Click="uxBrowseButton_Click"
ToolTip="フォルダを選択">
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="14" Foreground="#555"/>
</Button>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="uxAudioCheck" Content="PCの音声を録音"
Grid.Column="0"
IsChecked="True" VerticalAlignment="Center"
Foreground="{StaticResource TextPrimary}"/>
<TextBlock x:Name="uxStatusText"
Grid.Column="2"
Text=""
VerticalAlignment="Center"
HorizontalAlignment="Right"
FontSize="14"
FontWeight="Bold"
Foreground="{StaticResource AccentBrush}"/>
<Button x:Name="uxRecordButton"
Grid.Column="4"
Style="{StaticResource PrimaryButtonStyle}"
Click="uxRecordButton_Click" Width="140">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="uxBtnIcon" Text="" FontFamily="Segoe MDL2 Assets" VerticalAlignment="Center" Margin="0,1,8,0" FontSize="16"/>
<TextBlock x:Name="uxBtnText" Text="録画開始" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</Grid>
</StackPanel>
</Grid>
</Window>C#側のソースコード
C#側の全コードです。ScreenRecorderLibを用いた録画処理のほか、録画時間をカウントするタイマー表示も含んでいます。
C#側のソースコード
using ScreenRecorderLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Threading; // ★タイマー用に必要
using Microsoft.Win32;
namespace LiteRec
{
/// <summary>
/// コンボボックスに表示する録画対象(モニターまたはウィンドウ)のデータモデルクラス。
/// ScreenRecorderLibから取得したデバイス情報を保持します。
/// </summary>
public class SourceItem
{
/// <summary>表示名(例: "モニタ: Display 1", "アプリ: メモ帳")</summary>
public string Name { get; set; } = "";
/// <summary>ソースの種類 ("Monitor" または "Window")</summary>
public string Type { get; set; } = "";
/// <summary>モニターの場合のデバイスID(\\.\DISPLAY1 等)</summary>
public string DeviceName { get; set; } = "";
/// <summary>ウィンドウの場合のハンドル(IntPtr)</summary>
public IntPtr WindowHandle { get; set; }
/// <summary>コンボボックスでの表示用文字列を返します。</summary>
public override string ToString() => Name;
}
/// <summary>
/// LiteRec メインウィンドウのロジック。
/// 画面録画、タイマー表示、ドラッグ&ドロップ処理などを管理します。
/// </summary>
public partial class MainWindow : Window
{
/// <summary>録画機能を提供するレコーダーオブジェクト</summary>
private Recorder? _recorder;
/// <summary>現在録画中かどうかのフラグ</summary>
private bool _isRecording = false;
// ★タイマー関連の変数
/// <summary>録画時間を計測・表示するためのタイマー</summary>
private DispatcherTimer _timer;
/// <summary>録画を開始した時刻</summary>
private DateTime _startTime;
/// <summary>
/// コンストラクタ。
/// 保存先フォルダの初期化、タイマーの設定、録画ソースの読み込みを行います。
/// </summary>
public MainWindow()
{
InitializeComponent();
// 保存先フォルダの初期設定(デスクトップに "LiteRec_Videos" を作成)
string defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "LiteRec_Videos");
if (!Directory.Exists(defaultPath))
{
Directory.CreateDirectory(defaultPath);
}
uxOutputPathBox.Text = defaultPath;
// ★タイマーの初期化(1秒刻みでTickイベントを発火)
_timer = new DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += Timer_Tick;
// 録画対象(モニタ・ウィンドウ)の一覧を取得
RefreshSources();
}
/// <summary>
/// タイマーのTickイベントハンドラ。
/// 1秒ごとに呼び出され、録画経過時間をステータスバーに表示します。
/// </summary>
private void Timer_Tick(object? sender, EventArgs e)
{
if (_isRecording)
{
// 経過時間を計算
var elapsed = DateTime.Now - _startTime;
// "hh:mm:ss" 形式で表示 (例: ● 録画中 00:01:23)
uxStatusText.Text = $"録画中 {elapsed:hh\\:mm\\:ss}";
}
}
/// <summary>
/// 利用可能なモニターとウィンドウの一覧を取得し、コンボボックスを更新します。
/// </summary>
private void RefreshSources()
{
var list = new List<SourceItem>();
// 1. モニター一覧を取得
foreach (var monitor in Recorder.GetDisplays())
{
list.Add(new SourceItem
{
Name = $"モニタ: {monitor.FriendlyName}",
Type = "Monitor",
DeviceName = monitor.DeviceName
});
}
// 2. ウィンドウ一覧を取得(タイトルが存在するもののみ)
foreach (var win in Recorder.GetWindows())
{
if (!string.IsNullOrEmpty(win.Title))
{
list.Add(new SourceItem
{
Name = $"アプリ: {win.Title}",
Type = "Window",
WindowHandle = win.Handle
});
}
}
uxSourceCombo.ItemsSource = list;
// リストがあれば先頭を選択状態にする
if (list.Count > 0) uxSourceCombo.SelectedIndex = 0;
}
/// <summary>
/// 「保存先フォルダを選択」ボタンのクリックイベント。
/// フォルダ選択ダイアログを表示します。
/// </summary>
private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new OpenFolderDialog
{
Title = "保存先フォルダを選択",
Multiselect = false,
ValidateNames = true
};
// 現在入力されているパスがあれば、そこを初期位置にする
if (Directory.Exists(uxOutputPathBox.Text))
{
dialog.InitialDirectory = uxOutputPathBox.Text;
}
if (dialog.ShowDialog() == true)
{
uxOutputPathBox.Text = dialog.FolderName;
}
}
/// <summary>
/// 録画ボタンのクリックイベント。
/// 状態に応じて開始または停止を実行します。
/// </summary>
private void uxRecordButton_Click(object sender, RoutedEventArgs e)
{
if (_isRecording) StopRecording();
else StartRecording();
}
/// <summary>
/// テキストボックスへのドラッグ中イベント。
/// マウスカーソルの形状を「コピー可能」に変更します。
/// </summary>
private void uxOutputPathBox_PreviewDragOver(object sender, DragEventArgs e)
{
// ファイル/フォルダがドラッグされている場合のみコピー操作とする
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Copy;
e.Handled = true; // イベントをここで処理したことにする
}
else
{
e.Effects = DragDropEffects.None;
}
}
/// <summary>
/// テキストボックスへのドロップイベント。
/// ドロップされたフォルダパスを反映します。ファイルの場合は親フォルダを採用します。
/// </summary>
private void uxOutputPathBox_PreviewDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
// ドラッグされたファイル/フォルダのパス一覧を取得
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Length > 0)
{
string path = files[0];
// ファイルがドロップされた場合は、その親フォルダを使う
if (File.Exists(path))
{
path = Path.GetDirectoryName(path) ?? path;
}
// フォルダであればテキストボックスにセット
if (Directory.Exists(path))
{
uxOutputPathBox.Text = path;
}
}
}
}
/// <summary>
/// 録画を開始します。
/// レコーダーのオプション設定、ソースの適用、タイマー開始を行います。
/// </summary>
private void StartRecording()
{
var selectedItem = uxSourceCombo.SelectedItem as SourceItem;
if (selectedItem == null) return;
// ファイル名の生成(日時入り)
string fileName = $"LiteRec_{DateTime.Now:yyyyMMdd_HHmmss}.mp4";
var path = Path.Combine(uxOutputPathBox.Text, fileName);
// 基本設定(音声設定など)
var options = new RecorderOptions
{
AudioOptions = new AudioOptions
{
IsAudioEnabled = true,
IsOutputDeviceEnabled = true, // PC内部音声を録音
IsInputDeviceEnabled = false, // マイクはOFF
Bitrate = AudioBitrate.bitrate_128kbps,
Channels = AudioChannels.Stereo
}
};
// 録画ソース(モニタ または ウィンドウ)を判定して作成
RecordingSourceBase? source = null;
if (selectedItem.Type == "Monitor")
{
// モニタの場合:DisplayRecordingSource を作成
source = new DisplayRecordingSource(selectedItem.DeviceName);
}
else if (selectedItem.Type == "Window")
{
// ウィンドウの場合:WindowRecordingSource を作成
source = new WindowRecordingSource(selectedItem.WindowHandle);
}
// 作成したソースをオプションに適用
if (source != null)
{
options.SourceOptions = new SourceOptions
{
// リストとしてソースを渡す
RecordingSources = new List<RecordingSourceBase> { source }
};
}
try
{
// レコーダーの作成と初期化
_recorder = Recorder.CreateRecorder(options);
_recorder.OnRecordingComplete += OnRecComplete;
_recorder.OnRecordingFailed += OnRecFailed;
// 録画開始
_recorder.Record(path);
_isRecording = true;
// ★タイマー開始処理
_startTime = DateTime.Now;
_timer.Start();
UpdateUIState(true);
}
catch (Exception ex)
{
MessageBox.Show($"初期化エラー: {ex.Message}");
}
}
/// <summary>
/// 録画を停止します。
/// タイマーを止め、レコーダーのリソースを解放します。
/// </summary>
private void StopRecording()
{
// ★タイマー停止
_timer.Stop();
if (_recorder != null)
{
_recorder.Stop();
_recorder = null;
}
_isRecording = false;
UpdateUIState(false);
}
/// <summary>
/// 録画完了時に呼び出されるイベントハンドラ。
/// ステータス表示をリセットします。
/// </summary>
private void OnRecComplete(object? sender, RecordingCompleteEventArgs e)
{
// UIスレッドで操作するためにDispatcherを使用
Dispatcher.Invoke(() =>
{
uxStatusText.Text = "";
});
}
/// <summary>
/// UIの状態(ボタンの文字、アイコン、有効/無効)を更新します。
/// </summary>
/// <param name="recording">録画中なら true</param>
private void UpdateUIState(bool recording)
{
if (recording)
{
// 録画中のUI
uxBtnIcon.Text = "\uE73B"; // ■ (停止アイコン)
uxBtnText.Text = "録画停止";
// 初期表示 (0秒)
uxStatusText.Text = "録画中 00:00:00";
uxStatusText.Foreground = System.Windows.Media.Brushes.Red;
}
else
{
// 待機中のUI
uxBtnIcon.Text = "\uE7C8"; // ● (録画アイコン)
uxBtnText.Text = "録画開始";
uxStatusText.Text = "";
}
// 録画中は設定変更できないように制御をロック
uxSourceCombo.IsEnabled = !recording;
uxAudioCheck.IsEnabled = !recording;
uxBrowseButton.IsEnabled = !recording;
uxOutputPathBox.IsEnabled = !recording;
}
/// <summary>
/// 録画エラー発生時に呼び出されるイベントハンドラ。
/// </summary>
private void OnRecFailed(object? sender, RecordingFailedEventArgs e)
{
Dispatcher.Invoke(() =>
{
StopRecording();
MessageBox.Show($"録画エラー: {e.Error}");
});
}
}
}5. まとめと今後の展望
今回の「LiteRec」開発を通じて痛感したのは、「生成AIは、最新のライブラリ仕様を知らないことがある」 という事実です。
最初はAIの回答を信じてエラー修正のループに陥りましたが、人間側が「公式のサンプルコード」という "正解(コンテキスト)" を与えた瞬間、AIのパフォーマンスは劇的に向上しました。
- うまくいかない時: エラー文だけをAIに投げて「直して」と言う。
- うまくいく時: 「この公式ドキュメントの書き方に合わせて、私のコードを直して」と指示する。
AIとの開発で無限ループに陥ったり、知ったかぶりで怪しそうだと感じたら、この「AI × 公式ドキュメント」のハイブリッド開発をお試しください。
今回のソースコードについて
今回公開したソースコードは、ネット上の古い情報(v5系)と最新仕様(v6系)の狭間で迷子になりがちな「ScreenRecorderLib」の、現時点での動作確認済み実装パターンです。
LiteRec としてそのまま使うもよし。
自分好みに改造 して、特定ウィンドウ専用レコーダーにするもよし。
ぜひ、あなたの開発のベースとして自由に使ってください。
コメント