<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/App.xaml.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
using Serilog;
using Speaker.Recorder.Kinect;
using Speaker.Recorder.PowerPoint;
using Speaker.Recorder.Services;
using Speaker.Recorder.Services.IdentificationService;
using Speaker.Recorder.ViewModels;
using System;
using System.Diagnostics;
using System.Windows;

namespace Speaker.Recorder
{
    public partial class App : Application
    {
        public event EventHandler Loaded;

        public IHost AppHost { get; private set; }

        protected override async void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            this.AppHost = Host.CreateDefaultBuilder(e.Args)
                .ConfigureAppConfiguration((context, builder) =>
                {
                    builder.AddJsonFile("app.settings.json", optional: true, reloadOnChange: true)
                           .AddEnvironmentVariables();
                })
                .ConfigureServices((context, services) =>
                {
                    services.Configure<HostOptions>(o => o.ShutdownTimeout = TimeSpan.FromSeconds(30));

                    // Updater service must be the first!
                    services.AddTransient<IHostedService, AppUpdaterService>();
                    services.AddSingleton<IHostedService>(x => x.GetRequiredService<MainWindow>());
                    services.AddSingleton<IHostedService, KinectLoggingService>();

                    services.AddTransient<PlayerViewModel>();
                    services.AddTransient<RecordViewModel>();
                    services.AddTransient<Session>();
                    services.AddTransient<KinectRecorderService>();
                    services.AddTransient<PowerPointRecorderService>();

                    services.AddSingleton<SessionsService>();
                    services.AddSingleton<UploadManager>();
                    services.AddSingleton<LocalPathsHelper>();
                    services.AddSingleton<Func<KinectRecorderService>>(x => () => x.GetRequiredService<KinectRecorderService>());
                    services.AddSingleton<Func<PowerPointRecorderService>>(x => () => x.GetRequiredService<PowerPointRecorderService>());

                    services.AddSingleton<KinectCaptureProvidersFactory>();
                    services.AddSingleton<PowerPointGraphicsCaptureItemsFactory>();

                    services.AddSingleton((Application)this);
                    services.AddSingleton<MainWindow>();
                    services.AddSingleton<INavigationService>(x => x.GetRequiredService<MainWindow>());

                    var customIndetification = context.Configuration.GetValue("Data:SessionsIdentifier", (string)null);
                    if (string.IsNullOrEmpty(customIndetification))
                    {
                        services.AddSingleton<IIdentificationService, DesktopNameIdentificationService>();
                    }
                    else
                    {
                        services.AddSingleton((IIdentificationService)new CustomIdentificationService(customIndetification));
                    }
                })
                .ConfigureLogging((context, builder) =>
                {
                    var logger = new LoggerConfiguration()
                        .ReadFrom.Configuration(context.Configuration)
                        .CreateLogger();

                    builder.AddDebug();
                    builder.AddSerilog(logger);
                })
                .Build();

            PowerPointHelper.Initilize();

            await this.AppHost.StartAsync();

            this.Loaded?.Invoke(this, EventArgs.Empty);
            this.Loaded = null;

            await this.AppHost.WaitForShutdownAsync();
            this.Shutdown();
        }

        protected override async void OnExit(ExitEventArgs e)
        {
            base.OnExit(e);
            await this.AppHost.StopAsync();
            this.AppHost.Dispose();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Base/RelayCommand`1.cs<|end_filename|>
﻿using System;
using System.Windows.Input;

namespace Speaker.Recorder.ViewModels.Base
{
    public class RelayCommand<T> : ICommand
    {
        private readonly Action<T> executedMethod;
        private readonly Func<T, bool> canExecuteMethod;

        public event EventHandler CanExecuteChanged;

        public RelayCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<T> execute, Func<T, bool> canExecute)
        {
            this.executedMethod = execute ?? throw new ArgumentNullException(nameof(execute));
            this.canExecuteMethod = canExecute;
        }

        public bool CanExecute(object parameter) => this.canExecuteMethod == null || this.canExecuteMethod((T)parameter);

        public void Execute(object parameter) => this.executedMethod((T)parameter);

        public void OnCanExecuteChanged() => this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Kinect/KinectCaptureProvidersFactory.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;

namespace Speaker.Recorder.Kinect
{
    public class KinectCaptureProvidersFactory
    {
        private readonly Dictionary<object, (int count, KinectCaptureProvider provider)> currentCaptureProviders = new Dictionary<object, (int, KinectCaptureProvider)>();
        private readonly ILogger<KinectCaptureProvidersFactory> logger;
        private readonly IConfiguration configuration;

        public KinectCaptureProvidersFactory(ILogger<KinectCaptureProvidersFactory> logger, IConfiguration configuration)
        {
            this.logger = logger;
            this.configuration = configuration;
        }

        public void Remove(object key)
        {
            if (key != null && currentCaptureProviders.TryGetValue(key, out var current))
            {
                current.count--;
                this.logger.LogInformation($"KinectProvider for {key} count decremented {current.count}");
                if (current.count <= 0)
                {
                    this.logger.LogInformation($"Removing KinectProvider for {key} and killing process");
                    currentCaptureProviders.Remove(key);
                    current.provider.Dispose();
                }
                else
                {
                    currentCaptureProviders[key] = current;
                }
            }
        }

        public KinectCaptureProvider GetOrCreateProvider(object key)
        {
            if (key != null)
            {
                if (!currentCaptureProviders.TryGetValue(key, out var current))
                {
                    this.logger.LogInformation($"Creating KinectProvider for {key}");
                    KinectCaptureProvider provider = key switch
                    {
                        int deviceIndex when deviceIndex >= 0 => new KinectCaptureProvider(deviceIndex, this.configuration),
                        string playbackPath when playbackPath != null => new KinectCaptureProvider(playbackPath),
                        _ => throw new InvalidOperationException("The Azure Kinect provider only can be opened from a deviceId or a plaback file path."),
                    };

                    current = (0, provider);
                }

                this.logger.LogInformation($"KinectProvider for {key} count incremented {current.count}");
                currentCaptureProviders[key] = (++current.count, current.provider);
                return current.provider;
            }

            return null;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/INavigationService.cs<|end_filename|>
﻿using Microsoft.Extensions.Hosting;

namespace Speaker.Recorder.Services
{
    public interface INavigationService : IHostedService
    {
        void To<TViewModel>();

        void Back();
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/TypeConverterHelper.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows.Controls;
using System.Windows.Markup;

namespace Speaker.Recorder.Helpers
{
    internal static class TypeConverterHelper
    {
        private const string ContentControlFormatString = "<ContentControl xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:c='using:{0}'><c:{1}>{2}</c:{1}></ContentControl>";

        /// <summary>
        /// Converts string representation of a value to its object representation.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <param name="destinationTypeFullName">The full name of the destination type.</param>
        /// <returns>Object representation of the string value.</returns>
        /// <exception cref="ArgumentNullException">destinationTypeFullName cannot be null.</exception>
        public static Object Convert(string value, string destinationTypeFullName)
        {
            if (string.IsNullOrEmpty(destinationTypeFullName))
            {
                throw new ArgumentNullException(nameof(destinationTypeFullName));
            }

            string scope = TypeConverterHelper.GetScope(destinationTypeFullName);

            // Value types in the "System" namespace must be special cased due to a bug in the xaml compiler
            if (string.Equals(scope, "System", StringComparison.Ordinal))
            {
                if (string.Equals(destinationTypeFullName, (typeof(string).FullName), StringComparison.Ordinal))
                {
                    return value;
                }
                else if (string.Equals(destinationTypeFullName, typeof(bool).FullName, StringComparison.Ordinal))
                {
                    return bool.Parse(value);
                }
                else if (string.Equals(destinationTypeFullName, typeof(int).FullName, StringComparison.Ordinal))
                {
                    return int.Parse(value, CultureInfo.InvariantCulture);
                }
                else if (string.Equals(destinationTypeFullName, typeof(double).FullName, StringComparison.Ordinal))
                {
                    return double.Parse(value, CultureInfo.InvariantCulture);
                }
            }

            string type = TypeConverterHelper.GetType(destinationTypeFullName);
            string contentControlXaml = string.Format(CultureInfo.InvariantCulture, TypeConverterHelper.ContentControlFormatString, scope, type, value);

            ContentControl contentControl = XamlReader.Parse(contentControlXaml) as ContentControl;
            if (contentControl != null)
            {
                return contentControl.Content;
            }

            return null;
        }

        private static String GetScope(string name)
        {
            int indexOfLastPeriod = name.LastIndexOf('.');
            if (indexOfLastPeriod != name.Length - 1)
            {
                return name.Substring(0, indexOfLastPeriod);
            }

            return name;
        }

        private static String GetType(string name)
        {
            int indexOfLastPeriod = name.LastIndexOf('.');
            if (indexOfLastPeriod != name.Length - 1)
            {
                return name.Substring(++indexOfLastPeriod);
            }

            return name;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/UploadManager.cs<|end_filename|>
﻿using Microsoft.Azure.Storage.Blob;
using Microsoft.Azure.Storage;
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Storage.DataMovement;
using System.Threading;
using System.IO;
using Speaker.Recorder.Services.IdentificationService;
using System.Net;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Polly;
using Microsoft.Extensions.Logging;

namespace Speaker.Recorder.Services
{
    public class UploadManager : IDisposable
    {
        public const string FileUploadSearchPattern = "*";

        private int uploadRetries;
        private Task<CloudBlobContainer> cloudBlobContainerTask;
        private readonly CancellationTokenSource disposeCancellation;
        private readonly string StorageConnectionString;
        private readonly ILogger<UploadManager> logger;
        private readonly string GlobalIdentifier;

        public bool IsUploadAvailable { get; private set; }

        public UploadManager(IIdentificationService identificationService, IConfiguration configuration, ILogger<UploadManager> logger)
        {
            this.disposeCancellation = new CancellationTokenSource();
            this.StorageConnectionString = configuration.GetValue<string>("Data:AzureBlobConnection");
            this.GlobalIdentifier = identificationService.GetSanitizedIdentifier();
            this.logger = logger;
            var connections = configuration.GetValue("Data:DefaultConnectionLimit", Environment.ProcessorCount - 2);
            ServicePointManager.DefaultConnectionLimit = Math.Max(connections, 1);

            this.uploadRetries = configuration.GetValue("Data:UploadRetries", 10);

            this.cloudBlobContainerTask = this.InitializeContainerAsync();
        }

        private async Task<CloudBlobContainer> InitializeContainerAsync()
        {
            if (string.IsNullOrEmpty(this.StorageConnectionString) || !CloudStorageAccount.TryParse(this.StorageConnectionString, out var account))
            {
                this.IsUploadAvailable = false;
                this.logger.LogError($"The storage account connectionstring is not valid.");
                throw new InvalidOperationException("The storage account connectionstring is not valid.");
            }

            this.IsUploadAvailable = true;
            this.logger.LogInformation($"Initializing Cloud storage for {GlobalIdentifier} in {account.BlobEndpoint}");
            CloudBlobClient blobClient = account.CreateCloudBlobClient();
            blobClient.DefaultRequestOptions.MaximumExecutionTime = TimeSpan.FromSeconds(10);
            blobClient.DefaultRequestOptions.ServerTimeout = TimeSpan.FromSeconds(10);
            CloudBlobContainer blobContainer = blobClient.GetContainerReference(GlobalIdentifier);
            if (await blobContainer.CreateIfNotExistsAsync().ConfigureAwait(false))
            {
                this.logger.LogInformation($"The Cloud storage for {GlobalIdentifier} did not exists and was created");
            }
            return blobContainer;
        }

        public bool ExistsUploadFile(Session session)
        {
            var journalFile = new FileInfo(this.GetJournalFileForSession(session.LocalRecordingFolderPath));
            return journalFile.Exists && journalFile.Length > 0;
        }

        public async Task<DateTime?> GetUploadedDateTime(Session session)
        {
            if (this.IsUploadAvailable)
            {
                try
                {
                    var localSessionDirectory = session.LocalRecordingFolderPath;
                    var localFiles = Directory.GetFiles(localSessionDirectory);

                    if (localFiles.Any())
                    {
                        var blobDirectoryReference = await GetDirectoryBlobAsync(session.Id).ConfigureAwait(false);
                        var blobListing = blobDirectoryReference.ListBlobs(blobListingDetails: BlobListingDetails.Metadata).OfType<CloudBlockBlob>().ToList();

                        if (blobListing.Any())
                        {
                            bool allFilesAreInRemoteFolder = localFiles.All(x => blobListing.Any(y => Path.GetFileName(y.Name) == Path.GetFileName(x)));
                            if (allFilesAreInRemoteFolder)
                            {
                                return blobListing.Min(x => x.Properties.LastModified ?? DateTimeOffset.MinValue).ToLocalTime().DateTime;
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    this.logger.LogWarning(e, $"The uploaded info for session {session.Id} can not be obtained.");
                }
            }

            return null;
        }

        public async Task<bool> UploadSessionAsync(Session session, IProgress<TransferStatus> progressHandler, CancellationToken cancellationToken)
        {
            Task<bool> UploadSessionTry(Context context, CancellationToken cancellationToken)
            {
                this.logger.LogInformation($"Uploading session {session.Id}, count {context.Count}");
                return this.InternalUploadSessionAsync(session, progressHandler, cancellationToken);
            }

            var retry = Policy.Handle<Exception>().RetryAsync(this.uploadRetries);
            return await retry.ExecuteAsync(UploadSessionTry, new Context(), cancellationToken).ConfigureAwait(false);
        }

        private async Task<bool> InternalUploadSessionAsync(Session session, IProgress<TransferStatus> progressHandler, CancellationToken cancellationToken)
        {
            var srcDirectoryPath = session.LocalRecordingFolderPath;
            var dstBlob = await GetDirectoryBlobAsync(session.Id).ConfigureAwait(false);
            var journalFile = this.GetJournalFileForSession(srcDirectoryPath);

            using var journalStream = await this.GetJournalStream(session, journalFile).ConfigureAwait(false);
            using var linkedCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.disposeCancellation.Token);
            using var reg = linkedCancellation.Token.Register(() =>
            {
                this.logger.LogDebug($"The transfer has been cancelled and the journal file for session {session.Id} will be saved");
                journalStream.Position = 0;
                using var journal = File.Create(journalFile);
                journalStream.CopyTo(journal);
            });

            try
            {
                var options = new UploadDirectoryOptions()
                {
                    SearchPattern = FileUploadSearchPattern,
                    Recursive = false,
                    BlobType = BlobType.BlockBlob
                };
                var context = new DirectoryTransferContext(journalStream)
                {
                    ProgressHandler = progressHandler,
                    LogLevel = Microsoft.Azure.Storage.LogLevel.Informational
                };

                var transferTcs = new TaskCompletionSource<bool>();
                context.FileFailed += (sender, e) =>
                {
                    this.logger.LogInformation(e.Exception, $"Transfer fails. {e.Source} -> {e.Destination}.");
                    transferTcs.TrySetException(e.Exception);
                    if (!linkedCancellation.IsCancellationRequested)
                    {
                        linkedCancellation.Cancel();
                    }
                };
                context.FileSkipped += (sender, e) =>
                {
                    this.logger.LogInformation($"Transfer skips. {e.Source} -> {e.Destination}.");
                };
                context.FileTransferred += (sender, e) =>
                {
                    this.logger.LogInformation(e.Exception, $"Transfer succeds. {e.Source} -> {e.Destination}.");
                };

                // Start the upload
                var trasferStatus = await TransferManager.UploadDirectoryAsync(srcDirectoryPath, dstBlob, options, context, linkedCancellation.Token).ConfigureAwait(false);
                transferTcs.TrySetResult(true);
                await transferTcs.Task.ConfigureAwait(false);
            }
            catch (OperationCanceledException e)
            {
                this.logger.LogInformation(e, $"The transfer {session.Id} has been cancelled");
            }
            catch (InvalidOperationException e) when (e.Message.Contains("Only one transfer is allowed with stream journal."))
            {
                this.logger.LogWarning(e, $"The transfer {session.Id} has failed");
                File.Delete(journalFile);
                throw;
            }
            catch (Exception e)
            {
                linkedCancellation.Cancel();
                this.logger.LogWarning(e, $"The transfer {session.Id} has failed");
                throw;
            }

            if (!linkedCancellation.IsCancellationRequested)
            {
                File.Delete(journalFile);
                return true;
            }

            return false;
        }

        private async Task<Stream> GetJournalStream(Session session, string journalFile)
        {
            var journalStream = new MemoryStream();
            if (File.Exists(journalFile))
            {
                this.logger.LogDebug($"The journal file for session {session.Id} exists and will be used to resume the upload");
                using var current = File.OpenRead(journalFile);
                await current.CopyToAsync(journalStream).ConfigureAwait(false);
                journalStream.Position = 0;
            }

            return journalStream;
        }

        private string GetJournalFileForSession(string srcDirectoryPath)
        {
            var journalDir = Directory.CreateDirectory(Path.Combine(srcDirectoryPath, "upload"));
            var journalFile = Path.Combine(journalDir.FullName, ".journal");
            return journalFile;
        }

        private async Task<CloudBlobDirectory> GetDirectoryBlobAsync(string sessionIdentifier)
        {
            var blobContainer = await this.cloudBlobContainerTask.ConfigureAwait(false);
            return blobContainer.GetDirectoryReference(sessionIdentifier);
        }

        public void RemoveUploadStatus(Session session)
        {
            var srcDirectoryPath = session.LocalRecordingFolderPath;
            string journalFile = this.GetJournalFileForSession(srcDirectoryPath);
            File.Delete(journalFile);
        }

        public void Dispose()
        {
            this.disposeCancellation.Cancel();
        }
    }

}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Controls/KinectD3DImage.cs<|end_filename|>
﻿using System;
using System.Windows;
using System.Windows.Interop;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Direct3D9;
using SharpDX.DXGI;
using SharpDX.WIC;
using Windows.Graphics;
using Windows.Graphics.DirectX.Direct3D11;
using Speaker.Recorder.Kinect;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Controls
{
    public class KinectD3DImage : D3DImage, IDisposable
    {
        private Texture2DDescription renderTargetDescription;
        private SizeInt32 lastSize;
        private IDirect3DDevice device;
        private Direct3DEx direct3d;
        private ImagingFactory imagingFactory;
        private DeviceEx d9device;
        private SharpDX.Direct3D11.Device d3dDevice;
        private SharpDX.Direct3D11.Texture2D cpuRenderTarget;
        private SharpDX.Direct3D11.Texture2D renderTarget;
        private bool backBufferSetted;

        private KinectImageProvider provider;
        private ManualResetEventSlim renderEvent;
        private DecodedKinectCaptureFrame lastFrame;
        private bool disposed;
        private long fpsCount;

        public int FpsDivider { get; set; } = 1;

        public int RenderWidth { get; set; } = -1;

        public int RenderHeight { get; set; } = -1;

        public KinectD3DImage()
        {
            if (App.Current.MainWindow != null)
            {
                IntPtr window = new WindowInteropHelper(App.Current.MainWindow).Handle;
                var presentparams = new SharpDX.Direct3D9.PresentParameters
                {
                    Windowed = true,
                    SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
                    DeviceWindowHandle = window,
                    PresentationInterval = PresentInterval.Immediate,
                };

                const CreateFlags deviceFlags = CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve;

                this.direct3d = new Direct3DEx();
                this.imagingFactory = new ImagingFactory();
                this.device = Capture.Direct3D11Helper.CreateDevice();
                this.d3dDevice = Capture.Direct3D11Helper.CreateSharpDXDevice(this.device);
                this.d9device = new DeviceEx(this.direct3d, 0, DeviceType.Hardware, IntPtr.Zero, deviceFlags, presentparams);
                this.IsFrontBufferAvailableChanged += this.GraphicItemD3DImageIsFrontBufferAvailableChanged;
                this.renderEvent = new ManualResetEventSlim(false);
                var renderThread = new Thread(this.RenderThread);
                renderThread.Start();
            }
        }

        public void SetProvider(KinectImageProvider provider)
        {
            this.ResetState();

            this.provider = provider;
            if (this.provider != null)
            {
                this.provider.FrameArrived += this.OnFrameArrived;
            }
        }

        private void RenderThread(object obj)
        {
            var providerParam = new KinectImageProvider[1];
            try
            {
                while (!this.Dispatcher.HasShutdownStarted && !this.disposed)
                {
                    try
                    {
                        if (this.renderEvent.Wait(100))
                        {
                            this.renderEvent.Reset();

                            if (this.disposed)
                            {
                                return;
                            }

                            using var frame = Interlocked.Exchange(ref this.lastFrame, null);
                            if (frame != null && frame.Bitmap != null)
                            {
                                var frameSize = frame.Bitmap.Size;
                                var ratio = 1d;
                                if (this.RenderWidth > 0 && frameSize.Width > frameSize.Height)
                                {
                                    ratio = (double)this.RenderWidth / frameSize.Width;
                                }

                                if (this.RenderHeight > 0 && frameSize.Height > frameSize.Width)
                                {
                                    ratio = (double)this.RenderHeight / frameSize.Height;
                                }

                                ratio = ratio > 1 ? 1 : ratio;

                                var desiredSize = new Size2((int)(frameSize.Width * ratio), (int)(frameSize.Height * ratio));
                                if (this.renderTarget == null || this.renderTargetDescription.Width != desiredSize.Width || this.renderTargetDescription.Height != desiredSize.Height)
                                {
                                    this.renderTarget?.Dispose();
                                    this.cpuRenderTarget?.Dispose();
                                    this.CreateRenderTargets(desiredSize);
                                }

                                using var scale = new BitmapScaler(this.imagingFactory);
                                scale.Initialize(frame.Bitmap, desiredSize.Width, desiredSize.Height, BitmapInterpolationMode.Linear);
                                using var bitmap = new Bitmap(this.imagingFactory, scale, BitmapCreateCacheOption.NoCache);
                                using var bitmapLock = bitmap.Lock(BitmapLockFlags.Read);
                                DataBox imageDataBox = new DataBox(bitmapLock.Data.DataPointer, bitmapLock.Data.Pitch, bitmapLock.Data.Pitch * bitmapLock.Size.Height);
                                lock (d3dDevice)
                                {
                                    if (!this.d3dDevice.IsDisposed)
                                    {
                                        d3dDevice.ImmediateContext.UpdateSubresource(imageDataBox, this.cpuRenderTarget);
                                        d3dDevice.ImmediateContext.CopyResource(this.cpuRenderTarget, this.renderTarget);
                                        d3dDevice.ImmediateContext.Flush();
                                    }
                                }

                                if (!this.Dispatcher.HasShutdownStarted && !this.disposed)
                                {
                                    providerParam[0] = frame.Provider;
                                    this.Dispatcher.Invoke((InvokeRenderWithProvider)this.RequestRenderForProvider, providerParam);
                                }
                            }
                        }
                    }
                    catch (Exception e) when (!(e is OperationCanceledException))
                    {
                    }
                }
            }
            catch (OperationCanceledException)
            {
            }
        }

        private delegate void InvokeRenderWithProvider(KinectImageProvider provider);

        private void RequestRenderForProvider(KinectImageProvider provider)
        {
            if (this.provider == provider)
            {
                this.RequestRender();
            }
        }

        private void OnFrameArrived(object sender, EventArgs e)
        {
            this.fpsCount++;
            if (this.fpsCount % this.FpsDivider != 0)
            {
                return;
            }

            var provider = this.provider;
            if (provider != null)
            {
                var old = Interlocked.Exchange(ref this.lastFrame, provider.TryGetLastFrame());
                old?.Dispose();
                this.renderEvent.Set();
            }
        }

        private void CreateRenderTargets(Size2 size)
        {
            this.lastSize = new SizeInt32 { Width = size.Width, Height = size.Height };
            this.renderTargetDescription = new Texture2DDescription
            {
                CpuAccessFlags = CpuAccessFlags.None,
                Width = this.lastSize.Width,
                Height = this.lastSize.Height,
                Usage = ResourceUsage.Default,
                Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
                ArraySize = 1,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                OptionFlags = ResourceOptionFlags.Shared,
                MipLevels = 1,
                SampleDescription = new SampleDescription(1, 0),
            };
            this.renderTarget = new Texture2D(d3dDevice, this.renderTargetDescription);
            var cpuRenderTargetDescription = new Texture2DDescription
            {
                CpuAccessFlags = CpuAccessFlags.Write,
                Width = this.lastSize.Width,
                Height = this.lastSize.Height,
                Usage = ResourceUsage.Default,
                Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
                ArraySize = 1,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                OptionFlags = ResourceOptionFlags.None,
                MipLevels = 1,
                SampleDescription = new SampleDescription(1, 0),
            };
            this.cpuRenderTarget = new Texture2D(d3dDevice, cpuRenderTargetDescription);
            this.backBufferSetted = false;
        }

        private void ResetState(bool resetBackBuffer = true)
        {
            this.backBufferSetted = false;
            if (this.provider != null)
            {
                this.provider.FrameArrived -= this.OnFrameArrived;
            }

            if (resetBackBuffer)
            {
                this.Lock();
                this.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero, true);
                this.Unlock();
            }
        }

        private void GraphicItemD3DImageIsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            this.RequestRender();
        }

        public void RequestRender()
        {
            if (this.IsFrontBufferAvailable && !this.disposed)
            {
                if (this.TryLock(new Duration(TimeSpan.Zero)))
                {
                    if (!this.backBufferSetted)
                    {
                        using var resource = this.renderTarget.QueryInterface<SharpDX.DXGI.Resource>();
                        var handle = resource.SharedHandle;
                        using var texture = new Texture(
                            this.d9device,
                            this.renderTarget.Description.Width,
                            this.renderTarget.Description.Height,
                            1,
                            SharpDX.Direct3D9.Usage.RenderTarget,
                            SharpDX.Direct3D9.Format.A8R8G8B8,
                            Pool.Default,
                            ref handle);
                        using var surface = texture.GetSurfaceLevel(0);
                        this.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface.NativePointer, true);
                        this.backBufferSetted = true;
                    }

                    this.AddDirtyRect(new Int32Rect(0, 0, lastSize.Width, lastSize.Height));

                }

                this.Unlock();
            }
        }

        public void Dispose()
        {
            this.disposed = true;
            this.renderEvent?.Set();
            if (this.d3dDevice != null)
            {
                lock (this.d3dDevice)
                {
                    this.ResetState(false);
                    this.renderTarget?.Dispose();
                    this.d3dDevice?.Dispose();
                    this.device?.Dispose();
                }
            }
        }
    }
}

<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/SessionsService.cs<|end_filename|>
﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Speaker.Recorder.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace Speaker.Recorder.Services
{
    public class SessionsService
    {
        private readonly IServiceProvider serviceProvider;
        private readonly LocalPathsHelper localPathsHelper;

        public SessionsService(LocalPathsHelper localPathsHelper, IServiceProvider serviceProvider)
        {
            this.localPathsHelper = localPathsHelper;
            this.serviceProvider = serviceProvider;
        }

        public IEnumerable<Session> GetSessions()
        {
            var directories = Directory.GetDirectories(localPathsHelper.GetBasePath());

            if (!directories.Any())
            {
                return Enumerable.Empty<Session>();
            }
            else
            {
                var sessionTasks = directories
                    .Select(x => (session: this.serviceProvider.GetRequiredService<Session>(), directory: x))
                    .Select(y => (session: y.session, init: y.session.Initialize(y.directory)));

                return sessionTasks.Where(x => x.init).Select(x => x.session);
            }
        }

        public RecordingSession StartRecordingSession
            (KinectRecorderService[] kinectRecorderServices, 
            PowerPointRecorderService powerPointRecorderService,
            string powerPointPresentationPath,
            string sessionPath,
            CancellationTokenSource cancellationTokenSource)
        {
            var recordingSession = new RecordingSession(
                kinectRecorderServices, 
                powerPointRecorderService,
                powerPointPresentationPath,
                sessionPath,
                cancellationTokenSource,
                serviceProvider.GetRequiredService<ILogger<RecordingSession>>());

            recordingSession.Start();

            return recordingSession;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Kinect/KinectImageProvider.cs<|end_filename|>
﻿using Microsoft.Azure.Kinect.Sensor;
using SharpDX.WIC;
using System;
using System.Threading;

namespace Speaker.Recorder.Kinect
{
    public class KinectImageProvider : IDisposable
    {
        private event EventHandler frameArrived;

        public event EventHandler FrameArrived
        {
            add
            {
                this.frameArrived += value;
                if (this.frameArrived?.GetInvocationList().Length == 1)
                {
                    this.provider.CaptureArrived += this.OnCaptureArrived;
                }
            }
            remove
            {
                this.frameArrived -= value;
                if (this.frameArrived == null || this.frameArrived.GetInvocationList().Length == 0)
                {
                    this.provider.CaptureArrived -= this.OnCaptureArrived;
                }
            }
        }

        private ImagingFactory imagingFactory;
        private DecodedKinectCaptureFrame lastFrame;
        private KinectCaptureProvider provider;

        public KinectCaptureImage ImageType { get; }

        public KinectImageProvider(KinectCaptureProvider provider, KinectCaptureImage type)
        {
            this.imagingFactory = new ImagingFactory();
            this.provider = provider;
            this.ImageType = type;
        }

        private void OnCaptureArrived(object sender, Microsoft.Azure.Kinect.Sensor.Capture capture)
        {
            var handler = this.frameArrived;
            if (handler != null)
            {
                using var image = this.GetImage(capture)?.Reference();
                handler = this.frameArrived;
                if (image != null && handler != null)
                {
                    var frame = new DecodedKinectCaptureFrame(image.Reference(), this);
                    var oldFrame = Interlocked.Exchange(ref this.lastFrame, frame);
                    oldFrame?.Dispose();

                    handler = this.frameArrived;
                    handler?.Invoke(this, EventArgs.Empty);
                }
            }
        }

        public DecodedKinectCaptureFrame TryGetLastFrame()
        {
            return Interlocked.Exchange(ref this.lastFrame, null);
        }

        private Image GetImage(Microsoft.Azure.Kinect.Sensor.Capture capture)
        {
            return this.ImageType switch
            {
                KinectCaptureImage.Depth => capture.Depth,
                KinectCaptureImage.IR => capture.IR,
                _ => capture.Color,
            };
        }

        public void Dispose()
        {
            this.provider.CaptureArrived -= this.OnCaptureArrived;
            this.imagingFactory.Dispose();
            this.frameArrived = null;
            Interlocked.Exchange(ref this.lastFrame, null)?.Dispose();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Capture/SurfaceWithInfo.cs<|end_filename|>
﻿using System;
using Windows.Graphics.DirectX.Direct3D11;

namespace Speaker.Recorder.Capture
{
    public sealed class SurfaceWithInfo : IDisposable
    {
        public IDirect3DSurface Surface { get; internal set; }

        public TimeSpan SystemRelativeTime { get; internal set; }

        public void Dispose()
        {
            Surface?.Dispose();
            Surface = null;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/IdentificationService/CustomIdentificationService.cs<|end_filename|>
﻿namespace Speaker.Recorder.Services.IdentificationService
{
    public class CustomIdentificationService : BaseIdentificationService
    {
        public string SessionsIdentifier { get; }

        public CustomIdentificationService(string customIdentification)
        {
            this.SessionsIdentifier = customIdentification;
        }

        public override string GetRawIdentifier()
        {
            return this.SessionsIdentifier;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Controls/KinectControl.cs<|end_filename|>
﻿using Microsoft.Extensions.DependencyInjection;
using Speaker.Recorder.Kinect;
using System;
using System.Windows;
using System.Windows.Controls;
using Image = System.Windows.Controls.Image;

namespace Speaker.Recorder.Controls
{
    [TemplatePart(Name = ImageHostPart, Type = typeof(Image))]
    public class KinectControl : Control
    {
        protected const string ImageHostPart = "PART_ImageHost";
        private Image imageHost;
        private KinectD3DImage imageHostSource;
        private KinectCaptureProvider captureProvider;
        private KinectImageProvider imageProvider;
        private object lastKey;
        private KinectCaptureProvidersFactory kinectCaptureProvidersFactory;
        public static readonly DependencyProperty DeviceIndexProperty = DependencyProperty.Register(nameof(DeviceIndex), typeof(int), typeof(KinectControl), new PropertyMetadata(-1, DeviceIndexChanged));

        public int DeviceIndex
        {
            get { return (int)GetValue(DeviceIndexProperty); }
            set { SetValue(DeviceIndexProperty, value); }
        }

        public static readonly DependencyProperty PlaybackPathProperty = DependencyProperty.Register(nameof(PlaybackPath), typeof(string), typeof(KinectControl), new PropertyMetadata(null, PlaybackPathChanged));


        public string PlaybackPath
        {
            get { return (string)GetValue(PlaybackPathProperty); }
            set { SetValue(PlaybackPathProperty, value); }
        }

        public static readonly DependencyProperty SelectedImageProperty = DependencyProperty.Register(nameof(SelectedImage), typeof(KinectCaptureImage), typeof(KinectControl), new PropertyMetadata(KinectCaptureImage.Color, ProviderPropertyChanged));

        public KinectCaptureImage SelectedImage
        {
            get { return (KinectCaptureImage)GetValue(SelectedImageProperty); }
            set { SetValue(SelectedImageProperty, value); }
        }

        public static readonly DependencyProperty IsFailedProperty = DependencyProperty.Register(nameof(IsFailed), typeof(bool), typeof(KinectControl), new PropertyMetadata(false));

        public bool IsFailed
        {
            get { return (bool)GetValue(IsFailedProperty); }
            set { SetValue(IsFailedProperty, value); }
        }

        public static readonly DependencyProperty RenderWidthProperty = DependencyProperty.Register(nameof(RenderWidth), typeof(double), typeof(KinectControl), new PropertyMetadata(double.NaN, ProviderPropertyChanged));

        public double RenderWidth
        {
            get { return (double)GetValue(RenderWidthProperty); }
            set { SetValue(RenderWidthProperty, value); }
        }

        public static readonly DependencyProperty RenderHeightProperty = DependencyProperty.Register(nameof(RenderHeight), typeof(double), typeof(KinectControl), new PropertyMetadata(double.NaN, ProviderPropertyChanged));

        public double RenderHeight
        {
            get { return (double)GetValue(RenderHeightProperty); }
            set { SetValue(RenderHeightProperty, value); }
        }

        public static readonly DependencyProperty FpsDividerProperty = DependencyProperty.Register(nameof(FpsDivider), typeof(int), typeof(KinectControl), new PropertyMetadata(1, ProviderPropertyChanged));

        public int FpsDivider
        {
            get { return (int)GetValue(FpsDividerProperty); }
            set { SetValue(FpsDividerProperty, value); }
        }

        static KinectControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(KinectControl), new FrameworkPropertyMetadata(typeof(KinectControl)));
        }

        public KinectControl()
        {
            this.kinectCaptureProvidersFactory = (Application.Current as App)?.AppHost.Services.GetRequiredService<KinectCaptureProvidersFactory>();
            this.Dispatcher.ShutdownStarted += this.DispatcherShutdownStarted;
            this.Loaded += this.OnLoaded;
            this.Unloaded += this.OnUnloaded;
            this.IsVisibleChanged += this.OnIsVisibleChanged;
        }

        private void DispatcherShutdownStarted(object sender, EventArgs e)
        {
            this.UpdateProvider(null);
            this.imageHostSource?.Dispose();
        }

        private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                this.UpdateSource();
            }
            else
            {
                this.imageHostSource?.SetProvider(null);
            }
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            this.UpdateProvider(null);
            this.imageHostSource?.Dispose();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.UpdateSource();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (this.imageHost != null)
            {
                this.imageHostSource?.Dispose();
                this.imageHost.Source = null;
            }

            this.imageHost = (Image)this.GetTemplateChild(ImageHostPart);
            this.imageHostSource = new KinectD3DImage();
            this.imageHost.Source = this.imageHostSource;
        }

        private void UpdateProvider(object newValue)
        {
            this.IsFailed = false;

            if (this.captureProvider != null)
            {
                this.captureProvider.IsConnectedChanged -= this.CaptureProviderIsConnectedChanged;
            }

            this.kinectCaptureProvidersFactory.Remove(this.lastKey);

            if (App.Current?.MainWindow == null)
            {
                return;
            }

            KinectCaptureProvider next = null;
            if (newValue is string path && path != null || newValue is int index && index >= 0)
            {
                next = this.kinectCaptureProvidersFactory.GetOrCreateProvider(newValue);
            }

            this.lastKey = newValue;
            this.captureProvider = next;
            if (this.captureProvider != null)
            {
                this.captureProvider.IsConnectedChanged += this.CaptureProviderIsConnectedChanged;
            }

            this.CaptureProviderIsConnectedChanged(null, null);
            this.UpdateSource();
        }

        private void CaptureProviderIsConnectedChanged(object sender, System.EventArgs e)
        {
            this.Dispatcher.InvokeAsync(() =>
            {
                this.IsFailed = this.captureProvider?.IsConnected == null ? false : !this.captureProvider.IsConnected.Value;
            });
        }

        private void UpdateSource()
        {
            if (this.IsVisible)
            {
                this.imageProvider?.Dispose();
                this.imageProvider = this.captureProvider?.For(this.SelectedImage);
                if (this.imageHostSource != null)
                {
                    this.imageHostSource.FpsDivider = this.FpsDivider;
                    this.imageHostSource.RenderWidth = (int)(double.IsNaN(this.RenderWidth) ? -1 : this.RenderWidth);
                    this.imageHostSource.RenderHeight = (int)(double.IsNaN(this.RenderHeight) ? -1 : this.RenderHeight);
                    this.imageHostSource.SetProvider(this.imageProvider);
                }
            }
        }

        private static void PlaybackPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((KinectControl)d).UpdateProvider(e.NewValue);
        }

        private static void DeviceIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((KinectControl)d).UpdateProvider(e.NewValue);
        }

        private static void ProviderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((KinectControl)d).UpdateSource();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Base/NotifyPropertyObject.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Speaker.Recorder.ViewModels.Base
{
    public abstract class NotifyPropertyObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName]string propertyName = "")
        {
            if (this.AreEquals(ref backingStore, ref value, propertyName))
            {
                return false;
            }

            backingStore = value;
            this.OnPropertyChanged(propertyName);

            return true;
        }

        protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            if (this.PropertyChanged == null)
            {
                return;
            }

            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual bool AreEquals<T>(ref T backingStore, ref T value, string propertyName)
        {
            return EqualityComparer<T>.Default.Equals(backingStore, value);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/NvApiLoader.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace Speaker.Recorder.Helpers
{
    public class NvApiLoader
    {
        public static bool TryLoad()
        {
            var libName = Environment.Is64BitProcess ? "nvapi64.dll" : "nvapi.dll";
            return NativeLibrary.TryLoad(libName, out _);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Capture/Encoder.cs<|end_filename|>
﻿using NAudio.Wave;
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Media.Transcoding;
using Windows.Storage.Streams;

namespace Speaker.Recorder.Capture
{
    public sealed class Encoder : IDisposable
    {
        private IDirect3DDevice device;

        private Func<GraphicsCaptureItem> captureItemFactory;
        private IWaveIn waveIn;
        private BufferedWaveProvider audioProvider;
        private CaptureFrameWait frameGenerator;

        private VideoStreamDescriptor videoDescriptor;
        private AudioStreamDescriptor audioDescriptor;
        private MediaStreamSource mediaStreamSource;
        private MediaTranscoder transcoder;
        private bool isRecording;
        private bool closed = false;
        private TimeSpan audioTime;
        private Stopwatch encodingTime;

        public TimeSpan Time { get => encodingTime?.Elapsed ?? TimeSpan.Zero; }

        public Encoder(IDirect3DDevice device, Func<GraphicsCaptureItem> itemFactory, IWaveIn waveIn)
        {
            this.device = device;
            this.captureItemFactory = itemFactory;
            this.waveIn = waveIn;
            this.isRecording = false;
        }

        public Task InitializeAsync()
        {
            if (this.captureItemFactory != null)
            {
                frameGenerator = new CaptureFrameWait(this.device, this.captureItemFactory);
            }

            var audioTcs = new TaskCompletionSource<bool>();
            void FirstDataAvailable(object sender, WaveInEventArgs e)
            {
                this.waveIn.DataAvailable -= FirstDataAvailable;
                audioTcs.TrySetResult(true);
            }

            this.waveIn.DataAvailable += FirstDataAvailable;
            this.waveIn.StartRecording();
            this.audioProvider = new BufferedWaveProvider(waveIn.WaveFormat) { DiscardOnBufferOverflow = true, ReadFully = false };
            this.CreateMediaObjects(this.frameGenerator?.CurrentItem);
            return audioTcs.Task;
        }

        public IAsyncAction EncodeAsync(IRandomAccessStream stream, MediaEncodingProfile encodingProfile)
        {
            return this.EncodeInternalAsync(stream, encodingProfile).AsAsyncAction();
        }

        private async Task EncodeInternalAsync(IRandomAccessStream stream, MediaEncodingProfile encodingProfile)
        {
            if (!this.isRecording)
            {
                this.isRecording = true;
                this.waveIn.DataAvailable += OnWaveInDataAvailable;
                var transcode = await this.transcoder.PrepareMediaStreamSourceTranscodeAsync(this.mediaStreamSource, stream, encodingProfile);
                await transcode.TranscodeAsync();
            }
        }

        public void Dispose()
        {
            if (this.closed)
            {
                return;
            }
            this.closed = true;
            this.waveIn.DataAvailable -= OnWaveInDataAvailable;
            this.mediaStreamSource.Starting -= OnMediaStreamSourceStarting;
            this.mediaStreamSource.SampleRequested -= OnMediaStreamSourceSampleRequested;
            this.waveIn.StopRecording();

            if (!this.isRecording)
            {
                this.DisposeInternal();
            }

            this.isRecording = false;
        }

        private void DisposeInternal()
        {
            this.frameGenerator?.Dispose();
        }

        private void CreateMediaObjects(GraphicsCaptureItem item)
        {
            if (item != null)
            {
                int width = item.Size.Width;
                int height = item.Size.Height;
                var videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.Bgra8, (uint)width, (uint)height);
                this.videoDescriptor = new VideoStreamDescriptor(videoProperties);
            }

            var audioProperties = AudioEncodingProperties.CreatePcm((uint)this.waveIn.WaveFormat.SampleRate, (uint)this.waveIn.WaveFormat.Channels, (uint)this.waveIn.WaveFormat.BitsPerSample);
            this.audioDescriptor = new AudioStreamDescriptor(audioProperties);

            // Create our MediaStreamSource
            this.mediaStreamSource = this.videoDescriptor != null ? new MediaStreamSource(this.videoDescriptor, this.audioDescriptor) : new MediaStreamSource(this.audioDescriptor);
            this.mediaStreamSource.BufferTime = TimeSpan.FromSeconds(0);
            this.mediaStreamSource.Starting += this.OnMediaStreamSourceStarting;
            this.mediaStreamSource.SampleRequested += this.OnMediaStreamSourceSampleRequested;

            // Create our transcoder
            this.transcoder = new MediaTranscoder();
            this.transcoder.HardwareAccelerationEnabled = true;
        }

        private void OnMediaStreamSourceSampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
        {
            if (this.isRecording && !this.closed)
            {
                try
                {
                    if (args.Request.StreamDescriptor is VideoStreamDescriptor)
                    {
                        using var frame = this.frameGenerator.WaitForNewFrame();
                        if (frame == null)
                        {
                            args.Request.Sample = null;
                            this.DisposeInternal();
                            return;
                        }

                        var sample = MediaStreamSample.CreateFromDirect3D11Surface(frame.Surface, frame.SystemRelativeTime);
                        args.Request.Sample = sample;
                        frame.Dispose();
                    }
                    else
                    {
                        var bytes = this.audioProvider.BufferedBytes;
                        var audioFrameSize = this.audioProvider.BufferedDuration;
                        var audioData = ArrayPool<byte>.Shared.Rent(bytes);
                        var audioSize = this.audioProvider.Read(audioData, 0, bytes);
                        args.Request.Sample = MediaStreamSample.CreateFromBuffer(audioData.AsBuffer(0, audioSize), audioTime);
                        this.audioTime += audioFrameSize;
                        ArrayPool<byte>.Shared.Return(audioData);

                    }
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e);
                    args.Request.Sample = null;
                    this.DisposeInternal();
                }
            }
            else
            {
                args.Request.Sample = null;
                this.DisposeInternal();
            }
        }

        private void OnMediaStreamSourceStarting(MediaStreamSource sender, MediaStreamSourceStartingEventArgs args)
        {
            if (this.frameGenerator != null)
            {
                using var frame = this.frameGenerator.WaitForNewFrame();
                this.encodingTime = Stopwatch.StartNew();
                this.audioTime = frame.SystemRelativeTime;
            }
            else
            {
                this.encodingTime = Stopwatch.StartNew();
                this.audioTime = TimeSpan.Zero;
            }

            args.Request.SetActualStartPosition(this.audioTime);
        }

        private void OnWaveInDataAvailable(object sender, WaveInEventArgs e)
        {
            this.audioProvider.AddSamples(e.Buffer, 0, e.BytesRecorded);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/PlayerViewModel.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using Speaker.Recorder.Services;
using Speaker.Recorder.ViewModels.Base;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;

namespace Speaker.Recorder.ViewModels
{
    public class PlayerViewModel : BaseViewModel, IDisposable
    {
        private readonly INavigationService navigationService;
        private ObservableCollection<Session> recordedSessions;
        private Session recordedSessionSelected;
        private readonly SessionsService sessionsService;
        private readonly ILogger<PlayerViewModel> logger;
        private Session temporalSessionForStop;

        private bool isPlaying;
        public bool IsPlaying
        {
            get => isPlaying;
            set => SetProperty(ref isPlaying, value);
        }
        
        private bool stopUploadDialogIsShowing;
        public bool StopUploadDialogIsShowing
        {
            get => stopUploadDialogIsShowing;
            set => SetProperty(ref stopUploadDialogIsShowing, value);
        }

        public ObservableCollection<Session> RecordedSessions
        {
            get => recordedSessions;
            set
            {
                SetProperty(ref recordedSessions, value);
            }
        }

        public Session RecordedSessionSelected { get => recordedSessionSelected; set => SetProperty(ref recordedSessionSelected, value); }

        public ICommand GoBackCommand { get; }

        public ICommand CancelStopUploadDialogCommand { get; }

        public ICommand AcceptStopUploadDialogCommand { get; }

        public RelayCommand<Session> OpenFolderCommand { get; }

        public RelayCommand<Session> PauseUploadCommand { get; }

        public RelayCommand<Session> PlayUploadCommand { get; }

        public RelayCommand<Session> StopUploadCommand { get; }

        public RelayCommand<Session> UploadCommand { get; }

        public PlayerViewModel(INavigationService navigationService, SessionsService sessionsService, ILogger<PlayerViewModel> logger)
        {
            this.navigationService = navigationService;
            this.sessionsService = sessionsService;
            this.logger = logger;
            this.GoBackCommand = new RelayCommand(this.GoBack);
            this.AcceptStopUploadDialogCommand = new RelayCommand(this.AcceptStopUploadDialog);
            this.CancelStopUploadDialogCommand = new RelayCommand(this.CancelStopUploadDialog);
            this.PlayUploadCommand = new RelayCommand<Session>(this.PlayUpload);
            this.PauseUploadCommand = new RelayCommand<Session>(this.PauseUpload);
            this.StopUploadCommand = new RelayCommand<Session>(this.StopUpload);
            this.UploadCommand = new RelayCommand<Session>(this.Upload);
            this.OpenFolderCommand = new RelayCommand<Session>(this.OpenFolder);

            this.Initialize();
        }

        private void CancelStopUploadDialog()
        {
            this.StopUploadDialogIsShowing = false;
        }

        private async void AcceptStopUploadDialog()
        {
            this.StopUploadDialogIsShowing = false;
            await temporalSessionForStop.StopUploadAsync();
        }

        private void OpenFolder(Session session)
        {
            this.logger.LogInformation($"Opening folder : {session.LocalRecordingFolderPath}");
            Process.Start(new ProcessStartInfo(session.LocalRecordingFolderPath) { UseShellExecute = true });
        }

        protected virtual void Initialize()
        {
            this.RecordedSessions = new ObservableCollection<Session>(sessionsService.GetSessions().OrderByDescending(x => x.Created));
            if (this.RecordedSessions.Count != 0)
            {
                this.RecordedSessionSelected = this.RecordedSessions[0];
            }
        }

        private async void PauseUpload(Session session)
        {
            await session.PauseUploadAsync().ConfigureAwait(false);
        }

        private async void PlayUpload(Session session)
        {
            await session.UploadAsync().ConfigureAwait(false);
        }

        private async void Upload(Session session)
        {
            await session.UploadAsync().ConfigureAwait(false);
        }

        private void StopUpload(Session session)
        {
            this.StopUploadDialogIsShowing = true;
            this.temporalSessionForStop = session;
        }

        private void GoBack() => this.navigationService.Back();

        public void Dispose()
        {
            foreach (var item in this.RecordedSessions)
            {
                item.Dispose();
            }
        }
    }
}

<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Designer/DesignRecordViewModel.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;

namespace Speaker.Recorder.ViewModels
{
    public class DesignRecordViewModel : RecordViewModel
    {
        private static IConfiguration configuration;

        static DesignRecordViewModel()
        {
            configuration = new ConfigurationBuilder()
                .Build();
        }

        public DesignRecordViewModel() : base(null, null, null, null, null, configuration, null)
        {
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Models/RecordingSession.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using Speaker.Recorder.Services;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Models
{
    public class RecordingSession
    {
        private readonly KinectRecorderService[] kinectRecorders;
        private readonly PowerPointRecorderService powerPointRecorder;
        private readonly string powerPointPresentationPath;
        private readonly CancellationTokenSource cancellationTokenSource;
        private readonly ILogger<RecordingSession> logger;
        private readonly string sessionBasePath;

        public Task[] RecordingTasks { get; private set; }

        public DateTime Started { get; private set; }

        public TimeSpan Duration { get; private set; }

        public RecordingSession(KinectRecorderService[] kinectRecorders,
            PowerPointRecorderService powerPointRecorder,
            string powerPointPresentationPath,
            string sessionBasePath,
            CancellationTokenSource cancellationTokenSource,
            ILogger<RecordingSession> logger)
        {
            this.kinectRecorders = kinectRecorders;
            this.powerPointRecorder = powerPointRecorder;
            this.powerPointPresentationPath = powerPointPresentationPath;
            this.cancellationTokenSource = cancellationTokenSource;
            this.logger = logger;
            this.sessionBasePath = sessionBasePath;
        }

        public void Start()
        {
            logger.LogInformation($"Starting a recording ");
            var dstFile = string.Empty;
            if (this.powerPointPresentationPath != null)
            {
                // Copy slides to session folder. Rename file without changing extension
                try
                {
                    var slidesExtension = Path.GetExtension(this.powerPointPresentationPath);
                    dstFile = Path.Combine(this.sessionBasePath, $"{LocalPathsHelper.PowerPointSlidesFileNameWithoutExtension}{slidesExtension}");
                    File.Copy(this.powerPointPresentationPath, dstFile);
                }
                catch (Exception e)
                {
                    logger.LogError(e, $"Exception copying PPT slides {powerPointPresentationPath} to {dstFile}");
                    File.AppendAllText(Path.Combine(this.sessionBasePath, "log.txt"), $"[{DateTime.Now}] Exception copying PPT slides to session\n{e}\n");
                }
            }

            var _ = this.cancellationTokenSource.Token.Register(this.InternalStopRecorders);
            this.RecordingTasks = this.kinectRecorders.Select(x => x.Start()).Concat(new[] { this.powerPointRecorder?.Start() }).Where(x => x != null).ToArray();
        }

        private void InternalStopRecorders()
        {
            foreach (var item in this.kinectRecorders)
            {
                try
                {
                    item.Stop();
                }
                catch (Exception e)
                {
                    logger.LogError(e, $"Exception trying to stop a {nameof(KinectRecorderService)} {item?.ProviderKey}");
                    File.AppendAllText(Path.Combine(this.sessionBasePath, "log.txt"), $"[{DateTime.Now}] Exception trying to stop\n{e}\n");
                }
            }

            try
            {
                this.powerPointRecorder.Stop();
            }
            catch (Exception e)
            {
                logger.LogError(e, $"Exception trying to stop a {nameof(PowerPointRecorderService)} {powerPointRecorder?.PptPath}");
                File.AppendAllText(Path.Combine(this.sessionBasePath, "log.txt"), $"[{DateTime.Now}] Exception trying to stop\n{e}\n");
            }
        }

        public void Stop()
        {
            logger.LogInformation("Stopping recording");
            this.cancellationTokenSource.Cancel();
        }
    }
}

<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Models/Session.cs<|end_filename|>
﻿using Microsoft.Azure.Storage.DataMovement;
using Microsoft.Extensions.Logging;
using Speaker.Recorder.ViewModels.Base;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.FileProperties;

namespace Speaker.Recorder.Services
{
    public class Session : NotifyPropertyObject, IDisposable
    {

        private readonly UploadManager uploadManager;
        private readonly LocalPathsHelper localPathsHelper;
        private readonly ILogger<Session> logger;
        private Stopwatch speedStopwatch;
        private double lastSpeedTransferedSize;
        private double transferedSize;
        private StorageFolder storageFolder;
        private bool isInitilized;
        private CancellationTokenSource uploadCancellation;

        public string Id { get; private set; }

        public string LocalRecordingFolderPath { get; private set; }

        public string[] KinectVideoPaths { get; private set; }

        public string PresentationVideoPath => localPathsHelper.GetPowerPointRecordingPath(Id);

        public string PresentationSlidesPath => localPathsHelper.GetPowerPointSlidesPath(Id);

        public long TotalBytes { get; private set; }

        public DateTime Created { get; private set; }

        private DateTime uploaded;

        public DateTime Uploaded
        {
            get => uploaded;
            private set => SetProperty(ref uploaded, value);
        }

        private TimeSpan duration;
        public TimeSpan Duration
        {
            get => duration;
            private set => SetProperty(ref duration, value);
        }

        private bool isBusy;

        public bool IsBusy
        {
            get => isBusy;
            private set => SetProperty(ref isBusy, value);
        }

        private bool isUploaded;
        public bool IsUploaded
        {
            get => isUploaded;
            private set
            {
                SetProperty(ref isUploaded, value);
            }
        }

        private bool isUploading;
        public bool IsUploading
        {
            get => isUploading;
            private set
            {
                SetProperty(ref isUploading, value);
            }
        }

        private bool isUploadPaused;
        public bool IsUploadPaused
        {
            get => isUploadPaused;
            private set
            {
                SetProperty(ref isUploadPaused, value);
            }
        }

        private bool canBeUploaded;
        public bool CanBeUploaded
        {
            get => canBeUploaded;
            private set
            {
                SetProperty(ref canBeUploaded, value);
            }
        }

        private bool isFailed;
        public bool IsFailed
        {
            get => isFailed;
            private set
            {
                SetProperty(ref isFailed, value);
            }
        }

        private double uploadPercentage;

        public double UploadPercentage
        {
            get => uploadPercentage;
            private set
            {
                SetProperty(ref uploadPercentage, value);
            }
        }
        private double uploadSpeed;

        public double UploadSpeedMbps
        {
            get => uploadSpeed;
            private set
            {
                SetProperty(ref uploadSpeed, value);
            }
        }

        public Session(UploadManager uploadManager, LocalPathsHelper localPathsHelper, ILogger<Session> logger)
        {
            this.uploadManager = uploadManager;
            this.localPathsHelper = localPathsHelper;
            this.logger = logger;
        }

        /// <summary>
        /// Initializes synchronously some basic information about the session
        /// Starts in a fire-and-forget fashion the loading of more data
        /// </summary>
        /// <param name="localRecordingFolderPath"></param>
        /// <returns></returns>
        public bool Initialize(string localRecordingFolderPath)
        {
            this.IsBusy = true;

            this.CanBeUploaded = this.uploadManager.IsUploadAvailable;
            this.LocalRecordingFolderPath = localRecordingFolderPath;
            this.Id = new DirectoryInfo(localRecordingFolderPath).Name;

            this.KinectVideoPaths = Directory.GetFiles(this.LocalRecordingFolderPath, LocalPathsHelper.KinectRecordFilePattern);

            if (this.KinectVideoPaths.Length == 0)
            {
                return false;
            }

            // Check if all files exists
            foreach (var kinectVideoPath in this.KinectVideoPaths)
            {
                if (!File.Exists(kinectVideoPath))
                {
                    IsBusy = false;
                    return false;
                }
            }

            // Load basic metadata
            this.Created = File.GetCreationTime(this.KinectVideoPaths.FirstOrDefault());

            // Get total bytes size     
            var dirListOptions = new EnumerationOptions()
            {
                RecurseSubdirectories = false,
            };
            var filesInDir = Directory.GetFiles(this.LocalRecordingFolderPath, UploadManager.FileUploadSearchPattern, dirListOptions);
            this.TotalBytes = filesInDir.Select(x => new FileInfo(x).Length).Sum();

            // Further initialization
            _ = this.InitializeMetadataAsync();

            return true;
        }

        public async Task InitializeMetadataAsync()
        {
            var uploadCheckTask = this.CheckUploadStatusAsync();

            try
            {
                this.storageFolder = await StorageFolder.GetFolderFromPathAsync(this.LocalRecordingFolderPath);
                var videoLengthsTasks = this.KinectVideoPaths.Concat(new[] { this.PresentationVideoPath })
                    .Where(x => x != null)
                    .Select(x => this.GetVideoDuration(x))
                    .ToArray();
                await Task.WhenAll(videoLengthsTasks);

                this.Duration = videoLengthsTasks.Select(x => x.Result).Where(x => x > TimeSpan.Zero).Min();

                // Await upload check task to catch exceptions.
                await uploadCheckTask;

                if (!this.IsUploaded && this.CanBeUploaded && uploadManager.ExistsUploadFile(this))
                {
                    this.IsUploading = true;
                    this.IsUploadPaused = true;
                }

                this.isInitilized = true;
            }
            catch (Exception e)
            {
                logger.LogWarning(e, $"Error initiating asynchronous Session {this.Id} in {nameof(InitializeMetadataAsync)}");
                this.IsFailed = true;
                File.AppendAllText(Path.Combine(this.LocalRecordingFolderPath, "log.txt"), $"[{DateTime.Now}] Can't load \n{e}\n");
            }
            finally
            {
                this.IsBusy = false;
            }
        }

        private async Task<TimeSpan> GetVideoDuration(string videoFilePath)
        {
            var videoItem = await this.storageFolder.TryGetItemAsync(Path.GetFileName(videoFilePath));
            if (videoItem != null && videoItem.IsOfType(StorageItemTypes.File) && videoItem is StorageFile videoFile)
            {
                VideoProperties videoProperties;
                int retries = 0;
                do
                {
                    videoProperties = await videoFile.Properties.GetVideoPropertiesAsync();
                    if (videoProperties.Duration > TimeSpan.Zero)
                    {
                        return TimeSpan.FromSeconds(Math.Floor(videoProperties.Duration.TotalSeconds));
                    }

                    await Task.Delay(TimeSpan.FromSeconds(0.3));
                    retries++;
                } while (retries < 10);
            }

            logger.LogWarning($"Can't get the duration of the video {videoFilePath} of Session {this.Id}");
            return TimeSpan.Zero;
        }

        public async Task CheckUploadStatusAsync()
        {
            var uploaded = await uploadManager.GetUploadedDateTime(this);
            this.IsUploaded = uploaded.HasValue;
            this.Uploaded = uploaded.GetValueOrDefault();
        }

        public async Task UploadAsync()
        {
            logger.LogInformation($"Start a session {this.Id} upload");

            try
            {
                if (!this.isInitilized)
                {
                    this.IsBusy = true;
                    this.IsFailed = false;
                    await this.CheckUploadStatusAsync();
                    this.IsBusy = false;
                    return;
                }

                if (this.IsFailed)
                {
                    this.IsBusy = true;
                    this.IsFailed = false;
                    await this.CheckUploadStatusAsync();
                    this.IsBusy = false;
                }

                if (this.CanUpload())
                {
                    this.speedStopwatch = new Stopwatch();
                    this.transferedSize = 0;
                    this.lastSpeedTransferedSize = 0;
                    this.UploadSpeedMbps = 0;
                    var uploadCancellation = new CancellationTokenSource();
                    this.uploadCancellation = uploadCancellation;
                    var progress = new ProgressRecorder(this.TotalBytes);
                    progress.UpdateUploadProgress += (e, progress) =>
                    {
                        this.transferedSize = progress;
                        var uploadProgress = progress / this.TotalBytes;
                        this.IsBusy = uploadProgress <= 0;
                        if (!this.speedStopwatch.IsRunning)
                        {
                            this.lastSpeedTransferedSize = this.transferedSize;
                            this.speedStopwatch.Restart();
                        }

                        this.UploadPercentage = uploadProgress;
                    };

                    this.IsBusy = true;
                    if (!this.isUploadPaused)
                    {
                        this.UploadPercentage = 0;
                    }

                    this.TransferSpeedLoop(uploadCancellation.Token);

                    this.IsUploadPaused = false;
                    this.IsUploading = true;
                    try
                    {
                        if (await uploadManager.UploadSessionAsync(this, progress, this.uploadCancellation.Token))
                        {
                            this.IsUploaded = true;
                            this.IsUploading = false;
                            this.Uploaded = DateTime.Now;
                        }
                    }
                    catch (Exception ex)
                    {
                        logger.LogWarning(ex, $"Error trying to upload a file of session {this.Id}");
                        this.IsFailed = true;
                    }
                    finally
                    {
                        uploadCancellation.Cancel();
                    }
                }
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, $"Error trying to upload a file in {nameof(UploadAsync)} of session {this.Id}");
                this.IsBusy = false;
                this.IsFailed = true;
            }
        }

        private async void TransferSpeedLoop(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                await Task.Delay(800);
                var elapsed = this.speedStopwatch.Elapsed.TotalSeconds;
                if (elapsed > 0)
                {
                    var size = this.transferedSize - this.lastSpeedTransferedSize;
                    var speed = ((size * 8) / (1024 * 1024)) / elapsed;
                    this.UploadSpeedMbps = speed;
                }
            }
        }

        public async Task PauseUploadAsync()
        {
            logger.LogTrace($"Trying to pause a session {this.Id}");
            if (this.CanPauseUpload())
            {
                logger.LogInformation($"Pausing a session {this.Id} upload");
                var cancellation = this.uploadCancellation;
                this.uploadCancellation = null;
                if (cancellation != null)
                {
                    this.IsBusy = true;
                    await Task.Run(() => { cancellation?.Cancel(); });
                    this.uploadCancellation = null;
                    this.IsUploadPaused = true;
                    this.IsBusy = false;
                }
            }
        }

        public async Task StopUploadAsync()
        {
            logger.LogTrace($"Trying to stop a session {this.Id}");
            if (this.CanStopUpload())
            {
                logger.LogInformation($"Stopping a session {this.Id} upload");
                var cancellation = this.uploadCancellation;
                this.uploadCancellation = null;
                if (cancellation != null)
                {
                    this.IsBusy = true;
                    await Task.Run(() => { cancellation?.Cancel(); });
                    this.uploadManager.RemoveUploadStatus(this);
                    this.IsUploaded = false;
                    this.IsUploading = false;
                    this.IsUploadPaused = false;
                    this.IsBusy = false;
                }
            }
        }

        public bool CanUpload()
        {
            return !this.IsBusy && !this.IsUploaded;
        }

        public bool CanPauseUpload()
        {
            return !this.IsBusy && this.IsUploading && !this.IsUploadPaused;
        }

        public bool CanStopUpload()
        {
            return !this.IsBusy && (this.IsUploading || this.IsUploadPaused);
        }

        public void Dispose()
        {
            _ = Task.Run(() => { this.uploadCancellation?.Cancel(); });
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Kinect/KinectCaptureProvider.cs<|end_filename|>
﻿using Microsoft.Azure.Kinect.Sensor;
using Microsoft.Azure.Kinect.Sensor.Record;
using Microsoft.Extensions.Configuration;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Kinect
{
    public class KinectCaptureProvider : IDisposable
    {
        public event EventHandler IsConnectedChanged;

        private ManualResetEventSlim captureEvent = new ManualResetEventSlim(false);
        private event EventHandler<Microsoft.Azure.Kinect.Sensor.Capture> captureArrived;

        public event EventHandler<Microsoft.Azure.Kinect.Sensor.Capture> CaptureArrived
        {
            add
            {
                this.captureArrived += value;
                if (this.captureArrived?.GetInvocationList().Length == 1)
                {
                    this.device?.StartCameras(this.GetDeviceConfiguration());
                    this.captureEvent.Set();
                }
            }
            remove
            {
                this.captureArrived -= value;
                if (!this.disposed && (this.captureArrived == null || this.captureArrived.GetInvocationList().Length == 0))
                {
                    this.captureEvent.Reset();
                    this.device?.StopCameras();
                }
            }
        }

        private readonly string playbackPath;
        private readonly Playback playback;
        private readonly TimeSpan fpsTime;
        private readonly int deviceIndex = -1;
        private Device device;
        private bool disposed;
        private int seekCount;
        private bool? isConnected;
        private readonly object deviceLock = new object();
        private IConfiguration configuration;
        private SemaphoreSlim deviceCreationEvent;

        public bool? IsConnected
        {
            get => this.isConnected;
            private set
            {
                if (this.isConnected != value)
                {
                    this.isConnected = value;
                    this.IsConnectedChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }

        public KinectCaptureProvider(int deviceIndex, IConfiguration configuration)
        {
            this.configuration = configuration;
            this.deviceCreationEvent = new SemaphoreSlim(0);
            this.deviceIndex = deviceIndex;
            Task.Run(this.OpenDevice);
        }

        private async void OpenDevice()
        {
            while (this.device == null && !this.disposed)
            {
                try
                {
                    lock (this.deviceLock)
                    {
                        if (!this.disposed)
                        {
                            var deviceCount = Device.GetInstalledCount();
                            if (deviceIndex < deviceCount)
                            {
                                this.device = Device.Open(deviceIndex);
                                if (this.captureArrived != null)
                                {
                                    this.device.StartCameras(this.GetDeviceConfiguration());
                                }

                                this.SetExposure(null);
                                this.IsConnected = true;
                                var renderThread = new Thread(this.RenderThread);
                                renderThread.Start();
                                this.deviceCreationEvent.Release();
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e);
                }

                if (this.device == null)
                {
                    this.IsConnected = false;
                    await Task.Delay(1500);
                }
            }
        }

        public KinectCaptureProvider(string playbackPath)
        {
            try
            {
                this.playbackPath = playbackPath;
                this.playback = Playback.Open(playbackPath);
                this.fpsTime = this.playback.RecordConfiguration.CameraFPS switch
                {
                    FPS.FPS5 => TimeSpan.FromMilliseconds(1000 / 5),
                    FPS.FPS15 => TimeSpan.FromMilliseconds(1000 / 15),
                    FPS.FPS30 => TimeSpan.FromMilliseconds(1000 / 30),
                    _ => TimeSpan.FromMilliseconds(1000),
                };
                this.IsConnected = true;
                var renderThread = new Thread(this.RenderThread);
                renderThread.Start();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
                this.IsConnected = false;
            }
        }

        internal void SetExposure(TimeSpan? exposureValue)
        {
            if (this.device != null && !this.disposed)
            {
                var exposureMicroSeconds = (exposureValue?.TotalMilliseconds ?? 0) * 1000;
                this.device.SetColorControl(ColorControlCommand.ExposureTimeAbsolute, exposureValue == null ? ColorControlMode.Auto : ColorControlMode.Manual, (int)exposureMicroSeconds);
            }
        }

        private DeviceConfiguration GetDeviceConfiguration()
        {
            if (this.deviceIndex >= 0)
            {
                var deviceConfiguration = new DeviceConfiguration
                {
                    CameraFPS = FPS.FPS30,
                    ColorFormat = ImageFormat.ColorMJPG,
                    ColorResolution = ColorResolution.R1080p,
                    DepthMode = DepthMode.NFOV_Unbinned,
                    SynchronizedImagesOnly = true,
                };
                configuration.GetSection("Kinect:Device").Bind(deviceConfiguration);
                return deviceConfiguration;
            }
            else
            {
                return new DeviceConfiguration
                {
                    CameraFPS = this.playback.RecordConfiguration.CameraFPS,
                    ColorFormat = this.playback.RecordConfiguration.ColorFormat,
                    ColorResolution = this.playback.RecordConfiguration.ColorResolution,
                    DepthMode = this.playback.RecordConfiguration.DepthMode,
                    DepthDelayOffColor = this.playback.RecordConfiguration.DepthDelayOffColor,
                    WiredSyncMode = this.playback.RecordConfiguration.WiredSyncMode,
                };
            }
        }

        private void RenderThread(object obj)
        {
            var processTime = new Stopwatch();
            while ((this.device != null || this.playback != null) && !this.disposed)
            {
                this.captureEvent.Wait();
                if (this.disposed)
                {
                    continue;
                }

                processTime.Restart();

                try
                {
                    using var capture = this.device != null ? this.device.GetCapture() : this.playback.GetNextCapture();
                    if (!this.disposed)
                    {
                        if (this.playback != null && capture == null)
                        {
                            this.playback.Seek(TimeSpan.Zero);
                            this.seekCount++;
                            continue;
                        }

                        if (this.playback != null)
                        {
                            if (capture.Color != null)
                            {
                                capture.Color.DeviceTimestamp += this.playback.RecordingLength * this.seekCount;
                            }

                            if (capture.Depth != null)
                            {
                                capture.Depth.DeviceTimestamp += this.playback.RecordingLength * this.seekCount;
                            }

                            if (capture.IR != null)
                            {
                                capture.IR.DeviceTimestamp += this.playback.RecordingLength * this.seekCount;
                            }
                        }

                        this.captureArrived?.Invoke(this, capture);
                        processTime.Stop();
                        if (!this.disposed)
                        {
                            if (this.playback != null && processTime.Elapsed < this.fpsTime)
                            {
                                Thread.Sleep(this.fpsTime - processTime.Elapsed);
                            }
                        }
                    }
                }
                catch (AzureKinectException)
                {
                    if (this.device != null && !this.disposed)
                    {
                        try
                        {
                            this.device?.Dispose();
                        }
                        catch (Exception e)
                        {
                            Debug.WriteLine(e);
                        }
                        this.device = null;
                        this.OpenDevice();
                    }
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e);
                }
            }

            try
            {
                this.device?.Dispose();
                this.playback?.Dispose();
            }
            catch (Exception e)
            {
                Debug.WriteLine(e);
            }
        }

        public void Dispose()
        {
            this.disposed = true;
            this.captureEvent.Set();
        }

        public KinectImageProvider For(KinectCaptureImage image)
        {
            return new KinectImageProvider(this, image);
        }

        public async Task<Microsoft.Azure.Kinect.Sensor.Record.Recorder> GetRecorder(string filePath)
        {
            if (this.deviceIndex >= 0)
            {
                await this.deviceCreationEvent.WaitAsync().ConfigureAwait(false);
                this.deviceCreationEvent.Release();
                return Microsoft.Azure.Kinect.Sensor.Record.Recorder.Create(filePath, this.device, this.GetDeviceConfiguration());
            }
            else
            {
                return Microsoft.Azure.Kinect.Sensor.Record.Recorder.Create(filePath, null, this.GetDeviceConfiguration());
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Converters/NullToVisibilityConverter.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Data;

namespace Speaker.Recorder.Converters
{
    public class NullToVisibilityConverter : IValueConverter
    {
        public bool Invert { get; set; }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!this.Invert)
            {
                return value is null ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return value is null ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Base/BaseViewModel.cs<|end_filename|>
﻿namespace Speaker.Recorder.ViewModels.Base
{
    public class BaseViewModel : NotifyPropertyObject
    {
        
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/KinectRecorderService.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using Speaker.Recorder.Kinect;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Services
{
    public class KinectRecorderService : IDisposable
    {
        private TaskCompletionSource<bool> recordTcs;
        private readonly KinectCaptureProvidersFactory kinectCaptureProvidersFactory;
        private readonly ILogger<KinectRecorderService> logger;
        private KinectCaptureProvider provider;
        private Microsoft.Azure.Kinect.Sensor.Record.Recorder recording;
        private TimeSpan? firstFrameTime;
        private bool exposureFixed;
        private readonly object recordingLock = new object();

        public object ProviderKey { get; private set; }

        public TimeSpan FixExposure { get; set; } = TimeSpan.FromSeconds(2);

        public KinectRecorderService(KinectCaptureProvidersFactory kinectCaptureProvidersFactory, ILogger<KinectRecorderService> logger)
        {
            this.kinectCaptureProvidersFactory = kinectCaptureProvidersFactory;
            this.logger = logger;
        }

        public async Task<KinectRecorderService> WithAsync(object key, string filePath)
        {
            if (this.ProviderKey != null)
            {
                throw new InvalidOperationException("This instance has been initialized already.");
            }

            this.ProviderKey = key;
            await this.CreateRecording(filePath).ConfigureAwait(false);
            return this;
        }

        private async Task CreateRecording(string filePath)
        {
            this.logger.LogInformation($"Creating recorder for {this.ProviderKey} in path {filePath}");
            this.provider = this.kinectCaptureProvidersFactory.GetOrCreateProvider(this.ProviderKey);
            this.recording = await this.provider.GetRecorder(filePath).ConfigureAwait(false);
            this.provider.CaptureArrived += this.ProviderHoldCameraStartedHandler;
            this.recording.WriteHeader();
        }

        private void ProviderHoldCameraStartedHandler(object sender, Microsoft.Azure.Kinect.Sensor.Capture e)
        {
            // This handler holds the cameras started
        }

        public Task Start()
        {
            if (this.recordTcs != null)
            {
                throw new Exception("The recording has been started yet.");
            }

            this.logger.LogInformation($"Starting recorder for {this.ProviderKey}");
            this.recordTcs = new TaskCompletionSource<bool>();
            this.provider.CaptureArrived += Provider_CaptureArrived;
            this.provider.IsConnectedChanged += Provider_IsConnectedChanged;
            return recordTcs.Task;
        }

        private void Provider_IsConnectedChanged(object sender, EventArgs e)
        {
            this.logger.LogInformation($"Provider for {this.ProviderKey} IsConnected changed to {this.provider.IsConnected}");
            if (this.provider.IsConnected != true)
            {
                this.recordTcs.TrySetException(new Exception("The Azure Kinect device has been disconnected."));
                this.Stop();
            }
        }

        private void Provider_CaptureArrived(object sender, Microsoft.Azure.Kinect.Sensor.Capture e)
        {
            try
            {
                if (e.Color != null)
                {
                    if (this.firstFrameTime == null)
                    {
                        this.firstFrameTime = e.Color.DeviceTimestamp;
                    }

                    if (this.FixExposure > TimeSpan.Zero && !this.exposureFixed && (e.Color.DeviceTimestamp - this.firstFrameTime.Value) > this.FixExposure)
                    {
                        var exposureValue = e.Color.Exposure;
                        this.provider.SetExposure(exposureValue);
                        this.exposureFixed = true;
                    }
                }

                lock (this.recordingLock)
                {
                    this.recording?.WriteCapture(e);
                }
            }
            catch (Exception ex)
            {
                this.logger.LogWarning(ex, $"Error handling capture arrived in recording for {this.ProviderKey}");
            }
        }

        public void Stop()
        {
            this.logger.LogInformation($"Stopping record for {this.ProviderKey}");
            this.provider.CaptureArrived -= Provider_CaptureArrived;
            this.provider.CaptureArrived -= ProviderHoldCameraStartedHandler;
            this.provider.SetExposure(null);
            this.kinectCaptureProvidersFactory.Remove(ProviderKey);

            lock (this.recordingLock)
            {
                var recording = Interlocked.Exchange(ref this.recording, null);
                recording?.Flush();
                recording?.Dispose();
            }

            this.recordTcs?.TrySetResult(true);
        }

        public void Dispose()
        {
            this.Stop();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/LocalPathsHelper.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Speaker.Recorder.Services
{
    public class LocalPathsHelper
    {
        public const string PowerPointSlidesFileNameWithoutExtension = "slides";

        public static string KinectRecordFilePattern => "kinect*.mkv";

        private string KinectRecordFileName => "kinect{0}.mkv";

        private string PowerPointRecordFileName => "presentation.mp4";

        private string RootFolder { get; }

        public LocalPathsHelper(IConfiguration configuration)
        {
            this.RootFolder = configuration.GetValue("Data:RecordingFolder", Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Recorded Sessions"));
            Directory.CreateDirectory(RootFolder);
        }

        public string GetBasePath()
        {
            return this.RootFolder;
        }

        public string GetSessionPath(string sessionIdentifier)
        {
            var sessionFolder = Path.Combine(this.RootFolder, sessionIdentifier);
            Directory.CreateDirectory(sessionFolder);

            return sessionFolder;
        }

        public string GetKinectRecordingPath(string sessionIdentifier, int deviceIndex)
        {
            return Path.Combine(GetSessionPath(sessionIdentifier), string.Format(KinectRecordFileName, deviceIndex));
        }

        public string GetPowerPointRecordingPath(string sessionIdentifier)
        {
            return Path.Combine(GetSessionPath(sessionIdentifier), PowerPointRecordFileName);
        }

        public string GetPowerPointSlidesPath(string sessionIdentifier)
        {
            var sessionPath = GetSessionPath(sessionIdentifier);
            return Directory.GetFiles(sessionPath, "*.ppt*").FirstOrDefault();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Views/PlayerView.xaml.cs<|end_filename|>
﻿using Microsoft.Toolkit.Wpf.UI.XamlHost;
using Speaker.Recorder.Services;
using Speaker.Recorder.UWP;
using Speaker.Recorder.ViewModels;
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Speaker.Recorder.Views
{
    public partial class PlayerView : UserControl
    {
        private bool isDragging;
        private VideosPlayer player;

        public PlayerView()
        {
            InitializeComponent();

            this.Unloaded += PlayerView_Unloaded;
            this.DataContextChanged += PlayerView_DataContextChanged;
        }

        private void PlayerView_Unloaded(object sender, RoutedEventArgs e)
        {
            this.player?.SetVideos(null);
            this.PlayerViewerHost.Child = null;
        }

        private void PlayerView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null && e.OldValue is PlayerViewModel player)
            {
                player.PropertyChanged -= PlayerView_PropertyChanged;
            }

            if (e.NewValue != null && e.NewValue is PlayerViewModel playerNew)
            {
                playerNew.PropertyChanged += PlayerView_PropertyChanged;
                ResetMediaElements(((PlayerViewModel)this.DataContext).RecordedSessionSelected);
            }
        }

        private void PlayerView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(PlayerViewModel.RecordedSessionSelected))
            {
                ResetMediaElements(((PlayerViewModel)this.DataContext).RecordedSessionSelected);
            }
        }

        private void ResetMediaElements(Session session)
        {
            if(this.player == null)
            {
                return;
            }

            ((PlayerViewModel)this.DataContext).IsPlaying = false;
            this.player.SetVideos(session?.KinectVideoPaths?.Concat(new[] { session?.PresentationVideoPath }).Where(x => x != null).ToArray());
            var time = TimeSpan.Zero;
            TimelineSlider.Value = time.TotalSeconds;
            TextTimeLine.Text = time.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
        }

        private void TimelineSlider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
        {
            isDragging = true;
        }

        private void TimelineSlider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            if (this.player != null)
            {
                this.player.Seek(TimeSpan.FromSeconds(TimelineSlider.Value));
                TextTimeLine.Text = TimeSpan.FromSeconds(TimelineSlider.Value).ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
            }

            isDragging = false;
        }

        private void PlayButton_Click(object sender, RoutedEventArgs e)
        {
            var session = ((PlayerViewModel)this.DataContext).RecordedSessionSelected;
            if (session?.Duration > TimeSpan.Zero)
            {
                this.player.Play(session.Duration);
            }
            else
            {
                ((PlayerViewModel)this.DataContext).IsPlaying = false;
            }
        }

        private void PauseButton_Click(object sender, RoutedEventArgs e)
        {
            this.player.Pause();
        }

        private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
        {
            if(this.player != null)
            {
                this.player.PositionChanged -= Player_PositionChanged;
                this.player.MediaEnded -= Player_MediaEnded;
            }

            this.player = ((WindowsXamlHost)sender).Child as VideosPlayer;
            if (this.player != null)
            {
                this.player.PositionChanged += Player_PositionChanged;
                this.player.MediaEnded += Player_MediaEnded;
                ResetMediaElements(((PlayerViewModel)this.DataContext).RecordedSessionSelected);
            }
        }

        private void Player_MediaEnded(object sender, EventArgs e)
        {
            this.Dispatcher.Invoke(() =>
            {
                ((PlayerViewModel)this.DataContext).IsPlaying = false;
            });
        }

        private void Player_PositionChanged(object sender, TimeSpan e)
        {
            if (!isDragging)
            {
                this.Dispatcher.Invoke(() =>
                {
                    TimelineSlider.Value = e.TotalSeconds;
                    TextTimeLine.Text = e.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture);
                });
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/MainWindow.xaml.cs<|end_filename|>
﻿using Microsoft.Extensions.DependencyInjection;
using Speaker.Recorder.PowerPoint;
using Speaker.Recorder.Services;
using Speaker.Recorder.ViewModels;
using Speaker.Recorder.ViewModels.Base;
using System;
using System.ComponentModel;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Speaker.Recorder
{
    public partial class MainWindow : Window, INavigationService
    {
        private readonly Application application;
        private readonly PowerPointGraphicsCaptureItemsFactory pointGraphicsCaptureItemsFactory;
        private readonly IServiceProvider serviceProvider;

        public MainWindow(Application application, PowerPointGraphicsCaptureItemsFactory pointGraphicsCaptureItemsFactory, IServiceProvider serviceProvider)
        {
            this.application = application;
            this.pointGraphicsCaptureItemsFactory = pointGraphicsCaptureItemsFactory;
            this.serviceProvider = serviceProvider;
            this.InitializeComponent();
            this.Title += $" {Assembly.GetExecutingAssembly().GetName().Version}";
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
            this.pointGraphicsCaptureItemsFactory.SendKey(e.Key);
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);
            foreach (var item in this.MainContent.Children)
            {
                if (item is ContentControl element && element.Content is IDisposable disposable)
                {
                    disposable.Dispose();
                }
            }
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            if (!cancellationToken.IsCancellationRequested)
            {
                return this.Dispatcher.InvokeAsync(() =>
                {
                    this.application.MainWindow = this;
                    this.To<RecordViewModel>();
                    this.Show();
                    this.Activate();
                }).Task;
            }

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            this.pointGraphicsCaptureItemsFactory.CloseAll();
            return Task.CompletedTask;
        }

        public void To<TViewModel>()
        {
            var viewModel = serviceProvider.GetService<TViewModel>();
            if (this.MainContent.Children.Count > 0)
            {
                var lastView = this.MainContent.Children[^1];
                lastView.Visibility = Visibility.Collapsed;
                if (lastView is ContentControl element && element.Content is IPausableViewModel pausable)
                {
                    pausable.Pause();
                }
            }

            this.MainContent.Children.Add(new ContentControl { Content = viewModel });
        }

        public void Back()
        {
            var currentView = this.MainContent.Children[^1];
            if (currentView is ContentControl element && element.Content is IDisposable disposable)
            {
                disposable.Dispose();
            }

            this.MainContent.Children.Remove(currentView);
            if (this.MainContent.Children.Count > 0)
            {
                var lastView = this.MainContent.Children[^1];
                lastView.Visibility = Visibility.Visible;
                if (lastView is ContentControl lastElement && lastElement.Content is IPausableViewModel pausable)
                {
                    pausable.Resume();
                }
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/WaveFormatHelper.cs<|end_filename|>
﻿using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Speaker.Recorder.Helpers
{
    public static class WaveFormatHelper
    {
        public static WaveFormat SelectBestWaveInFormat(this WaveInEvent waveIn)
        {
            var deviceCapabilities = WaveIn.GetCapabilities(waveIn.DeviceNumber);
            var formats = Enum.GetValues(typeof(SupportedWaveFormat)).Cast<SupportedWaveFormat>().OrderByDescending(x => x);
            foreach (var item in formats)
            {
                if (deviceCapabilities.SupportsWaveFormat(item))
                {
                    return item.ToWaveFormat();
                }
            }

            throw new NotSupportedException();
        }

        public static WaveFormat ToWaveFormat(this SupportedWaveFormat item)
        {
            var name = item.ToString();
            var match = Regex.Match(name, @"WAVE_FORMAT_(\d+)([MS])(\d+)");
            var rate = match.Groups[1].Value switch
            {
                "1" => 11025,
                "2" => 22050,
                "4" => 44100,
                "44" => 44100,
                "48" => 48000,
                "96" => 96000,
                _ => throw new NotImplementedException(),
            };
            var bits = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);
            var channels = match.Groups[2].Value == "M" ? 1 : 2;
            return new WaveFormat(rate, bits, channels);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Capture/CaptureFrameWait.cs<|end_filename|>
﻿using System;
using System.Threading;
using Windows.Graphics;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Graphics.DirectX.Direct3D11;

namespace Speaker.Recorder.Capture
{
    public sealed class CaptureFrameWait : IDisposable
    {
        private readonly ManualResetEvent[] events;
        private readonly ManualResetEvent frameEvent;
        private readonly ManualResetEvent closedEvent;
        private readonly Func<GraphicsCaptureItem> itemFactory;
        private readonly SharpDX.Direct3D11.Multithread multithread;
        private IDirect3DDevice device;
        private SharpDX.Direct3D11.Device d3dDevice;
        private SharpDX.Direct3D11.Texture2D blankTexture;
        private Direct3D11CaptureFrame currentFrame;
        private GraphicsCaptureSession session;
        private Direct3D11CaptureFramePool framePool;
        private int initializationThread;

        public GraphicsCaptureItem CurrentItem { get; private set; }

        public CaptureFrameWait(IDirect3DDevice device, Func<GraphicsCaptureItem> itemFactory)
        {
            this.device = device;
            this.d3dDevice = Direct3D11Helper.CreateSharpDXDevice(device);
            this.multithread = d3dDevice.QueryInterface<SharpDX.Direct3D11.Multithread>();
            this.multithread.SetMultithreadProtected(true);
            this.frameEvent = new ManualResetEvent(false);
            this.closedEvent = new ManualResetEvent(false);
            this.events = new[] { closedEvent, frameEvent };
            this.itemFactory = itemFactory;
            this.CurrentItem = this.itemFactory();

            InitializeBlankTexture(this.CurrentItem.Size);
            InitializeCapture();
        }

        private void InitializeCapture()
        {
            this.initializationThread = Thread.CurrentThread.ManagedThreadId;
            this.CurrentItem.Closed += this.OnClosed;
            this.framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(
                this.device,
                DirectXPixelFormat.B8G8R8A8UIntNormalized,
                1,
                this.CurrentItem.Size);
            this.framePool.FrameArrived += this.OnFrameArrived;
            this.session = this.framePool.CreateCaptureSession(this.CurrentItem);
            this.session.StartCapture();
        }

        private void InitializeBlankTexture(SizeInt32 size)
        {
            var description = new SharpDX.Direct3D11.Texture2DDescription
            {
                Width = size.Width,
                Height = size.Height,
                MipLevels = 1,
                ArraySize = 1,
                Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
                SampleDescription = new SharpDX.DXGI.SampleDescription()
                {
                    Count = 1,
                    Quality = 0
                },
                Usage = SharpDX.Direct3D11.ResourceUsage.Default,
                BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget,
                CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
                OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None
            };
            this.blankTexture = new SharpDX.Direct3D11.Texture2D(this.d3dDevice, description);

            using var renderTargetView = new SharpDX.Direct3D11.RenderTargetView(this.d3dDevice, this.blankTexture);
            this.d3dDevice.ImmediateContext.ClearRenderTargetView(renderTargetView, new SharpDX.Mathematics.Interop.RawColor4(0, 0, 0, 1));
        }

        private void SetResult(Direct3D11CaptureFrame frame)
        {
            this.currentFrame = frame;
            this.frameEvent.Set();
        }

        private void Stop()
        {
            this.closedEvent.Set();
        }

        private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
        {
            this.SetResult(sender.TryGetNextFrame());
        }

        private void OnClosed(GraphicsCaptureItem sender, object args)
        {
            var newItem = this.itemFactory();
            if (newItem == null)
            {
                this.Stop();
            }
            else
            {
                this.CleanupItem();
                this.CurrentItem = this.itemFactory();
                this.InitializeCapture();
            }
        }

        private void CleanupItem()
        {
            this.framePool?.Dispose();
            if (this.initializationThread == Thread.CurrentThread.ManagedThreadId)
            {
                this.session?.Dispose();
            }
            if (this.CurrentItem != null)
            {
                this.CurrentItem.Closed -= OnClosed;
            }
            this.CurrentItem = null;
            this.currentFrame?.Dispose();
        }

        public SurfaceWithInfo WaitForNewFrame()
        {
            // Let's get a fresh one.
            this.currentFrame?.Dispose();
            this.frameEvent.Reset();

            var signaledEvent = this.events[WaitHandle.WaitAny(this.events)];
            if (signaledEvent == this.closedEvent)
            {
                this.CleanupItem();
                return null;
            }

            using var multithreadLock = new MultithreadLock(this.multithread);
            using var sourceTexture = Direct3D11Helper.CreateSharpDXTexture2D(this.currentFrame.Surface);
            var description = sourceTexture.Description;
            description.Usage = SharpDX.Direct3D11.ResourceUsage.Default;
            description.BindFlags = SharpDX.Direct3D11.BindFlags.ShaderResource | SharpDX.Direct3D11.BindFlags.RenderTarget;
            description.CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None;
            description.OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.None;

            using var copyTexture = new SharpDX.Direct3D11.Texture2D(this.d3dDevice, description);
            var width = Math.Clamp(this.currentFrame.ContentSize.Width, 0, this.currentFrame.Surface.Description.Width);
            var height = Math.Clamp(this.currentFrame.ContentSize.Height, 0, this.currentFrame.Surface.Description.Height);

            var region = new SharpDX.Direct3D11.ResourceRegion(0, 0, 0, width, height, 1);

            this.d3dDevice.ImmediateContext.CopyResource(blankTexture, copyTexture);
            this.d3dDevice.ImmediateContext.CopySubresourceRegion(sourceTexture, 0, region, copyTexture, 0);
            var result = new SurfaceWithInfo
            {
                SystemRelativeTime = this.currentFrame.SystemRelativeTime,
                Surface = Direct3D11Helper.CreateDirect3DSurfaceFromSharpDXTexture(copyTexture)
            };
            return result;
        }

        public void Dispose()
        {
            this.Stop();
            this.CleanupItem();
            this.device = null;
            this.d3dDevice = null;
            this.blankTexture?.Dispose();
            this.blankTexture = null;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/PowerPointRecorderService.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using NAudio.Wave;
using Speaker.Recorder.Capture;
using Speaker.Recorder.Helpers;
using Speaker.Recorder.PowerPoint;
using System;
using System.IO;
using System.Threading.Tasks;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;

namespace Speaker.Recorder.Services
{
    public class PowerPointRecorderService : IDisposable
    {
        private Encoder encoder;
        private readonly PowerPointGraphicsCaptureItemsFactory graphicsCaptureItemsFactory;
        private readonly ILogger<PowerPointRecorderService> logger;
        private IRandomAccessStream randomStream;
        private IDirect3DDevice device;
        private WaveInEvent waveIn;
        private MediaEncodingProfile encodingProfile;
        private FileStream stream;

        public string PptPath { get; private set; }

        public TimeSpan Time { get => this.encoder.Time; }

        public PowerPointRecorderService(PowerPointGraphicsCaptureItemsFactory graphicsCaptureItemsFactory, ILogger<PowerPointRecorderService> logger)
        {
            this.graphicsCaptureItemsFactory = graphicsCaptureItemsFactory;
            this.logger = logger;
        }

        public async Task<PowerPointRecorderService> WithAsync(string pptPath, string recordPath)
        {
            if (this.PptPath != null)
            {
                throw new InvalidOperationException("This instance has been initialized already.");
            }

            this.PptPath = pptPath;
            await this.CreateRecord(recordPath).ConfigureAwait(false);
            return this;
        }

        private Task CreateRecord(string recordPath)
        {
            this.logger.LogInformation($"Creating recorder for {this.PptPath} in path {recordPath}");
            this.stream = File.OpenWrite(recordPath);
            this.randomStream = stream.AsRandomAccessStream();
            this.device = Direct3D11Helper.CreateDevice();

            this.waveIn = new WaveInEvent();
            this.waveIn.WaveFormat = this.waveIn.SelectBestWaveInFormat();

            this.encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);

            this.encoder = new Encoder(device, this.PptPath != null ? () => this.graphicsCaptureItemsFactory.GetOrCreateGraphicsCaptureItem(this.PptPath) : (Func<GraphicsCaptureItem>)null, waveIn);
            return this.encoder.InitializeAsync();
        }

        public async Task Start()
        {
            this.logger.LogInformation($"Starting recorder for {this.PptPath}");
            try
            {
                await this.encoder.EncodeAsync(randomStream, encodingProfile);
                this.graphicsCaptureItemsFactory.Remove(this.PptPath);
            }
            finally
            {
                this.Stop();
                this.device?.Dispose();
                this.waveIn?.Dispose();
                this.randomStream?.Dispose();
                this.stream?.Dispose();
            }
        }

        public void Stop()
        {
            this.logger.LogInformation($"Stopping record for {this.PptPath}");
            this.encoder?.Dispose();
        }

        public void Dispose()
        {
            this.Stop();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Models/ProgressRecorder.cs<|end_filename|>
﻿using Microsoft.Azure.Storage.DataMovement;
using System;

namespace Speaker.Recorder.Services
{
    /// <summary>
    /// A helper class to record progress reported by data movement library.
    /// </summary>
    public class ProgressRecorder : IProgress<TransferStatus>
    {
        private long latestBytesTransferred;
        private long latestNumberOfFilesTransferred;
        private long latestNumberOfFilesSkipped;
        private long latestNumberOfFilesFailed;
        private long bytesTotal;

        public event EventHandler<double> UpdateUploadProgress;

        public ProgressRecorder(long bytesTotal)
        {
            this.bytesTotal = bytesTotal;
        }

        public void Report(TransferStatus progress)
        {
            var transferSpeedBytes = progress.BytesTransferred - this.latestBytesTransferred;
            this.latestBytesTransferred = progress.BytesTransferred;
            this.latestNumberOfFilesTransferred = progress.NumberOfFilesTransferred;
            this.latestNumberOfFilesSkipped = progress.NumberOfFilesSkipped;
            this.latestNumberOfFilesFailed = progress.NumberOfFilesFailed;

            UpdateUploadProgress?.Invoke(this, this.latestBytesTransferred);
        }

        public override string ToString()
        {
            return string.Format("Transferred bytes: {0}; Transfered: {1}; Skipped: {2}, Failed: {3}",
                this.latestBytesTransferred,
                this.latestNumberOfFilesTransferred,
                this.latestNumberOfFilesSkipped,
                this.latestNumberOfFilesFailed);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/PowerPoint/PowerPointGraphicsCaptureItemsFactory.cs<|end_filename|>
﻿using Microsoft.Extensions.Logging;
using Speaker.Recorder.Capture;
using Speaker.Recorder.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using Windows.Graphics.Capture;

namespace Speaker.Recorder.PowerPoint
{
    public class PowerPointGraphicsCaptureItemsFactory
    {
        private readonly Dictionary<string, (int count, GraphicsCaptureItem graphicsCaptureItem, Process process)> currentCaptureItems = new Dictionary<string, (int, GraphicsCaptureItem, Process)>();
        private readonly ILogger<PowerPointGraphicsCaptureItemsFactory> logger;

        public PowerPointGraphicsCaptureItemsFactory(ILogger<PowerPointGraphicsCaptureItemsFactory> logger)
        {
            this.logger = logger;
        }

        public void Remove(string path)
        {
            if (path != null && currentCaptureItems.TryGetValue(path, out var current))
            {
                current.count--;
                this.logger.LogInformation($"PowerPointProvider for {path} count decremented {current.count}");
                if (current.count <= 0)
                {
                    this.logger.LogInformation($"Removing PowerPointProvider for {path} and killing process");
                    currentCaptureItems.Remove(path);
                    current.process.Kill();
                }
                else
                {
                    currentCaptureItems[path] = current;
                }
            }
        }

        public void SendKey(Key key)
        {
            foreach (var item in currentCaptureItems.Values)
            {
                WindowHelper.SendKey(item.process.MainWindowHandle, new IntPtr(KeyInterop.VirtualKeyFromKey(key)));
            }
        }

        public void CloseAll()
        {
            foreach (var item in currentCaptureItems.Values)
            {
                try
                {
                    item.process.Kill();
                }
                catch { }
            }
        }

        public GraphicsCaptureItem GetOrCreateGraphicsCaptureItem(string path)
        {
            if (path != null)
            {
                try
                {
                    if (!currentCaptureItems.TryGetValue(path, out var current))
                    {
                        this.logger.LogInformation($"Creating PowerPointProvider for {path}");
                        var process = PowerPointHelper.StartPowerPoint(path);
                        var size = WindowHelper.GetWindowSize(process.MainWindowHandle);
                        WindowHelper.SetWindowPosition(process.MainWindowHandle, new Rect(new Point(size.Width - 1, size.Height - 1), new Size(1920, 1080)));
                        WindowHelper.SetForegroundWindow(Process.GetCurrentProcess().MainWindowHandle);
                        GraphicsCaptureItem item = CaptureHelper.CreateItemForWindow(process.MainWindowHandle);
                        item.Closed += Item_Closed;
                        current = (0, item, process);

                        void Item_Closed(GraphicsCaptureItem sender, object args)
                        {
                            sender.Closed -= Item_Closed;
                            currentCaptureItems.Remove(path);
                        }
                    }

                    current.count++;
                    this.logger.LogInformation($"PowerPointProvider for {path} count incremented {current.count}");
                    currentCaptureItems[path] = current;
                    return current.graphicsCaptureItem;

                }
                catch (Exception e)
                {
                    this.logger.LogInformation(e, $"Error creating PowerPointProvider for {path}");
                }
            }

            return null;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/ISessionsService.cs<|end_filename|>
﻿using Speaker.Recorder.Models;
using System.Collections.Generic;

namespace Speaker.Recorder.Services
{
    public interface ISessionsService
    {
        public RecordingSession StartRecordingSession(KinectRecorderService kinectRecorder, PowerPointRecorderService powerPointRecorder);

        public IEnumerable<Session> GetSessions();
    }
}

<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Base/RelayCommand.cs<|end_filename|>
﻿using System;
using System.Windows.Input;

namespace Speaker.Recorder.ViewModels.Base
{
    public class RelayCommand : ICommand
    {
        private readonly Action executedMethod;
        private readonly Func<bool> canExecuteMethod;

        public event EventHandler CanExecuteChanged;

        public RelayCommand(Action execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action execute, Func<bool> canExecute)
        {
            this.executedMethod = execute ?? throw new ArgumentNullException(nameof(execute));
            this.canExecuteMethod = canExecute;
        }

        public bool CanExecute(object parameter) => this.canExecuteMethod == null || this.canExecuteMethod();

        public void Execute(object parameter) => this.executedMethod();

        public void OnCanExecuteChanged() => this.CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Base/IPausableViewModel.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Speaker.Recorder.ViewModels.Base
{
    public interface IPausableViewModel
    {
        void Pause();

        void Resume();
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder.UWP/App.xaml.cs<|end_filename|>
﻿using Microsoft.Toolkit.Win32.UI.XamlHost;

namespace Speaker.Recorder.UWP
{
    public sealed partial class App : XamlApplication
    {
        public App()
        {
            this.InitializeComponent();
        }

        public void Include()
        {
            var t1 = typeof(Microsoft.Xaml.Interactivity.Behavior);
            var t2 = typeof(Microsoft.Xaml.Interactions.Core.CallMethodAction);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/PowerPoint/PowerPointHelper.cs<|end_filename|>
﻿using Speaker.Recorder.Helpers;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.PowerPoint
{
    static class PowerPointHelper
    {
        private static string powerPointPath;

        public static string FindExtensionExe(string extension)
        {
            try
            {
                var assocInfo = new ProcessStartInfo("cmd.exe");
                assocInfo.ArgumentList.Add("/c");
                assocInfo.ArgumentList.Add("assoc");
                assocInfo.ArgumentList.Add(extension);
                assocInfo.CreateNoWindow = true;
                assocInfo.WindowStyle = ProcessWindowStyle.Hidden;
                assocInfo.RedirectStandardOutput = true;
                using var assocProcess = Process.Start(assocInfo);
                assocProcess.WaitForExit();
                var assocResult = assocProcess.StandardOutput.ReadToEnd();
                var assocType = assocResult.Substring(extension.Length + 1).Trim(); // .pptx=PowerPoint.Show.12

                var ftypeInfo = new ProcessStartInfo("cmd.exe");
                ftypeInfo.ArgumentList.Add("/c");
                ftypeInfo.ArgumentList.Add("ftype");
                ftypeInfo.ArgumentList.Add(assocType);
                ftypeInfo.CreateNoWindow = true;
                ftypeInfo.WindowStyle = ProcessWindowStyle.Hidden;
                ftypeInfo.RedirectStandardOutput = true;
                using var ftypeProcess = Process.Start(ftypeInfo);
                ftypeProcess.WaitForExit();
                var ftypeResult = ftypeProcess.StandardOutput.ReadToEnd();
                var fileMatch = Regex.Match(ftypeResult, @"^[^=]+=(""([^""]+)""|([^\s]+))"); // PowerPoint.Show.12="C:\Program Files\Microsoft Office\Root\Office16\POWERPNT.EXE" "%1" /ou "%u"
                var ftypePath = fileMatch.Groups[1].Value.StartsWith('"') ? fileMatch.Groups[2].Value.Trim() : fileMatch.Groups[3].Value.Trim();

                return ftypePath;
            }
            catch (Exception e)
            {
                Debug.WriteLine($"Exception finding program for {extension}: {e}");
                return null;
            }
        }

        public static void Initilize()
        {
            powerPointPath = FindExtensionExe(".pptx");
        }

        public static Process StartPowerPoint(string pptPath)
        {
            var success = FileUnblocker.Unblock(pptPath);
            if (!success)
            {
                throw new InvalidOperationException($"The specified file is blocked and can not be unblocked. {pptPath}");
            }

            if (powerPointPath == null)
            {
                powerPointPath = FindExtensionExe(Path.GetExtension(pptPath));
            }

            var processName = Path.GetFileNameWithoutExtension(powerPointPath);
            var existingProcesses = Process.GetProcessesByName(processName);
            foreach (var item in existingProcesses)
            {
                var cmd = GetCommandLine(item);
                if (cmd?.Contains("/s", StringComparison.InvariantCultureIgnoreCase) == true)
                {
                    item.Kill();
                }
            }

            ProcessStartInfo psi = new ProcessStartInfo(powerPointPath);
            psi.ArgumentList.Add("/S");
            psi.ArgumentList.Add(pptPath);
            psi.UseShellExecute = true;
            using var process = Process.Start(psi);
            process.WaitForInputIdle();
            Process powerPointProcess;
            int maxTries = 50;
            do
            {
                maxTries--;
                if (!process.HasExited)
                {
                    Thread.Sleep(10);
                }
                powerPointProcess = WindowEnumerationHelper.GetProcesses(processName).FirstOrDefault(x => GetCommandLine(x)?.Contains(pptPath) == true);
                if (powerPointProcess == null)
                {
                    Thread.Sleep(10);
                }
            } while (powerPointProcess == null && maxTries > 0);

            return powerPointProcess;
        }

        public static string GetCommandLine(Process process)
        {
            try
            {
                string cmdLine = null;
                using (var searcher = new ManagementObjectSearcher($"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}"))
                {
                    // By definition, the query returns at most 1 match, because the process
                    // is looked up by ID (which is unique by definition).
                    var matchEnum = searcher.Get().GetEnumerator();
                    if (matchEnum.MoveNext())
                    {
                        cmdLine = matchEnum.Current["CommandLine"]?.ToString();
                    }
                }

                return cmdLine;
            }
            catch
            {
                return null;
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/IdentificationService/BaseIdentificationService.cs<|end_filename|>
﻿using System;
using System.Text.RegularExpressions;

namespace Speaker.Recorder.Services.IdentificationService
{
    public abstract class BaseIdentificationService : IIdentificationService
    {
        public abstract string GetRawIdentifier();

        public string GetSanitizedIdentifier()
        {
            var identifier = GetRawIdentifier();

            // Convert to lowercase
            identifier = identifier.ToLowerInvariant();

            // Remove invalid characters (valid: lowercase, numbers, hyphens)
            Regex forbidenCharactersRegex = new Regex("[^a-z0-9-]*");
            identifier = forbidenCharactersRegex.Replace(identifier, string.Empty);

            // Remove if two or more hyphens
            Regex twoOrMoreHyphens = new Regex("-{2,}");
            identifier = twoOrMoreHyphens.Replace(identifier, string.Empty);

            // If starts or ends with a hyphen, replace it with a zero
            Regex startsWithAHyphen = new Regex("^[^0-9a-z]");
            Regex endsWithAHyphen = new Regex("[^0-9a-z]$");
            identifier = startsWithAHyphen.Replace(identifier, "0");
            identifier = endsWithAHyphen.Replace(identifier, "0");

            // If it has less than three characters, append some zeros
            if (identifier.Length < 3)
            {
                identifier += "000";
            }

            // If it has more than 63 characters, truncate it
            if (identifier.Length - 63 > 0)
            {
                identifier = identifier.Substring(0, 63);
            }

            return identifier;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Capture/MultithreadLock.cs<|end_filename|>
﻿using System;

namespace Speaker.Recorder.Capture
{
    public sealed class MultithreadLock : IDisposable
    {
        private SharpDX.Direct3D11.Multithread multithread;

        public MultithreadLock(SharpDX.Direct3D11.Multithread multithread)
        {
            this.multithread = multithread;
            this.multithread?.Enter();
        }

        public void Dispose()
        {
            this.multithread?.Leave();
            this.multithread = null;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder.UWP.Controls/VideosPlayer.xaml.cs<|end_filename|>
﻿using System;
using Windows.Media;
using Windows.Media.Core;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Speaker.Recorder.UWP
{
    public sealed partial class VideosPlayer : UserControl
    {
        private MediaTimelineController timelineController;

        public event EventHandler<TimeSpan> PositionChanged;

        public event EventHandler MediaEnded;

        public VideosPlayer()
        {
            this.InitializeComponent();
            this.timelineController = new MediaTimelineController();
            this.timelineController.Ended += TimelineController_Ended;
            this.timelineController.PositionChanged += TimelineController_PositionChanged;
        }

        public void MoveMainToBack() => this.MoveUiToBack(this.MainMediaElementButton, this.MainGrid);

        public void MoveSecondToBack() => this.MoveUiToBack(this.SecondMediaElementButton, this.MainGrid);

        public void MoveThirdToBack() => this.MoveUiToBack(this.ThirdMediaElementButton, this.MainGrid);

        public void MoveUiToBack(UIElement element, Grid yourParentElement)
        {
            int index = yourParentElement.Children.IndexOf(element);
            if (index != -1)
                yourParentElement.Children.Move((uint)index, 0);
        }

        public async void SetVideos(string[] videos)
        {
            this.timelineController.Pause();
            this.timelineController.Position = TimeSpan.Zero;
            this.timelineController.Duration = null;
            this.MainMediaElementButton.IsChecked = true;
            if (videos?.Length >= 1)
            {
                this.MainMediaPlayer.Source = MediaSource.CreateFromStorageFile(await StorageFile.GetFileFromPathAsync(videos[0]));
            }
            else
            {
                this.MainMediaPlayer.Source = null;
            }
            if (videos?.Length >= 2)
            {
                this.SecondMediaPlayer.Source = MediaSource.CreateFromStorageFile(await StorageFile.GetFileFromPathAsync(videos[1]));
            }
            else
            {
                this.SecondMediaPlayer.Source = null;
            }
            if (videos?.Length >= 3)
            {
                this.ThirdMediaPlayer.Source = MediaSource.CreateFromStorageFile(await StorageFile.GetFileFromPathAsync(videos[2]));
            }
            else
            {
                this.ThirdMediaPlayer.Source = null;
            }
        }

        private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
        {
            this.PositionChanged?.Invoke(this, sender.Position);
        }

        private void TimelineController_Ended(MediaTimelineController sender, object args)
        {
            this.MediaEnded?.Invoke(this, EventArgs.Empty);
        }

        public void Play(TimeSpan? duration)
        {
            if (this.timelineController.State == MediaTimelineControllerState.Paused && this.timelineController.Position != TimeSpan.Zero)
            {
                this.timelineController.Resume();
            }
            else
            {
                this.SetTimeController(this.MainMediaPlayer);
                this.SetTimeController(this.SecondMediaPlayer);
                this.SetTimeController(this.ThirdMediaPlayer);

                this.timelineController.Duration = duration;
                this.timelineController.Start();
            }
        }

        private void SetTimeController(MediaPlayerElement mediaPlayerElement)
        {
            if (mediaPlayerElement.MediaPlayer != null)
            {
                mediaPlayerElement.MediaPlayer.TimelineController = this.timelineController;
            }
        }

        public void Pause()
        {
            this.timelineController.Pause();
        }

        public void Seek(TimeSpan position)
        {
            var isRunning = this.timelineController.State == MediaTimelineControllerState.Running;
            this.timelineController.Position = position;
            if (isRunning)
            {
                this.timelineController.Resume();
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/FileUnblocker.cs<|end_filename|>
﻿using System.Runtime.InteropServices;

namespace Speaker.Recorder.Helpers
{
    public class FileUnblocker
    {
        const long INVALID_FILE_ATTRIBUTES = -1;

        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool DeleteFile(string name);

        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern int GetFileAttributes(string name);

        public static bool Unblock(string fileName)
        {
            if (IsBlocked(fileName))
            {
                return DeleteFile(fileName + ":Zone.Identifier");
            }

            return true;
        }

        public static bool IsBlocked(string fileName)
        {
            int attributes = GetFileAttributes(fileName + ":Zone.Identifier");
            return attributes != INVALID_FILE_ATTRIBUTES;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/IdentificationService/IIdentificationService.cs<|end_filename|>
﻿namespace Speaker.Recorder.Services.IdentificationService
{
    public interface IIdentificationService
    {
        string GetRawIdentifier();

        // As per Azure Documentation, e.g to create a blob, we need:
        // - 3 to 63 characters
        // - Only lowercase, numbers and hyphens
        // - Begin and end with a letter or a number
        // - NO consecutive hyphens
        string GetSanitizedIdentifier();
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/WindowEnumerationHelper.cs<|end_filename|>
﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Speaker.Recorder.Helpers
{
    static class WindowEnumerationHelper
    {
        public static IEnumerable<Process> GetProcesses()
        {
            var processesWithWindows = from p in Process.GetProcesses()
                                       where !string.IsNullOrWhiteSpace(p.MainWindowTitle) && WindowHelper.IsWindowValidForCapture(p.MainWindowHandle)
                                       select p;
            return processesWithWindows;
        }

        public static IEnumerable<Process> GetProcesses(string processName)
        {
            var processesWithWindows = from p in Process.GetProcesses()
                                       where p.ProcessName == processName && !string.IsNullOrWhiteSpace(p.MainWindowTitle) && WindowHelper.IsWindowValidForCapture(p.MainWindowHandle)
                                       select p;
            return processesWithWindows;
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Controls/PowerPointControl.cs<|end_filename|>
﻿using Microsoft.Extensions.DependencyInjection;
using Speaker.Recorder.Kinect;
using Speaker.Recorder.PowerPoint;
using System;
using System.Windows;
using System.Windows.Controls;
using Windows.Graphics.Capture;
using Image = System.Windows.Controls.Image;

namespace Speaker.Recorder.Controls
{
    [TemplatePart(Name = ImageHostPart, Type = typeof(Image))]
    public class PowerPointControl : Control
    {
        protected const string ImageHostPart = "PART_ImageHost";
        private Image imageHost;
        private GraphicsItemD3DImage imageHostSource;
        private GraphicsCaptureItem currentGraphicsItem;
        private PowerPointGraphicsCaptureItemsFactory powerPointGraphicsCaptureItemsFactory;
        public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register(nameof(FilePath), typeof(string), typeof(PowerPointControl), new PropertyMetadata(null, FilePathChanged));


        public string FilePath
        {
            get { return (string)GetValue(FilePathProperty); }
            set { SetValue(FilePathProperty, value); }
        }

        public static readonly DependencyProperty IsFailedProperty = DependencyProperty.Register(nameof(IsFailed), typeof(bool), typeof(PowerPointControl), new PropertyMetadata(false));

        public bool IsFailed
        {
            get { return (bool)GetValue(IsFailedProperty); }
            set { SetValue(IsFailedProperty, value); }
        }

        public static readonly DependencyProperty RenderWidthProperty = DependencyProperty.Register(nameof(RenderWidth), typeof(double), typeof(PowerPointControl), new PropertyMetadata(double.NaN));

        public double RenderWidth
        {
            get { return (double)GetValue(RenderWidthProperty); }
            set { SetValue(RenderWidthProperty, value); }
        }

        public static readonly DependencyProperty RenderHeightProperty = DependencyProperty.Register(nameof(RenderHeight), typeof(double), typeof(PowerPointControl), new PropertyMetadata(double.NaN));

        public double RenderHeight
        {
            get { return (double)GetValue(RenderHeightProperty); }
            set { SetValue(RenderHeightProperty, value); }
        }

        public static readonly DependencyProperty FpsDividerProperty = DependencyProperty.Register(nameof(FpsDivider), typeof(int), typeof(PowerPointControl), new PropertyMetadata(1, ProviderPropertyChanged));

        public int FpsDivider
        {
            get { return (int)GetValue(FpsDividerProperty); }
            set { SetValue(FpsDividerProperty, value); }
        }

        static PowerPointControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(PowerPointControl), new FrameworkPropertyMetadata(typeof(PowerPointControl)));
        }

        public PowerPointControl()
        {
            this.powerPointGraphicsCaptureItemsFactory = (Application.Current as App)?.AppHost.Services.GetRequiredService<PowerPointGraphicsCaptureItemsFactory>();
            this.Dispatcher.ShutdownStarted += this.DispatcherShutdownStarted;
            this.Loaded += this.OnLoaded;
            this.Unloaded += this.OnUnloaded;
            this.IsVisibleChanged += this.OnIsVisibleChanged;
        }

        private void DispatcherShutdownStarted(object sender, EventArgs e)
        {
            this.UpdateProvider(this.FilePath, null);
        }

        private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                this.UpdateSource();
            }
            else
            {
                this.imageHostSource?.SetGraphicItem(null);
            }
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            this.UpdateProvider(this.FilePath, null);
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            this.UpdateSource();
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (this.imageHost != null)
            {
                this.imageHost.Source = null;
            }

            this.imageHost = (Image)this.GetTemplateChild(ImageHostPart);
            this.imageHostSource = new GraphicsItemD3DImage();
            this.imageHost.Source = this.imageHostSource;
            this.UpdateSource();
        }

        private void UpdateProvider(string oldValue, string newValue)
        {
            this.IsFailed = false;

            this.powerPointGraphicsCaptureItemsFactory.Remove(oldValue);
            if (this.currentGraphicsItem != null)
            {
                this.currentGraphicsItem.Closed -= Next_Closed;
            }

            if (App.Current?.MainWindow == null)
            {
                return;
            }

            var next = this.powerPointGraphicsCaptureItemsFactory.GetOrCreateGraphicsCaptureItem(newValue);
            if (next == null && newValue != null)
            {
                this.IsFailed = true;
            }
            else if (next != null)
            {
                next.Closed += Next_Closed;
            }

            this.currentGraphicsItem = next;
                this.UpdateSource();
        }

        private void Next_Closed(GraphicsCaptureItem sender, object args)
        {
            sender.Closed -= Next_Closed;
            this.UpdateProvider(null, this.FilePath);
        }

        private void UpdateSource()
        {
            if (this.IsVisible && this.imageHostSource != null)
            {
                this.imageHostSource.FpsDivider = this.FpsDivider;
                this.imageHostSource.RenderWidth = (int)(double.IsNaN(this.RenderWidth) ? -1 : this.RenderWidth);
                this.imageHostSource.RenderHeight = (int)(double.IsNaN(this.RenderHeight) ? -1 : this.RenderHeight);
                this.imageHostSource?.SetGraphicItem(this.currentGraphicsItem);
            }
        }

        private static void FilePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((PowerPointControl)d).UpdateProvider((string)e.OldValue, (string)e.NewValue);
        }

        private static void ProviderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((PowerPointControl)d).UpdateSource();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Kinect/DecodedKinectCaptureFrame.cs<|end_filename|>
﻿using Lennox.LibYuvSharp;
using Microsoft.Azure.Kinect.Sensor;
using SharpDX;
using SharpDX.WIC;
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace Speaker.Recorder.Kinect
{
    public class DecodedKinectCaptureFrame : IDisposable
    {
        private readonly ImagingFactory imagingFactory;
        private readonly Image image;
        private BitmapDecoder bitmapDecoder;
        private WICStream stream;
        private BitmapSource lastGeneratedBitmap;
        private bool disposed;
        private BitmapFrameDecode frame;
        private FormatConverter formatConverter;

        public BitmapSource Bitmap
        {
            get
            {
                if (this.lastGeneratedBitmap == null && !this.disposed)
                {
                    lock (this.imagingFactory)
                    {
                        if (this.lastGeneratedBitmap == null && !this.disposed)
                        {
                            this.lastGeneratedBitmap = this.GetBitmapFromImage();
                            this.lastGeneratedBitmap.Disposed += this.LastGeneratedBitmap_Disposed;
                        }
                    }
                }

                return this.lastGeneratedBitmap;
            }
        }

        public KinectImageProvider Provider { get; }

        private void LastGeneratedBitmap_Disposed(object sender, EventArgs e)
        {
            this.lastGeneratedBitmap.Disposed -= this.LastGeneratedBitmap_Disposed;
            Interlocked.CompareExchange(ref this.lastGeneratedBitmap, null, (BitmapSource)sender);
        }

        [DllImport("k4a", CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr k4a_image_get_buffer(IntPtr image_handle);

        public DecodedKinectCaptureFrame(Image image, KinectImageProvider provider)
        {
            this.imagingFactory = new ImagingFactory();
            this.image = image;
            Provider = provider;
        }

        private unsafe BitmapSource GetBitmapFromImage()
        {
            var captureData = k4a_image_get_buffer(this.image.Handle);
            var captureSize = (int)this.image.Size;

            switch (image.Format)
            {
                case ImageFormat.ColorMJPG:
                    {
                        this.bitmapDecoder = new BitmapDecoder(this.imagingFactory, ContainerFormatGuids.Jpeg);
                        this.stream = new WICStream(this.imagingFactory, new DataPointer(captureData, captureSize));
                        this.bitmapDecoder.Initialize(this.stream, DecodeOptions.CacheOnLoad);

                        this.frame = this.bitmapDecoder.GetFrame(0);
                        this.formatConverter = new FormatConverter(this.imagingFactory);
                        formatConverter.Initialize(frame, PixelFormat.Format32bppPBGRA, BitmapDitherType.None, null, 0.0, BitmapPaletteType.Custom);
                        return formatConverter;
                    }
                case ImageFormat.ColorBGRA32:
                    return new Bitmap(this.imagingFactory,
                        image.WidthPixels,
                        image.HeightPixels,
                        PixelFormat.Format32bppPBGRA,
                        new DataRectangle(captureData, image.StrideBytes),
                        captureSize);
                case ImageFormat.IR16:
                case ImageFormat.Depth16:
                    return new Bitmap(this.imagingFactory,
                        image.WidthPixels,
                        image.HeightPixels,
                        PixelFormat.Format16bppGrayFixedPoint,
                        new DataRectangle(captureData, image.StrideBytes),
                        captureSize);
                case ImageFormat.ColorNV12:
                    {
                        int textureWidth = this.image.WidthPixels;
                        int textureHeight = this.image.HeightPixels;
                        var imagePixels = textureWidth * textureHeight;
                        var nv12Span = new Span<byte>(captureData.ToPointer(), captureSize);
                        var nv12ySpan = nv12Span.Slice(0, imagePixels);
                        var nv12uvSpan = nv12Span.Slice(nv12ySpan.Length, (int)(imagePixels * 0.5));

                        var bitmap = new Bitmap(this.imagingFactory,
                            image.WidthPixels,
                            image.HeightPixels,
                            PixelFormat.Format32bppPBGRA,
                            BitmapCreateCacheOption.CacheOnLoad);
                        using (var bitmapLock = bitmap.Lock(BitmapLockFlags.Write))
                        {
                            var ptr = (byte*)bitmapLock.Data.DataPointer.ToPointer();
                            fixed (byte* yptr = nv12ySpan)
                            fixed (byte* uvptr = nv12uvSpan)
                            {
                                _ = LibYuv.NV12ToARGB(yptr, textureWidth, uvptr, textureWidth, ptr, textureWidth * 4, textureWidth, textureHeight);
                            }
                        }

                        return bitmap;
                    }
                case ImageFormat.ColorYUY2:
                    {
                        int textureWidth = this.image.WidthPixels;
                        int textureHeight = this.image.HeightPixels;
                        var imagePixels = textureWidth * textureHeight;
                        var yuv2Span = new Span<byte>(captureData.ToPointer(), captureSize);

                        var bitmap = new Bitmap(this.imagingFactory,
                            image.WidthPixels,
                            image.HeightPixels,
                            PixelFormat.Format32bppPBGRA,
                            BitmapCreateCacheOption.CacheOnLoad);
                        using (var bitmapLock = bitmap.Lock(BitmapLockFlags.Write))
                        {
                            var ptr = (byte*)bitmapLock.Data.DataPointer.ToPointer();
                            fixed (byte* yuv2ptr = yuv2Span)
                            {
                                _ = LibYuv.YUY2ToARGB(yuv2ptr, textureWidth * 2, ptr, textureWidth * 4, textureWidth, textureHeight);
                            }
                        }

                        return bitmap;
                    }
                case ImageFormat.Custom8:
                case ImageFormat.Custom16:
                case ImageFormat.Custom:
                default:
                    throw new NotImplementedException($"Format not implemented: {image.Format}.");
            }
        }

        public void Dispose()
        {
            if (!this.disposed)
            {
                this.disposed = true;
                this.frame?.Dispose();
                this.formatConverter?.Dispose();
                this.image?.Dispose();
                this.stream?.Dispose();
                this.bitmapDecoder?.Dispose();
                this.lastGeneratedBitmap?.Dispose();
                this.imagingFactory?.Dispose();
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/AppUpdaterService.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Services
{
    public class AppUpdaterService : IHostedService
    {
        private readonly HttpClient http;
        private readonly string updateUrl;
        private readonly FileVersionInfo assemblyFileVersionInfo;
        private readonly IHostApplicationLifetime applicationLifetime;
        private readonly ILogger<AppUpdaterService> logger;

        public AppUpdaterService(
            IConfiguration configuration,
            IHostApplicationLifetime applicationLifetime,
            ILogger<AppUpdaterService> logger)
        {
            this.http = new HttpClient();
            this.updateUrl = configuration.GetValue<string>("Data:AppUpdateUrl");
            this.assemblyFileVersionInfo = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location);
            this.applicationLifetime = applicationLifetime;
            this.logger = logger;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            logger.LogInformation($"Starting update");
            if (await this.CheckAndUpdate())
            {
                this.applicationLifetime.StopApplication();
            }
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }

        public async Task<bool> CheckAndUpdate()
        {
            if (this.updateUrl != null && Version.TryParse(assemblyFileVersionInfo.ProductVersion, out var currentVersion) && currentVersion > new Version(1, 0, 0))
            {
                DirectoryInfo tempUpdateExtractFolder = null;
                string tempUpdateFile = null;
                logger.LogInformation($"Current version : {currentVersion}");
                try
                {
                    var timeoutCancellationToken = new CancellationTokenSource();
                    timeoutCancellationToken.CancelAfter(5000);
                    using var httpResponse = await this.http.GetAsync(this.updateUrl, HttpCompletionOption.ResponseHeadersRead, timeoutCancellationToken.Token).ConfigureAwait(false);
                    if (httpResponse.Headers.TryGetValues("x-ms-meta-AppVersion", out var versionValues) && versionValues.Any())
                    {
                        if (Version.TryParse(versionValues.First(), out var lastVersion))
                        {
                            logger.LogInformation($"Last version: {lastVersion}");
                            logger.LogInformation(currentVersion < lastVersion ? $"The application will be updated" : "No update required");
                            if (currentVersion < lastVersion)
                            {
                                tempUpdateFile = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
                                using (var tempUpdateStream = File.Create(tempUpdateFile, 16 * 1024))
                                {
                                    await httpResponse.Content.CopyToAsync(tempUpdateStream).ConfigureAwait(false);
                                }

                                tempUpdateExtractFolder = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(tempUpdateFile)));
                                logger.LogInformation($"Created temp directory in {tempUpdateExtractFolder}");
                                ZipFile.ExtractToDirectory(tempUpdateFile, tempUpdateExtractFolder.FullName);
                                logger.LogInformation("Extracted new version");
                                UpdateSelf(tempUpdateExtractFolder.FullName);
                                return true;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, $"Exception trying download new version");
                }
                finally
                {
                    if (tempUpdateFile != null)
                    {
                        File.Delete(tempUpdateFile);
                    }
                }
            }

            return false;
        }

        public static void UpdateSelf(string updateFolder)
        {
            var selfExecutable = Process.GetCurrentProcess().MainModule.FileName;
            var self = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            var updateBatFile = Path.Combine(self, "Update.bat");

            using (var batFile = new StreamWriter(File.Create(updateBatFile)))
            {
                batFile.WriteLine("@ECHO OFF");
                batFile.WriteLine("TIMEOUT /t 1 /nobreak > NUL");
                batFile.WriteLine("TASKKILL /IM \"{0}\" > NUL", Path.GetFileName(selfExecutable));
                batFile.WriteLine("MOVE \"{0}\\*\" \"{1}\"", updateFolder, self);
                batFile.WriteLine("DEL \"%~f0\" & START \"\" /B \"{0}\"", selfExecutable);
            }

            ProcessStartInfo startInfo = new ProcessStartInfo(updateBatFile)
            {
                CreateNoWindow = true,
                UseShellExecute = false,
                WorkingDirectory = self
            };

            Process.Start(startInfo);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder.UWP.Controls/Controls/ChangeAttachedPropertyAction.cs<|end_filename|>
﻿using Microsoft.Xaml.Interactions.Core;
using Microsoft.Xaml.Interactivity;
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Speaker.Recorder.UWP.Controls
{
    public class ChangeAttachedPropertyAction : DependencyObject, IAction
    {
        public static readonly DependencyProperty PropertyNameProperty = DependencyProperty.Register(
            nameof(PropertyName),
            typeof(PropertyPath),
            typeof(ChangeAttachedPropertyAction),
            new PropertyMetadata(null));

        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
            nameof(Value),
            typeof(object),
            typeof(ChangeAttachedPropertyAction),
            new PropertyMetadata(null));

        public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register(
            nameof(TargetObject),
            typeof(object),
            typeof(ChangeAttachedPropertyAction),
            new PropertyMetadata(null));

        public PropertyPath PropertyName
        {
            get
            {
                return (PropertyPath)this.GetValue(PropertyNameProperty);
            }
            set
            {
                this.SetValue(PropertyNameProperty, value);
            }
        }

        public object Value
        {
            get
            {
                return this.GetValue(ValueProperty);
            }
            set
            {
                this.SetValue(ValueProperty, value);
            }
        }

        public object TargetObject
        {
            get
            {
                return this.GetValue(TargetObjectProperty);
            }
            set
            {
                this.SetValue(TargetObjectProperty, value);
            }
        }

        public object Execute(object sender, object parameter)
        {
            var targetObject = sender;
            if (this.ReadLocalValue(ChangePropertyAction.TargetObjectProperty) != DependencyProperty.UnsetValue)
            {
                targetObject = this.TargetObject;
            }

            if (targetObject == null || this.PropertyName == null)
            {
                return false;
            }

            this.UpdatePropertyValue(targetObject);
            return true;
        }

        private void UpdatePropertyValue(object targetObject)
        {
            var propertyNamePaths = this.PropertyName.Path.Split('.');
            var className = propertyNamePaths[0];
            var propertyName = propertyNamePaths[propertyNamePaths.Length - 1];
            // At this moment only works with types in Windows.UI.Xaml.Controls namespace
            Type targetType = typeof(Grid).Assembly.GetType("Windows.UI.Xaml.Controls." + className);

            var setMethod = targetType.GetRuntimeMethods().FirstOrDefault(x => x.Name == $"Set{propertyName}");
            if(setMethod == null)
            {
                throw new ArgumentException(string.Format(
                    CultureInfo.CurrentCulture,
                    "Property {0} defined by type {1} does not expose a set method and therefore cannot be modified.",
                    propertyName,
                    className));
            }

            var propertyType = setMethod.GetParameters()[1].ParameterType;

            Exception innerException = null;
            try
            {
                object result = null;
                string valueAsString = null;
                TypeInfo propertyTypeInfo = propertyType.GetTypeInfo();
                if (this.Value == null)
                {
                    // The result can be null if the type is generic (nullable), or the default value of the type in question
                    result = propertyTypeInfo.IsValueType ? Activator.CreateInstance(propertyType) : null;
                }
                else if (propertyTypeInfo.IsAssignableFrom(this.Value.GetType().GetTypeInfo()))
                {
                    result = this.Value;
                }
                else
                {
                    valueAsString = this.Value.ToString();
                    result = propertyTypeInfo.IsEnum ? Enum.Parse(propertyType, valueAsString, false) :
                        TypeConverterHelper.Convert(valueAsString, propertyType.FullName);
                }

                setMethod.Invoke(null, new[] { targetObject, result });
            }
            catch (FormatException e)
            {
                innerException = e;
            }
            catch (ArgumentException e)
            {
                innerException = e;
            }

            if (innerException != null)
            {
                throw new ArgumentException(string.Format(
                    CultureInfo.CurrentCulture,
                    "Cannot assign value of type {0} to property {1} of type {2}. The {1} property can be assigned only values of type {2}.",
                    this.Value != null ? this.Value.GetType().Name : "null",
                    this.PropertyName,
                    propertyType.Name),
                    innerException);
            }
        }
    }
}

<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/RecordViewModel.cs<|end_filename|>
﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Win32;
using Speaker.Recorder.Models;
using Speaker.Recorder.Services;
using Speaker.Recorder.ViewModels.Base;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

namespace Speaker.Recorder.ViewModels
{
    public class RecordViewModel : BaseViewModel, IPausableViewModel, IDisposable
    {
        private bool roomDialogIsShowing;
        private bool isRecording;
        private bool presentationSavedIsShowing;
        private string selectedPpt;
        private bool isKinectReady;
        private bool isPptReady;
        private CancellationTokenSource recordingCancellationTokenSource;
        private CancellationTokenSource deviceCountCancellationTokenSource;
        private TimeSpan recordDuration;
        private string kinectRecordFile;
        private int kinectDeviceIndex = -1;
        private int kinectDeviceCount;
        private bool disposed;

        private readonly INavigationService navigationService;
        private readonly LocalPathsHelper localPathsHelper;
        private readonly SessionsService sessionsService;
        private readonly Func<KinectRecorderService> kinectRecordFactory;
        private readonly Func<PowerPointRecorderService> powerPointRecordFactory;
        private readonly IConfiguration configuration;
        private readonly ILogger<RecordViewModel> logger;
        private RecordingSession recordingSession;

        public TimeSpan MinRecordTime { get; }

        public int KinectDeviceCount
        {
            get => kinectDeviceCount;
            set => SetProperty(ref kinectDeviceCount, value);
        }

        public string KinectRecordFile
        {
            get => kinectRecordFile;
            set => SetProperty(ref kinectRecordFile, value);
        }

        public int KinectDeviceIndex
        {
            get => kinectDeviceIndex;
            set => SetProperty(ref kinectDeviceIndex, value);
        }

        public string SelectedPpt
        {
            get => selectedPpt;
            set
            {
                if (SetProperty(ref selectedPpt, value))
                {
                    this.OnPropertyChanged(nameof(IsReady));
                    this.RecordCommand.OnCanExecuteChanged();
                }
            }
        }

        public bool IsPptReady
        {
            get => isPptReady;
            set
            {
                if (SetProperty(ref isPptReady, value))
                {
                    this.OnPropertyChanged(nameof(IsReady));
                    this.RecordCommand.OnCanExecuteChanged();
                }
            }
        }

        public bool IsKinectReady
        {
            get => isKinectReady;
            set
            {
                if (SetProperty(ref isKinectReady, value))
                {
                    this.OnPropertyChanged(nameof(IsReady));
                    this.RecordCommand.OnCanExecuteChanged();
                }
            }
        }

        public bool IsReady
        {
            get => this.IsKinectReady;
        }

        public bool IsRecording
        {
            get => isRecording;
            set
            {
                if (SetProperty(ref isRecording, value))
                {
                    this.OnPropertyChanged(nameof(StayOutIsShowing));
                    this.OnPropertyChanged(nameof(YouCanStartIsShowing));
                    this.GoToPlayerCommand.OnCanExecuteChanged();
                    this.LoadPowerPointCommand.OnCanExecuteChanged();
                }
            }
        }

        public TimeSpan RecordDuration
        {
            get => recordDuration;
            set
            {
                if (SetProperty(ref recordDuration, value))
                {
                    this.OnPropertyChanged(nameof(StayOutIsShowing));
                    this.OnPropertyChanged(nameof(YouCanStartIsShowing));
                    this.StopRecordCommand.OnCanExecuteChanged();
                }
            }
        }

        public bool RoomDialogIsShowing
        {
            get => roomDialogIsShowing;
            set => SetProperty(ref roomDialogIsShowing, value);
        }

        private TimeSpan StayOutMessageDuration => this.configuration.GetValue("Kinect:Record:StayOutMessageTime", TimeSpan.FromSeconds(5));

        public bool StayOutIsShowing
        {
            get => this.IsRecording && this.RecordDuration >= TimeSpan.FromSeconds(0) && this.RecordDuration < StayOutMessageDuration;
        }

        public bool YouCanStartIsShowing
        {
            get => this.IsRecording && this.RecordDuration >= StayOutMessageDuration
                && this.RecordDuration < StayOutMessageDuration + this.configuration.GetValue("Kinect:Record:YouCanStartMessageTime", TimeSpan.FromSeconds(3));
        }

        public bool PresentationSavedIsShowing
        {
            get => presentationSavedIsShowing;
            set
            {
                if (SetProperty(ref presentationSavedIsShowing, value))
                {
                    this.StopRecordCommand.OnCanExecuteChanged();
                }
            }
        }

        public RelayCommand GoToPlayerCommand { get; }

        public RelayCommand LoadPowerPointCommand { get; }

        public ICommand CancelEmptyDialogCommand { get; }

        public RelayCommand StopRecordCommand { get; }

        public ICommand AcceptEmptyDialogCommand { get; }

        public RelayCommand RecordCommand { get; }

        public RecordViewModel(INavigationService navigationService,
            LocalPathsHelper localPathsHelper,
            SessionsService sessionsService,
            Func<KinectRecorderService> kinectRecordFactory,
            Func<PowerPointRecorderService> powerPointRecordFactory,
            IConfiguration configuration,
            ILogger<RecordViewModel> logger)
        {
            this.KinectRecordFile = configuration.GetValue<string>("Kinect:PlaybackFile", null);
            if (this.KinectRecordFile == null)
            {
                this.KinectDeviceIndex = configuration.GetValue("Kinect:DeviceIndex", 0);
            }

            this.MinRecordTime = configuration.GetValue("Kinect:Record:MinRecordTime", TimeSpan.FromSeconds(10));

            this.navigationService = navigationService;
            this.localPathsHelper = localPathsHelper;
            this.sessionsService = sessionsService;
            this.kinectRecordFactory = kinectRecordFactory;
            this.powerPointRecordFactory = powerPointRecordFactory;
            this.configuration = configuration;
            this.logger = logger;
            this.GoToPlayerCommand = new RelayCommand(() => this.navigationService?.To<PlayerViewModel>(), () => !this.IsRecording);
            this.RecordCommand = new RelayCommand(() => this.RoomDialogIsShowing = true, () => this.IsReady);
            this.StopRecordCommand = new RelayCommand(() => this.recordingSession?.Stop(), () => this.RecordDuration > this.MinRecordTime && !this.PresentationSavedIsShowing);
            this.LoadPowerPointCommand = new RelayCommand(this.LoadPowerPoint, () => !this.IsRecording);
            this.CancelEmptyDialogCommand = new RelayCommand(() => this.RoomDialogIsShowing = false);
            this.AcceptEmptyDialogCommand = new RelayCommand(this.AceptsEmptyDialog);

            this.Resume();
        }

        public void Pause()
        {
            this.deviceCountCancellationTokenSource?.Cancel();
        }

        public void Resume()
        {
            if (this.KinectRecordFile == null)
            {
                this.KinectDeviceCountUpdater();
            }
        }

        private async void KinectDeviceCountUpdater()
        {
            var cancellationTokenSource = new CancellationTokenSource();
            Interlocked.Exchange(ref this.deviceCountCancellationTokenSource, cancellationTokenSource)?.Cancel();
            try
            {
                while (!this.disposed)
                {
                    cancellationTokenSource.Token.ThrowIfCancellationRequested();
                    this.KinectDeviceCount = Microsoft.Azure.Kinect.Sensor.Device.GetInstalledCount();
                    if (this.KinectDeviceCount <= this.KinectDeviceIndex)
                    {
                        this.KinectDeviceIndex = 0;
                    }

                    await Task.Delay(1300, cancellationTokenSource.Token);
                }
            }
            catch (OperationCanceledException)
            {
                logger.LogDebug("Device count loop cancelled.");
            }
        }

        private void LoadPowerPoint()
        {
            logger.LogInformation("Load PowerPoint");
            var fileDialog = new OpenFileDialog
            {
                DefaultExt = ".pptx",
                Filter = "PowerPoint file (*.pptx;*.ppt)|*.pptx;*.ppt",
                Multiselect = false,
                CheckPathExists = true,
            };

            if (fileDialog.ShowDialog() == true)
            {
                this.SelectedPpt = fileDialog.FileName;
                logger.LogInformation($"Selected PowerPoint: {this.SelectedPpt}");
            }
        }

        private async void AceptsEmptyDialog()
        {
            this.RoomDialogIsShowing = false;
            this.IsRecording = true;

            var sessionIdentifier = DateTime.Now.ToString("yyyy-dd-M-HH-mm-ss");
            var basePath = localPathsHelper.GetBasePath();
            var sessionPath = localPathsHelper.GetSessionPath(sessionIdentifier);
            var presentationPath = localPathsHelper.GetPowerPointRecordingPath(sessionIdentifier);
            this.recordingCancellationTokenSource = new CancellationTokenSource();

            KinectRecorderService[] kinectRecords = null;
            try
            {
                if (this.KinectDeviceCount > 0)
                {
                    kinectRecords = new KinectRecorderService[this.KinectDeviceCount];
                    for (int i = 0; i < this.KinectDeviceCount; i++)
                    {
                        var kinectPath = localPathsHelper.GetKinectRecordingPath(sessionIdentifier, i);
                        kinectRecords[i] = await kinectRecordFactory().WithAsync(i, kinectPath);
                    }
                }
                else
                {
                    var kinectPath = localPathsHelper.GetKinectRecordingPath(sessionIdentifier, 0);
                    kinectRecords = new KinectRecorderService[]
                    {
                        await this.kinectRecordFactory().WithAsync(this.KinectRecordFile, kinectPath)
                    };
                }

                foreach (var item in kinectRecords)
                {
                    item.FixExposure = this.configuration.GetValue("Kinect:Record:ExposureFixTime", TimeSpan.Zero);
                }

                using var powerPointRecord = await powerPointRecordFactory().WithAsync(this.SelectedPpt, presentationPath);

                this.recordingSession = sessionsService.StartRecordingSession(
                    kinectRecords,
                    powerPointRecord,
                    this.selectedPpt,
                    sessionPath,
                    this.recordingCancellationTokenSource);

                while (!this.recordingCancellationTokenSource.IsCancellationRequested)
                {
                    var task =
                        await Task.WhenAny(this.recordingSession.RecordingTasks.Concat(new[] { Task.Delay(200) }));
                    await task;
                    this.RecordDuration = powerPointRecord.Time;
                }

                this.PresentationSavedIsShowing = true;
            }
            catch (Exception e)
            {
                logger.LogError(e.Message, $"{nameof(AceptsEmptyDialog)}");
                File.AppendAllText(Path.Combine(basePath, "log.txt"), $"[{DateTime.Now}] Exception recording\n{e}\n");
            }
            finally
            {
                for (int i = 0; i < (kinectRecords?.Length ?? 0); i++)
                {
                    kinectRecords[i]?.Dispose();
                }

                this.recordingCancellationTokenSource?.Dispose();
                this.recordingCancellationTokenSource = null;

                await Task.Delay(this.configuration.GetValue("Kinect:Record:PresentationSavedMessageTime", TimeSpan.FromSeconds(3)));
                this.RecordDuration = TimeSpan.Zero;
                this.PresentationSavedIsShowing = false;
                this.IsRecording = false;
            }
        }

        public void Dispose()
        {
            this.disposed = true;
            this.recordingCancellationTokenSource?.Cancel();
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder.UWP.Controls/Controls/VideoButton.cs<|end_filename|>
﻿using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Speaker.Recorder.UWP.Controls
{
    public sealed class VideoButton : RadioButton
    {
        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
            nameof(Title),
            typeof(string), 
            typeof(VideoButton),
            new PropertyMetadata(null));

        public string Title
        {
            get { return (string)GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        public VideoButton()
        {
            this.DefaultStyleKey = typeof(VideoButton);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Program.cs<|end_filename|>
﻿using Microsoft.Toolkit.Win32.UI.XamlHost;
using Speaker.Recorder.Helpers;
using System;
using System.Diagnostics;

namespace Speaker.Recorder
{
    public class Program
    {
        [STAThread]
        public static void Main()
        {
            NvApiLoader.TryLoad();

            var splashBackground = new System.Windows.SplashScreen("Resources/Splash_Screen.png");
            splashBackground.Show(false, !Debugger.IsAttached);

            var uwpApp = new UWP.App();
            var application = new App();
            application.InitializeComponent();
            application.Loaded += (object sender, EventArgs e) =>
            {
                splashBackground.Close(TimeSpan.FromMilliseconds(300));
            };
            application.Run();
        }
    }
}


<|start_filename|>VirtualStage/KinectMaskGenerator/main.cpp<|end_filename|>
#pragma comment(lib, "k4a.lib")
#define IMG_OFFSET 0

#include <iostream>

#include <sys/types.h>
#include <sys/stat.h>

#include <k4a/k4a.h>
#include <k4arecord/playback.h>
#include <k4abt.h>

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/highgui.hpp>

#include <opencv2/imgproc.hpp>
#include <fstream>

using namespace cv;

struct stat info;

#define VERIFY(result, error)                                                                        \
  if (result != K4A_RESULT_SUCCEEDED)                                                                \
  {                                                                                                  \
    printf("%s \n - (File: %s, Function: %s, Line: %d)\n", error, __FILE__, __FUNCTION__, __LINE__); \
    exit(1);                                                                                         \
  }

#define WAIT_VERIFY(result, error)                                                                   \
  if (result != K4A_WAIT_RESULT_SUCCEEDED)                                                           \
  {                                                                                                  \
    printf("%s \n - (File: %s, Function: %s, Line: %d)\n", error, __FILE__, __FUNCTION__, __LINE__); \
    exit(1);                                                                                         \
  }

static cv::Mat color_to_opencv(const k4a_image_t &im);
static cv::Mat depth_to_opencv(const k4a_image_t &im);
static cv::Mat index_to_opencv(const k4a_image_t &im);

const uint64_t default_interval = 66666;
uint64_t last_duration;
uint64_t current_interval;

struct SyncronizationFileWriter
{
  std::ofstream timestampFile;

  void init(std::string outputFolder)
  {
    std::stringstream filename;
    filename << outputFolder << "/timestampfile.txt";
    timestampFile.open(filename.str().c_str());
  }

  void writeStamp(int frame_count)
  {
    std::stringstream ssaux;
    ssaux << std::setfill('0') << std::setw(4) << frame_count << "_out.png";
    timestampFile << "file " << ssaux.str() << std::endl;
    timestampFile << "duration " << current_interval / 1e6 << std::endl;
  }

  void close()
  {
    timestampFile.close();
  }
};

int main(int argc, char *argv[])
{
  if (argc < 5)
  {
    printf("USAGE: VIDEO_INPUT_PATH MASK_OUTPUT_PATH START DURATION [COLOR_IMG_SUFFIX]");
    exit(1);
  }

  char *INPUT = argv[1];
  char *OUTPUT = argv[2];

  uint64_t timestamp_start_us = (uint64_t)std::stoi(argv[3]) * (uint64_t)1e6;
  printf("Starting at second %f\n", timestamp_start_us / 1e6);
  int duration = std::stoi(argv[4]);
  uint64_t timestamp_end_us = 0;
  if (duration > 0)
  {
    timestamp_end_us = (uint64_t)std::stoi(argv[4]) * (uint64_t)1e6 + timestamp_start_us;
    printf("Finishing at second %f\n", timestamp_end_us / 1e6);
  }

  bool write_color = false;
  char *color_img_suffix = "";
  if (argc >= 6)
  {
    write_color = true;
    color_img_suffix = argv[5];
  }

  SyncronizationFileWriter syncFileWriter;
  syncFileWriter.init(OUTPUT);

  k4a_playback_t playback_handle = NULL;
  VERIFY(k4a_playback_open(INPUT, &playback_handle), "Failed to open recording\n");

  uint64_t recording_length = k4a_playback_get_recording_length_usec(playback_handle);
  printf("Recording is %lld seconds long\n", recording_length / 1000000);

  k4a_calibration_t sensor_calibration;
  VERIFY(k4a_playback_get_calibration(playback_handle, &sensor_calibration),
         "Get camera calibration failed!");

  k4a_playback_set_color_conversion(playback_handle,
                                    K4A_IMAGE_FORMAT_COLOR_BGRA32);

  // Create the tracker
  k4abt_tracker_t tracker = NULL;
  k4abt_tracker_configuration_t tracker_config = K4ABT_TRACKER_CONFIG_DEFAULT;
  VERIFY(k4abt_tracker_create(&sensor_calibration, tracker_config, &tracker), "Body tracker initialization failed!");

  int frame_count = 0;
  char buffer[120];
  int color_image_width_pixels = sensor_calibration.color_camera_calibration.resolution_width;
  int color_image_height_pixels = sensor_calibration.color_camera_calibration.resolution_height;
  int depth_image_width_pixels = sensor_calibration.depth_camera_calibration.resolution_width;
  int depth_image_height_pixels = sensor_calibration.depth_camera_calibration.resolution_height;
  k4a_image_t mask_image = NULL;
  k4a_image_t transformed_mask_image = NULL;
  k4a_image_t transformed_depth_image = NULL;
  // transform depth camera into color camera geometry
  VERIFY(k4a_image_create(K4A_IMAGE_FORMAT_CUSTOM8,
                          color_image_width_pixels,
                          color_image_height_pixels,
                          color_image_width_pixels * (int)sizeof(uint8_t),
                          &transformed_mask_image),
         "Could not create image for mask transformation!");

  VERIFY(k4a_image_create(K4A_IMAGE_FORMAT_DEPTH16,
                          color_image_width_pixels,
                          color_image_height_pixels,
                          color_image_width_pixels * (int)sizeof(uint16_t),
                          &transformed_depth_image),
         "Could not create image for depth transformation!");
  k4a_transformation_t transformation = k4a_transformation_create(&sensor_calibration);
  Mat last_mask = cv::Mat::zeros(cv::Size(1920, 1080), CV_8U);
  bool writing = false;
  uint64_t initial_duration = 0;

  while (true)
  {
    frame_count++;

    // Get a capture
    k4a_capture_t sensor_capture;
    k4a_stream_result_t get_capture_result = k4a_playback_get_next_capture(playback_handle, &sensor_capture);
    if (get_capture_result == K4A_STREAM_RESULT_EOF)
    {
      break;
    }
    else if (get_capture_result != K4A_STREAM_RESULT_SUCCEEDED)
    {
      if (writing)
      {
        // If the frame is corrupt, we write last interval timestamp and last mask
        printf("Warning: Get capture returned error %d on frame %d\n", get_capture_result, frame_count);
        snprintf(buffer, 128, "%s/%04d_masksAK.png", OUTPUT, frame_count);
        cv::imwrite(buffer, last_mask);
        syncFileWriter.writeStamp(frame_count);
      }
      continue;
    }

    // if there is no color image, we just consider that frame didn't exist
    k4a_image_t color_image = k4a_capture_get_color_image(sensor_capture);
    if (color_image == 0)
    {
      printf("Warning: No color image %d on frame %d, dropped\n", get_capture_result, frame_count);
      frame_count--;
      k4a_capture_release(sensor_capture);
      continue;
    }

    // write color image
    if (writing && write_color)
    {
      Mat color = color_to_opencv(color_image);
      snprintf(buffer, 128, "%s/%04d_%s.png", OUTPUT, frame_count, color_img_suffix);
      cv::imwrite(buffer, color);
    }

    // get frame timestamp information
    uint64_t duration_us = k4a_image_get_device_timestamp_usec(color_image);
    k4a_image_release(color_image);
    if (initial_duration == 0)
    { // get the first duration to compare with the rest
      initial_duration = duration_us;
    }
    current_interval = duration_us - last_duration;
    last_duration = duration_us;
    // sometimes first timestamp is wrong, in that case we use default
    if (current_interval / 1e6 > 5)
    {
      current_interval = default_interval;
    }

    // check if we are in the interested piece of video
    if (!writing && (duration_us - initial_duration) >= timestamp_start_us)
    {
      writing = true; // start writing
      frame_count = 1;
    }
    else if (writing && duration > 0 && (duration_us - initial_duration) >= timestamp_end_us)
    {
      break; // done
    }

    // don't queue if we are to too far from the start
    if (!writing && (duration_us - initial_duration) < (timestamp_start_us - int(1e6)))
    {
      k4a_capture_release(sensor_capture);
      continue;
    }

    // queque capture
    k4a_wait_result_t queue_capture_result = k4abt_tracker_enqueue_capture(tracker, sensor_capture, K4A_WAIT_INFINITE);
    k4a_capture_release(sensor_capture);
    if (queue_capture_result == K4A_WAIT_RESULT_TIMEOUT)
    {
      // It should never hit timeout when K4A_WAIT_INFINITE is set.
      printf("Error! Add capture to tracker process queue timeout!\n");
      exit(1);
    }
    else if (queue_capture_result == K4A_WAIT_RESULT_FAILED)
    {
      if (writing)
      {
        // If for some reason (probably there is no depth information) we don't have a mask for
        //   this frame, we use the last one
        printf("Warning! Add capture to tracker process queue failed! Frame: %d\n", frame_count);

        snprintf(buffer, 128, "%s/%04d_masksAK.png", OUTPUT, frame_count);
        cv::imwrite(buffer, last_mask);
        syncFileWriter.writeStamp(frame_count);
      }
      continue;
    }

    // get capture for body tracker
    k4abt_frame_t body_frame = NULL;
    k4a_wait_result_t pop_frame_result = k4abt_tracker_pop_result(tracker, &body_frame, K4A_WAIT_INFINITE);
    if (pop_frame_result == K4A_WAIT_RESULT_TIMEOUT)
    {
      // It should never hit timeout when K4A_WAIT_INFINITE is set.
      printf("Error! Pop body frame result timeout!\n");
      exit(1);
    }
    else if (pop_frame_result != K4A_WAIT_RESULT_SUCCEEDED)
    {
      printf("Pop body frame result failed!\n");
      exit(1);
    }

    // go to next iteration if we are not writing
    if (!writing)
    {
      k4abt_frame_release(body_frame);
      continue;
    }

    // Process and write mask
    k4a_image_t body_index_map = k4abt_frame_get_body_index_map(body_frame);
    k4a_capture_t input_capture = k4abt_frame_get_capture(body_frame);
    k4a_image_t depth_image = k4a_capture_get_depth_image(input_capture);

    // transform body index into mask
    Mat depth_mask = index_to_opencv(body_index_map);
    cv::bitwise_not(depth_mask, depth_mask);

    VERIFY(k4a_image_create_from_buffer(
               K4A_IMAGE_FORMAT_CUSTOM8,
               depth_image_width_pixels,
               depth_image_height_pixels,
               depth_image_width_pixels * (int)sizeof(uint8_t),
               depth_mask.data,
               (size_t)(depth_mask.total() * depth_mask.elemSize()),
               NULL,
               NULL,
               &mask_image),
           "Could not create image from buffer!");

    VERIFY(k4a_transformation_depth_image_to_color_camera_custom(
               transformation,
               depth_image,
               mask_image,
               transformed_depth_image,
               transformed_mask_image,
               K4A_TRANSFORMATION_INTERPOLATION_TYPE_NEAREST,
               0),
           "Coud not transform depth image!");

    // Get mask, depth and color images
    Mat mask = index_to_opencv(transformed_mask_image);

    Mat rs_mask;
    cv::resize(mask, rs_mask, cv::Size(1920, 1080));
    snprintf(buffer, 128, "%s/%04d_masksAK.png", OUTPUT, frame_count);
    last_mask = rs_mask;
    cv::imwrite(buffer, rs_mask);
    syncFileWriter.writeStamp(frame_count);

    //imshow("BodyTracking", mask);
    //waitKey(1);

    k4a_image_release(mask_image);
    k4a_image_release(depth_image);
    k4a_image_release(body_index_map);
    k4a_capture_release(input_capture);

    k4abt_frame_release(body_frame);
  }

  printf("Finished body tracking processing!\n");

  // Clean up body tracker
  k4abt_tracker_shutdown(tracker);
  k4abt_tracker_destroy(tracker);

  k4a_playback_close(playback_handle);

  syncFileWriter.close();
  return 0;
}

static cv::Mat color_to_opencv(const k4a_image_t &im)
{
  cv::Mat cv_image_with_alpha(k4a_image_get_height_pixels(im), k4a_image_get_width_pixels(im), CV_8UC4, (void *)k4a_image_get_buffer(im));
  cv::Mat cv_image_no_alpha;
  cv::cvtColor(cv_image_with_alpha, cv_image_with_alpha, cv::COLOR_BGRA2BGR);
  return cv_image_with_alpha;
}

static cv::Mat depth_to_opencv(const k4a_image_t &im)
{
  return cv::Mat(k4a_image_get_height_pixels(im),
                 k4a_image_get_width_pixels(im),
                 CV_16U,
                 (void *)k4a_image_get_buffer(im),
                 static_cast<size_t>(k4a_image_get_stride_bytes(im)));
}

static cv::Mat index_to_opencv(const k4a_image_t &im)
{
  return cv::Mat(k4a_image_get_height_pixels(im),
                 k4a_image_get_width_pixels(im),
                 CV_8U,
                 (void *)k4a_image_get_buffer(im),
                 static_cast<size_t>(k4a_image_get_stride_bytes(im)));
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Controls/GraphicsItemD3DImage.cs<|end_filename|>
﻿using SharpDX.Direct3D11;
using SharpDX.Direct3D9;
using SharpDX.DXGI;
using Speaker.Recorder.Capture;
using System;
using System.Windows;
using System.Windows.Interop;
using Windows.Graphics;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Graphics.DirectX.Direct3D11;

namespace Speaker.Recorder.Controls
{
    public class GraphicsItemD3DImage : D3DImage
    {
        private GraphicsCaptureItem item;
        private Texture2DDescription renderTargetDescription;
        private Direct3D11CaptureFramePool framePool;
        private GraphicsCaptureSession session;
        private SizeInt32 lastSize;
        private IDirect3DDevice device;
        private Direct3DEx direct3d;
        private DeviceEx d9device;
        private SharpDX.Direct3D11.Device d3dDevice;
        private SharpDX.Direct3D11.Texture2D renderTarget;
        private bool backBufferSetted;
        private long fpsCount;

        public int FpsDivider { get; set; } = 1;

        public double RenderWidth { get; set; } = -1;

        public double RenderHeight { get; set; } = -1;

        public GraphicsItemD3DImage()
        {
            if (App.Current.MainWindow != null)
            {
                IntPtr window = new WindowInteropHelper(App.Current.MainWindow).Handle;
                var presentparams = new SharpDX.Direct3D9.PresentParameters
                {
                    Windowed = true,
                    SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
                    DeviceWindowHandle = window,
                    PresentationInterval = PresentInterval.Immediate,
                };

                const CreateFlags deviceFlags = CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve;

                this.direct3d = new Direct3DEx();
                this.device = Direct3D11Helper.CreateDevice();
                this.d3dDevice = Direct3D11Helper.CreateSharpDXDevice(this.device);
                this.d9device = new DeviceEx(this.direct3d, 0, DeviceType.Hardware, IntPtr.Zero, deviceFlags, presentparams);
                this.IsFrontBufferAvailableChanged += this.GraphicItemD3DImageIsFrontBufferAvailableChanged;
            }
        }

        ~GraphicsItemD3DImage()
        {
            this.ResetState(false);
            this.renderTarget?.Dispose();
            this.d3dDevice?.Dispose();
            this.device?.Dispose();
        }

        public void SetGraphicItem(GraphicsCaptureItem item)
        {
            this.ResetState();
            this.item = item;

            if (this.item != null)
            {
                this.renderTargetDescription = new Texture2DDescription
                {
                    CpuAccessFlags = CpuAccessFlags.None,
                    Width = item.Size.Width,
                    Height = item.Size.Height,
                    Usage = ResourceUsage.Default,
                    Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
                    ArraySize = 1,
                    BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                    OptionFlags = ResourceOptionFlags.Shared,
                    MipLevels = 1,
                    SampleDescription = new SampleDescription(1, 0),
                };
                this.renderTarget = new Texture2D(d3dDevice, this.renderTargetDescription);

                framePool = Direct3D11CaptureFramePool.Create(device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, this.item.Size);
                session = framePool.CreateCaptureSession(this.item);
                lastSize = this.item.Size;

                framePool.FrameArrived += this.OnFrameArrived;
                session.StartCapture();
            }
        }

        private void ResetState(bool resetBackBuffer = true)
        {
            this.backBufferSetted = false;
            this.session?.Dispose();
            if (this.framePool != null)
            {
                this.framePool.FrameArrived -= this.OnFrameArrived;
                this.framePool.Dispose();
            }

            if (resetBackBuffer)
            {
                this.Lock();
                this.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero, true);
                this.Unlock();
            }
        }

        private void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
        {
            this.RequestRender();
        }

        private void GraphicItemD3DImageIsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            this.RequestRender();
        }

        public void RequestRender()
        {
            this.fpsCount++;
            using var frame = this.framePool.TryGetNextFrame();
            if (frame != null && this.IsFrontBufferAvailable && this.fpsCount % this.FpsDivider == 0)
            {
                if (this.TryLock(new Duration(TimeSpan.Zero)))
                {
                    var newSize = false;
                    if (frame.ContentSize.Width != lastSize.Width ||
                        frame.ContentSize.Height != lastSize.Height)
                    {
                        // The thing we have been capturing has changed size.
                        // We need to resize the swap chain first, then blit the pixels.
                        // After we do that, retire the frame and then recreate the frame pool.
                        newSize = true;
                        lastSize = frame.ContentSize;
                        this.renderTargetDescription.Width = lastSize.Width;
                        this.renderTargetDescription.Height = lastSize.Height;
                        this.renderTarget?.Dispose();
                        this.renderTarget = new Texture2D(d3dDevice, this.renderTargetDescription);
                        backBufferSetted = false;
                    }

                    using (var bitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface))
                    {
                        d3dDevice.ImmediateContext.CopyResource(bitmap, this.renderTarget);
                    }

                    if (newSize || !backBufferSetted)
                    {
                        using var resource = this.renderTarget.QueryInterface<SharpDX.DXGI.Resource>();
                        var handle = resource.SharedHandle;

                        using var texture = new Texture(
                            this.d9device,
                            this.renderTarget.Description.Width,
                            this.renderTarget.Description.Height,
                            1,
                            SharpDX.Direct3D9.Usage.RenderTarget,
                            SharpDX.Direct3D9.Format.A8R8G8B8,
                            Pool.Default,
                            ref handle);
                        using var surface = texture.GetSurfaceLevel(0);
                        this.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface.NativePointer, true);
                        backBufferSetted = true;
                    }

                    this.AddDirtyRect(new Int32Rect(0, 0, lastSize.Width, lastSize.Height));

                    if (newSize)
                    {
                        framePool.Recreate(device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, lastSize);
                    }

                }

                this.Unlock();
            }
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/ViewModels/Designer/DesignPlayerViewModel.cs<|end_filename|>
﻿namespace Speaker.Recorder.ViewModels
{
    public class DesignPlayerViewModel : PlayerViewModel
    {
        public DesignPlayerViewModel() : base(null, null,null)
        {
        }

        protected override void Initialize()
        {
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/KinectLoggingService.cs<|end_filename|>
﻿using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace Speaker.Recorder.Services
{
    public class KinectLoggingService : IHostedService
    {
        private readonly ILogger logger;

        public KinectLoggingService(ILogger<KinectLoggingService> logger)
        {
            this.logger = logger;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            Microsoft.Azure.Kinect.Sensor.Logger.LogMessage += Logger_LogMessage;
            Microsoft.Azure.Kinect.Sensor.Record.RecordLogger.LogMessage += RecordLogger_LogMessage;
            return Task.CompletedTask;
        }

        private void Logger_LogMessage(Microsoft.Azure.Kinect.Sensor.LogMessage obj)
        {
            this.logger.Log(this.GetLevel(obj.LogLevel), obj.Message);
        }

        private void RecordLogger_LogMessage(Microsoft.Azure.Kinect.Sensor.LogMessage obj)
        {
            this.logger.Log(this.GetLevel(obj.LogLevel), obj.Message);
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            Microsoft.Azure.Kinect.Sensor.Logger.LogMessage -= Logger_LogMessage;
            Microsoft.Azure.Kinect.Sensor.Record.RecordLogger.LogMessage -= RecordLogger_LogMessage;
            return Task.CompletedTask;
        }

        private Microsoft.Extensions.Logging.LogLevel GetLevel(Microsoft.Azure.Kinect.Sensor.LogLevel logLevel)
        {
            return logLevel switch
            {
                Microsoft.Azure.Kinect.Sensor.LogLevel.Critical => LogLevel.Critical,
                Microsoft.Azure.Kinect.Sensor.LogLevel.Error => LogLevel.Error,
                Microsoft.Azure.Kinect.Sensor.LogLevel.Warning => LogLevel.Warning,
                Microsoft.Azure.Kinect.Sensor.LogLevel.Information => LogLevel.Information,
                Microsoft.Azure.Kinect.Sensor.LogLevel.Trace => LogLevel.Trace,
                _ => LogLevel.None,
            };
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Kinect/KinectCaptureImages.cs<|end_filename|>
﻿namespace Speaker.Recorder.Kinect
{
    public enum KinectCaptureImage
    {
        Color,
        Depth,
        IR,
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Helpers/WindowHelper.cs<|end_filename|>
﻿using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace Speaker.Recorder.Helpers
{
    static class WindowHelper
    {
        enum GetAncestorFlags
        {
            // Retrieves the parent window. This does not include the owner, as it does with the GetParent function.
            GetParent = 1,
            // Retrieves the root window by walking the chain of parent windows.
            GetRoot = 2,
            // Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
            GetRootOwner = 3
        }

        public enum WindowMessages : uint
        {
            WM_KEYUP = 0x0101,
            WM_KEYDOWN = 0x0100,
            WM_SETFOCUS = 0x0007,
            WM_MBUTTONDBLCLK = 0x0209,
        }

        public enum GetWindowLongs
        {
            GWL_WNDPROC = (-4),
            GWL_HINSTANCE = (-6),
            GWL_HWNDPARENT = (-8),
            GWL_STYLE = (-16),
            GWL_EXSTYLE = (-20),
            GWL_USERDATA = (-21),
            GWL_ID = (-12)
        }

        enum SetWindowPositions : uint
        {
            SWP_ASYNCWINDOWPOS = 0x4000,
        }

        [Flags]
        public enum WindowStyles : uint
        {
            WS_BORDER = 0x800000,
            WS_CAPTION = 0xc00000,
            WS_CHILD = 0x40000000,
            WS_CLIPCHILDREN = 0x2000000,
            WS_CLIPSIBLINGS = 0x4000000,
            WS_DISABLED = 0x8000000,
            WS_DLGFRAME = 0x400000,
            WS_GROUP = 0x20000,
            WS_HSCROLL = 0x100000,
            WS_MAXIMIZE = 0x1000000,
            WS_MAXIMIZEBOX = 0x10000,
            WS_MINIMIZE = 0x20000000,
            WS_MINIMIZEBOX = 0x20000,
            WS_OVERLAPPED = 0x0,
            WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_SIZEFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
            WS_POPUP = 0x80000000u,
            WS_POPUPWINDOW = WS_POPUP | WS_BORDER | WS_SYSMENU,
            WS_SIZEFRAME = 0x40000,
            WS_SYSMENU = 0x80000,
            WS_TABSTOP = 0x10000,
            WS_VISIBLE = 0x10000000,
            WS_VSCROLL = 0x200000
        }

        enum DWMWINDOWATTRIBUTE : uint
        {
            NCRenderingEnabled = 1,
            NCRenderingPolicy,
            TransitionsForceDisabled,
            AllowNCPaint,
            CaptionButtonBounds,
            NonClientRtlLayout,
            ForceIconicRepresentation,
            Flip3DPolicy,
            ExtendedFrameBounds,
            HasIconicBitmap,
            DisallowPeek,
            ExcludedFromPeek,
            Cloak,
            Cloaked,
            FreezeRepresentation
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;        // x position of upper-left corner
            public int Top;         // y position of upper-left corner
            public int Right;       // x position of lower-right corner
            public int Bottom;      // y position of lower-right corner
        }

        public enum WS_EX
        {
            None = 0,
            WS_EX_LAYERED = 0x80000,
            WS_EX_TRANSPARENT = 0x00000020,
        }

        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("user32")]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPositions uFlags);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, WindowMessages Msg, IntPtr wParam, UIntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr PostMessage(IntPtr hWnd, WindowMessages Msg, IntPtr wParam, UIntPtr lParam);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        static extern IntPtr GetShellWindow();

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("user32.dll", ExactSpelling = true)]
        static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);

        [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
        static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
        static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

        // This static method is required because Win32 does not support
        // GetWindowLongPtr directly.
        // http://pinvoke.net/default.aspx/user32/GetWindowLong.html
        static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
        {
            if (IntPtr.Size == 8)
                return GetWindowLongPtr64(hWnd, nIndex);
            else
                return GetWindowLongPtr32(hWnd, nIndex);
        }

        [DllImport("dwmapi.dll")]
        static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE dwAttribute, out bool pvAttribute, int cbAttribute);

        public static bool IsWindowValidForCapture(IntPtr hwnd)
        {
            if (hwnd.ToInt32() == 0)
            {
                return false;
            }

            if (hwnd == GetShellWindow())
            {
                return false;
            }

            if (!IsWindowVisible(hwnd))
            {
                return false;
            }

            if (GetAncestor(hwnd, GetAncestorFlags.GetRoot) != hwnd)
            {
                return false;
            }

            var style = (WindowStyles)(ulong)GetWindowLongPtr(hwnd, (int)GetWindowLongs.GWL_STYLE);
            if (style.HasFlag(WindowStyles.WS_DISABLED))
            {
                return false;
            }

            var hrTemp = DwmGetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.Cloaked, out var cloaked, Marshal.SizeOf<bool>());
            if (hrTemp == 0 && cloaked)
            {
                return false;
            }

            return true;
        }

        public static Size GetWindowSize(IntPtr hwnd)
        {
            GetWindowRect(hwnd, out RECT rc);
            var width = rc.Right - rc.Left;
            var height = rc.Bottom - rc.Top;
            return new Size(width, height);
        }

        internal static void SetWindowPosition(IntPtr hwnd, Rect rectangle)
        {
            SetWindowPos(hwnd, IntPtr.Zero, (int)rectangle.X - 1, (int)rectangle.Y - 1, (int)rectangle.Width, (int)rectangle.Height, SetWindowPositions.SWP_ASYNCWINDOWPOS);
        }

        internal static void SendKey(IntPtr hwnd, IntPtr key)
        {
            PostMessage(hwnd, WindowMessages.WM_SETFOCUS, IntPtr.Zero, UIntPtr.Zero);
            PostMessage(hwnd, WindowMessages.WM_KEYDOWN, key, UIntPtr.Zero);
            PostMessage(hwnd, WindowMessages.WM_KEYUP, key, UIntPtr.Zero);
            PostMessage(hwnd, WindowMessages.WM_MBUTTONDBLCLK, IntPtr.Zero, UIntPtr.Zero);
        }
    }
}


<|start_filename|>VirtualStage/Speaker.Recorder/Speaker.Recorder/Services/IdentificationService/DesktopNameIdentificationService.cs<|end_filename|>
﻿using System;
using System.Management;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;

namespace Speaker.Recorder.Services.IdentificationService
{
    public class DesktopNameIdentificationService : BaseIdentificationService
    {
        public static string RawIdentifier;

        public override string GetRawIdentifier()
        {
            if (RawIdentifier == null)
            {
                RawIdentifier = $"{Environment.MachineName}-{GetMachineIdentifier()}";
            }

            return RawIdentifier;
        }

        public static string GetMachineIdentifier()
        {
            try
            {
                string uuid = string.Empty;

                using ManagementClass mc = new ManagementClass("Win32_ComputerSystemProduct");
                ManagementObjectCollection moc = mc.GetInstances();

                foreach (ManagementObject mo in moc)
                {
                    uuid = mo.Properties["UUID"].Value.ToString();
                    break;
                }

                return uuid;
            }
            catch { }
            try
            {
                foreach (NetworkInterface nic in NetworkInterface.GetAllNetworkInterfaces())
                {
                    // Only consider Ethernet network interfaces
                    if (nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet &&
                        nic.OperationalStatus == OperationalStatus.Up)
                    {
                        return nic.GetPhysicalAddress().ToString();
                    }
                }
            }
            catch { }
            return string.Empty;
        }
    }
}
