omega80 Опубликовано 3 часа назад Опубликовано 3 часа назад Постоянные поиски видеорегистраторов для нашего отдела сборки заказов были омрачены дополнительными сложностями в виде новых кодировщиков стандарта H.265. Старые версии ПО для видеозаписей на сервер поддерживали только 264 версию, и после приобретения камер высокого разрешения 4к встал вопрос: покупать новые версии программ видеорегистрации, либо написать самому. Решили сами сделать так, как нам будет удобно, да и в случае обновления стандартов и протоколов будет легко адаптировать её. Основные требования: 1) Всегда поверх всех окон - оператор не должен закрывать, сворачивать либо убирать окно записи, программа всегда должна быть на виду сборщика; 2) Программа должна принимать номер заказа - будет использоваться для названия видеофайла для облегчения последующего поиска по номеру заказа. Причем, пока оператор не введет номер заказа - кнопка начала записи должна быть неактивной; 3) После нажатия "СТАРТ ЗАПИСИ" должно быть окно контроля действий сборщика, а также броская мигающая надпись о текущем процессе записи; 4) После окончания записи видеофайл должен сохраняться на выбранный диск с названием, содержащим номер заказа + дату файла. После сохранения необходимо проверить успешность выгрузки файла, если все отлично - выдать уведомление. Затем стереть содержимое поля ввода номера заказа для подготовки ввода следующего номера; 5) Дополнительно должны проверяться все необходимые зависимости и обрабатываться возможные ошибки с выводом информации об ошибке: 5.1 - не установлен VLC x32 5.2 - нет второго диска для сохранения файлов Решение задачи: Используем бесплатный стек Visual Studio под приложение .NET для Windows desktop. Создадим новый проект, выбираем .NET Desktop Development (Разработка классических приложений .NET) - он необходим для создания Windows Forms. Далее открываем Form1 и указываем код (ver.3.1): using System; using System.Drawing; using System.IO; using System.Windows.Forms; using Vlc.DotNet.Forms; using System.Runtime.InteropServices; namespace RecordingApp { public partial class Form1 : Form { private System.Windows.Forms.Timer blinkTimer; private Panel statusPanel; private Label lblStatus; private VlcControl vlcControl; private TextBox txtOrderNumber; private Button btnStart; private Button btnStop; private bool isRecording = false; public Form1() { CreateUI(); blinkTimer = new System.Windows.Forms.Timer { Interval = 500 }; blinkTimer.Tick += BlinkTimer_Tick; blinkTimer.Start(); // Запускаем сразу для удержания TopMost } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); const uint SWP_NOSIZE = 0x0001; const uint SWP_NOMOVE = 0x0002; const uint TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; private void CreateUI() { this.Text = "Контроль сборки заказов"; this.Size = new Size(900, 700); // 1. Всегда поверх всех окон this.TopMost = true; this.TopMost = false; this.TopMost = true; this.WindowState = FormWindowState.Normal; // 2. Убираем кнопки Свернуть, Развернуть и Закрыть (крестик) this.ControlBox = false; // 3. Запрещаем изменять размер окна (опционально, для красоты) this.FormBorderStyle = FormBorderStyle.FixedSingle; this.StartPosition = FormStartPosition.CenterScreen; this.BackColor = Color.FromArgb(240, 240, 240); Panel topPanel = new Panel { Dock = DockStyle.Top, Height = 80, BackColor = Color.FromArgb(220, 220, 220) }; Label lblOrder = new Label { Text = "Номер заказа:", Location = new Point(20, 28), Size = new Size(130, 25), Font = new Font("Segoe UI", 11F, FontStyle.Bold) }; txtOrderNumber = new TextBox { Location = new Point(160, 25), Size = new Size(250, 27), Font = new Font("Segoe UI", 11F), TextAlign = HorizontalAlignment.Center }; btnStart = new Button { Text = "▶ СТАРТ ЗАПИСИ", Location = new Point(430, 20), Size = new Size(170, 40), BackColor = Color.LightGreen, Font = new Font("Segoe UI", 11F, FontStyle.Bold), Enabled = false }; btnStart.Click += BtnStart_Click; btnStop = new Button { Text = "⏹ СТОП ЗАПИСИ", Location = new Point(620, 20), Size = new Size(170, 40), BackColor = Color.LightCoral, Font = new Font("Segoe UI", 11F, FontStyle.Bold), Enabled = false }; btnStop.Click += BtnStop_Click; txtOrderNumber.TextChanged += (s, e) => btnStart.Enabled = !string.IsNullOrWhiteSpace(txtOrderNumber.Text) && !isRecording; topPanel.Controls.AddRange(new Control[] { lblOrder, txtOrderNumber, btnStart, btnStop }); statusPanel = new Panel { Dock = DockStyle.Top, Height = 70, BackColor = Color.DarkRed, Visible = false }; lblStatus = new Label { Text = "", Font = new Font("Segoe UI", 20F, FontStyle.Bold), ForeColor = Color.White, TextAlign = ContentAlignment.MiddleCenter, Dock = DockStyle.Fill }; statusPanel.Controls.Add(lblStatus); vlcControl = new VlcControl { Dock = DockStyle.Fill, BackColor = Color.Black }; ((System.ComponentModel.ISupportInitialize)vlcControl).BeginInit(); vlcControl.VlcLibDirectoryNeeded += OnVlcLibDirectoryNeeded; ((System.ComponentModel.ISupportInitialize)vlcControl).EndInit(); this.Controls.Add(vlcControl); this.Controls.Add(statusPanel); this.Controls.Add(topPanel); } private void OnVlcLibDirectoryNeeded(object sender, VlcLibDirectoryNeededEventArgs e) { string vlcPath = @"C:\Program Files (x86)\VideoLAN\VLC"; if (!Directory.Exists(vlcPath)) { MessageBox.Show("Установите 32-битную версию VLC!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } e.VlcLibDirectory = new DirectoryInfo(vlcPath); } private void BtnStart_Click(object sender, EventArgs e) { string orderNumber = txtOrderNumber.Text.Trim(); string folderPath = @"D:\Video"; try { if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath); string fileName = $"Order_{orderNumber}_{DateTime.Now:yyyyMMdd_HHmmss}.mkv"; string fullPath = Path.Combine(folderPath, fileName).Replace("\\", "/"); string cameraUrl = "rtsp://admin:admin@192.168.0.20:554/Streaming/Channels/101"; // Дублируем поток H.265 в файл MKV и на экран без перекодирования var mediaOptions = new[] { $":sout=#duplicate{{dst=display,dst=std{{access=file,mux=ffmpeg{{mux=matroska}},dst='{fullPath}'}}}}", ":sout-keep" }; vlcControl.Stop(); vlcControl.Play(new Uri(cameraUrl), mediaOptions); isRecording = true; btnStart.Enabled = false; btnStop.Enabled = true; txtOrderNumber.Enabled = false; statusPanel.Visible = true; lblStatus.Text = $"⚠ ЗАПИСЬ ЗАКАЗА № {orderNumber} ⚠"; blinkTimer.Start(); this.Text = $"Запись заказа №{orderNumber}"; } catch (Exception ex) { MessageBox.Show($"Ошибка: {ex.Message}"); } SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); } private void BtnStop_Click(object sender, EventArgs e) { try { vlcControl.Stop(); isRecording = false; btnStop.Enabled = false; txtOrderNumber.Enabled = true; txtOrderNumber.Clear(); statusPanel.Visible = false; blinkTimer.Stop(); this.Text = "Контроль сборки заказов"; MessageBox.Show("Запись заказа успешно завершена и сохранена.", "Готово", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show($"Ошибка при остановке: {ex.Message}"); } } private void BlinkTimer_Tick(object sender, EventArgs e) { statusPanel.BackColor = (statusPanel.BackColor == Color.DarkRed) ? Color.Crimson : Color.DarkRed; lblStatus.ForeColor = (lblStatus.ForeColor == Color.White) ? Color.Yellow : Color.White; // ПРИНУДИТЕЛЬНО возвращаем окно в самый верх каждые 0.5 сек // Это не даст браузеру перекрыть программу во время работы SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); } } } В итоге получилась простенькая программа:
Рекомендуемые сообщения
Для публикации сообщений создайте учётную запись или авторизуйтесь
Вы должны быть пользователем, чтобы оставить комментарий
Создать аккаунт
Зарегистрируйте новый аккаунт в нашем сообществе. Это очень просто!
Регистрация нового пользователяВойти
Уже есть аккаунт? Войти в систему.
Войти