3107f928ab
The original premise (extract FFmpegEncoder / StreamHealthMonitor / FramePump classes) was a no-op — those already exist as Services/Encoder/FfmpegEncoder.cs and Services/Encoder/FramePump.cs. The honest functional seam was the local recording-output concern, which was interleaved with live-stream orchestration: - New MainViewModel.Recording.cs (121): StartRecordFile, FinalizeRecordingAsync, UniquePath, ChooseRecordFolder, ResetRecordFolder, DefaultRecordFolder + the recording fields (_recordFolder, RecordFolderDisplay, _activeRecordPath, _recordStartTime, _recordLength). - MainViewModel.Streaming.Operations.cs 475 -> 377 (keeps live session lifecycle, health, visuals). - MainViewModel.Streaming.cs 328 -> 323 (keeps pills/state/properties intact). Same partial class — all MVVM bound-property glue untouched, no behavior change. Build 0 warnings; 246 pass, only the 2 known failures. Docs: ViewModels/index.md tracker updated in same commit.
122 lines
4.1 KiB
C#
122 lines
4.1 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using Microsoft.Win32;
|
|
using ytLive.Helpers;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.ViewModels;
|
|
|
|
public partial class MainViewModel : ViewModelBase
|
|
{
|
|
private string? _recordFolder;
|
|
public string RecordFolderDisplay => _recordFolder ?? DefaultRecordFolder();
|
|
private string? _activeRecordPath;
|
|
private DateTime _recordStartTime;
|
|
private TimeSpan _recordLength;
|
|
|
|
private string? StartRecordFile()
|
|
{
|
|
try
|
|
{
|
|
var folder = _recordFolder ?? DefaultRecordFolder();
|
|
Directory.CreateDirectory(folder);
|
|
var name = RecordingFile.BuildStartName(DateTime.Now) + ".mp4";
|
|
var path = Path.Combine(folder, name);
|
|
_activeRecordPath = path;
|
|
return path;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AppLog.Write(ex, "Recording: failed to prepare output path");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async Task FinalizeRecordingAsync()
|
|
{
|
|
var startPath = _activeRecordPath;
|
|
_activeRecordPath = null;
|
|
if (string.IsNullOrEmpty(startPath)) return;
|
|
try
|
|
{
|
|
while (_framePump.IsRunning) await Task.Delay(50);
|
|
|
|
if (!File.Exists(startPath))
|
|
{
|
|
AppLog.Write($"Recording: output not found for rename: {startPath}");
|
|
return;
|
|
}
|
|
_recordLength = _liveElapsed;
|
|
var autoStem = RecordingFile.BuildFinalName(_recordStartTime, _recordLength);
|
|
var dir = Path.GetDirectoryName(startPath)!;
|
|
|
|
// Manual rename modal: give the creator a chance to name the recording;
|
|
// null = keep the auto name (or the default recorded above).
|
|
string stem = autoStem;
|
|
var dialog = new RenameRecordingDialog(autoStem);
|
|
if (dialog.ShowDialog() == true && !string.IsNullOrEmpty(dialog.ResultFileName))
|
|
stem = dialog.ResultFileName;
|
|
|
|
var finalPath = UniquePath(Path.Combine(dir, stem + ".mp4"));
|
|
File.Move(startPath, finalPath);
|
|
AppLog.Write($"Recording saved: {finalPath}");
|
|
_notifications.Info("Recording saved",
|
|
Path.GetFileName(finalPath) + " — you can rename it in your videos folder at any time.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AppLog.Write(ex, "Recording: rename-on-stop failed");
|
|
_notifications.Error("Recording saved",
|
|
$"The recording was written but couldn't be renamed: {Path.GetFileName(startPath)}");
|
|
}
|
|
}
|
|
|
|
private static string UniquePath(string path)
|
|
{
|
|
if (!File.Exists(path)) return path;
|
|
var dir = Path.GetDirectoryName(path)!;
|
|
var name = Path.GetFileNameWithoutExtension(path);
|
|
var ext = Path.GetExtension(path);
|
|
for (var i = 2; ; i++)
|
|
{
|
|
var candidate = Path.Combine(dir, $"{name}-{i}{ext}");
|
|
if (!File.Exists(candidate)) return candidate;
|
|
}
|
|
}
|
|
|
|
private void ChooseRecordFolder()
|
|
{
|
|
var dialog = new Microsoft.Win32.OpenFolderDialog
|
|
{
|
|
Title = "Choose a folder for local recordings",
|
|
InitialDirectory = _recordFolder ?? DefaultRecordFolder(),
|
|
};
|
|
if (dialog.ShowDialog() == true)
|
|
{
|
|
_recordFolder = dialog.FolderName;
|
|
_layoutStore.SaveRecordFolder(_recordFolder);
|
|
OnPropertyChanged(nameof(RecordFolderDisplay));
|
|
_notifications.Info("Record folder set", _recordFolder);
|
|
}
|
|
}
|
|
|
|
private void ResetRecordFolder()
|
|
{
|
|
_recordFolder = null;
|
|
_layoutStore.SaveRecordFolder(null);
|
|
OnPropertyChanged(nameof(RecordFolderDisplay));
|
|
_notifications.Info("Record folder reset", RecordFolderDisplay);
|
|
}
|
|
|
|
internal static string DefaultRecordFolder()
|
|
{
|
|
var downloads = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
|
|
return Directory.Exists(downloads)
|
|
? downloads
|
|
: Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
|
|
}
|
|
}
|