Seeking Support in Encrypted Video Streaming with WPF and LibVLC

Kamyab Faghih 70 Reputation points
2025-04-13T05:30:34.83+00:00

Hello, I've developed a video player project using C#, WPF, and .NET 5. In this application, encrypted MKV videos (encrypted with AES) are first decoded as offline streams, then buffered in memory and finally sent to LibVLC for playback.

The issue I'm facing is this: after the video starts playing, if I change the video timeline using the slider and the targeted segment hasn't been decoded and buffered yet, playback stops entirely.

What I want instead is for the buffering to restart from the new slider position and for the video to continue playing from that point.

I'd really appreciate it if you could guide me on how to handle this. Thanks in advance!

using System;
using System.IO;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using LibVLCSharp.Shared.MediaPlayerElement;
using Microsoft.Win32;
using LibVLCSharp.WPF;
using LibVLCSharp.Shared;
using System.Security.Cryptography.Xml;
using System.Windows.Controls;
using System.Text;
using System.Windows.Input;
using System.Windows.Threading;
namespace NiavashVideoPlayer
{
    /// <summary>
    /// Interaction logic for Player.xaml
    /// </summary>
    public partial class Player : Window
    {
        private string _encryptedFile = "";
        private StreamWriter sw = new StreamWriter("vlc_log.txt", true);
        private byte[] _key = Encoding.UTF8.GetBytes("my32bytekey123456789012345678901");
        private byte[] _iv = Encoding.UTF8.GetBytes("my32bytekey12345");
        private LibVLC _libVLC;
        private MediaPlayer _mediaPlayer;
        private const int BufferSize = 256 * 1024; // 256 KB
        private const long MaxBufferBytes = 50 * 1024 * 1024; // 50 MB حداکثر بافر
        private long encryptedFileLength = 0;
        private long mediaDuration = 0;
        private bool isPlaying = false;
        private bool isUserDragging = false;
        private MemoryStream decryptedStream;
        private CancellationTokenSource cancellationTokenSource;
        private long currentReadPosition = 0;
        private long seekRequestedPosition = -1;
        private bool isSeeking = false;
        private bool isSeekingInProgress = false;
        public Player()
        {
            InitializeComponent();
            Core.Initialize();
            _libVLC = new LibVLC("--verbose=2", "--file-caching=5000");
            _libVLC.Log += LibVLC_Log;
            _mediaPlayer = new MediaPlayer(_libVLC);
            videoView.MediaPlayer = _mediaPlayer;
        }
        private void LibVLC_Log(object? sender, LogEventArgs e)
        {
            string logLine = $"[{e.Level}] {e.Module}: {e.Message}";
            Console.WriteLine(logLine);
            sw.WriteLine(logLine + Environment.NewLine);
        }
        private static void IncrementCounter(byte[] counter)
        {
            for (int i = counter.Length - 1; i >= 0; i--)
            {
                if (++counter[i] != 0)
                    break;
            }
        }
        private async void PlayDecryptedVideo(long startTime = 0)
        {
            if (isPlaying) return;
            isPlaying = true;
            // دیکود اولیه برای متادیتا
            using var tempStream = new MemoryStream();
            using var fileStream = new FileStream(_encryptedFile, FileMode.Open, FileAccess.Read);
            encryptedFileLength = fileStream.Length;
            byte[] buffer = new byte[BufferSize];
            byte[] decryptedBuffer = new byte[BufferSize];
            byte[] counter = (byte[])_iv.Clone();
            using var aes = Aes.Create();
            aes.Key = _key;
            aes.IV = _iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            using var encryptor = aes.CreateEncryptor();
            int bytesRead = await fileStream.ReadAsync(buffer, 0, Math.Min(BufferSize, (int)encryptedFileLength));
            for (int offset = 0; offset < bytesRead; offset += 16)
            {
                byte[] encryptedCounter = encryptor.TransformFinalBlock(counter, 0, counter.Length);
                int blockSize = Math.Min(16, bytesRead - offset);
                for (int i = 0; i < blockSize; i++)
                {
                    decryptedBuffer[offset + i] = (byte)(buffer[offset + i] ^ encryptedCounter[i]);
                }
                IncrementCounter(counter);
            }
            await tempStream.WriteAsync(decryptedBuffer, 0, bytesRead);
            var tempMedia = new Media(_libVLC, new StreamMediaInput(tempStream));
            await tempMedia.Parse(MediaParseOptions.ParseLocal);
            mediaDuration = tempMedia.Duration > 0 ? tempMedia.Duration : (long)(encryptedFileLength * 1000 / (1920 * 800 * 1.5)); // تخمین مدت زمان
            Dispatcher.Invoke(() =>
            {
                timelineSlider.Maximum = mediaDuration / 1000;
                timelineSlider.Value = 0; // شروع از صفر
                timelineSlider.IsEnabled = true;
            });
            // استریم اصلی
            currentReadPosition = 0; // از اول شروع می‌کنیم
            cancellationTokenSource = new CancellationTokenSource();
            decryptedStream = new MemoryStream();
            _ = Task.Run(() => StreamDecryptedVideo(_encryptedFile, cancellationTokenSource.Token));
            await Task.Delay(2000); // تأخیر ثابت برای بافرینگ
            var streamMediaInput = new StreamMediaInput(decryptedStream);
            var media = new Media(_libVLC, streamMediaInput);
            _mediaPlayer.Media = media;
            _mediaPlayer.Play();
            _mediaPlayer.LengthChanged += (s, e) =>
            {
                if (_mediaPlayer.Length > 0)
                {
                    mediaDuration = _mediaPlayer.Length;
                    Dispatcher.Invoke(() => timelineSlider.Maximum = mediaDuration / 1000);
                }
            };
            _mediaPlayer.TimeChanged += (s, e) =>
            {
                Dispatcher.Invoke(() =>
                {
                    if (!isUserDragging && !isSeekingInProgress && _mediaPlayer.Time >= 0 && _mediaPlayer.Time <= mediaDuration)
                    {
                        timelineSlider.Value = _mediaPlayer.Time / 1000;
                    }
                });
            };
        }
        private async Task StreamDecryptedVideo(string filePath, CancellationToken cancellationToken)
        {
            decryptedStream.SetLength(Math.Min(MaxBufferBytes, encryptedFileLength));
            decryptedStream.Position = 0;
            decryptedStream.SetLength(Math.Min(MaxBufferBytes, encryptedFileLength));
            decryptedStream.Position = 0;
            if (decryptedStream != null)
            {
                decryptedStream.Dispose();
            }
            decryptedStream = new MemoryStream();
            decryptedStream.SetLength(Math.Min(MaxBufferBytes, encryptedFileLength));
            using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, true);
            encryptedFileLength = fileStream.Length;
            byte[] buffer = new byte[BufferSize];
            byte[] decryptedBuffer = new byte[BufferSize];
            byte[] counter = (byte[])_iv.Clone();
            using var aes = Aes.Create();
            aes.Key = _key;
            aes.IV = _iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            using var encryptor = aes.CreateEncryptor();
            fileStream.Seek(currentReadPosition, SeekOrigin.Begin);
            long localPosition = currentReadPosition;
            while (!cancellationToken.IsCancellationRequested)
            {
                int bytesRead = await fileStream.ReadAsync(buffer, 0, Math.Min(BufferSize, (int)(encryptedFileLength - localPosition)), cancellationToken);
                if (bytesRead <= 0) break;
                for (int offset = 0; offset < bytesRead; offset += 16)
                {
                    byte[] encryptedCounter = encryptor.TransformFinalBlock(counter, 0, counter.Length);
                    int blockSize = Math.Min(16, bytesRead - offset);
                    for (int i = 0; i < blockSize; i++)
                    {
                        decryptedBuffer[offset + i] = (byte)(buffer[offset + i] ^ encryptedCounter[i]);
                    }
                    IncrementCounter(counter);
                }
                if (decryptedStream.Position + bytesRead > decryptedStream.Length)
                {
                    byte[] currentData = decryptedStream.ToArray();
                    int trimSize = (int)(decryptedStream.Position + bytesRead - decryptedStream.Length);
                    Array.Copy(currentData, trimSize, currentData, 0, currentData.Length - trimSize);
                    decryptedStream.SetLength(Math.Min(MaxBufferBytes, encryptedFileLength));
                    decryptedStream.Position = decryptedStream.Length - bytesRead;
                }
                await decryptedStream.WriteAsync(decryptedBuffer, 0, bytesRead, cancellationToken);
                localPosition += bytesRead;
                if (seekRequestedPosition >= 0 && !isSeeking)
                {
                    isSeeking = true;
                    long newPos = seekRequestedPosition;
                    seekRequestedPosition = -1;
                    currentReadPosition = newPos;
                    decryptedStream.SetLength(0);
                    decryptedStream.Position = 0;
                    _mediaPlayer.Stop();
                    cancellationTokenSource?.Cancel();
                    cancellationTokenSource = new CancellationTokenSource();
                    _ = Task.Run(() => StreamDecryptedVideo(filePath, cancellationTokenSource.Token));
                    await Task.Delay(1000);
                    long targetTime = (long)(newPos * mediaDuration / encryptedFileLength);
                    targetTime = Math.Min(targetTime, mediaDuration);
                    var newStreamMediaInput = new StreamMediaInput(decryptedStream);
                    var newMedia = new Media(_libVLC, newStreamMediaInput);
                    _mediaPlayer.Media = newMedia;
                    _mediaPlayer.Play();
                    _mediaPlayer.Time = targetTime;
                    Dispatcher.Invoke(() => timelineSlider.Value = targetTime / 1000);
                    isSeeking = false;
                    break;
                }
            }
        }
        private async Task SeekAndPlay(long targetTime)
        {
            if (!isPlaying)
                return;
            // اطمینان از معتبر بودن targetTime
            targetTime = Math.Max(0, Math.Min(targetTime, mediaDuration));
            // مستقیماً موقعیت پخش را به روز رسانی می‌کنیم
            _mediaPlayer.Time = targetTime;
            // به‌روزرسانی اسلایدر (اختیاری)
            Dispatcher.Invoke(() => timelineSlider.Value = targetTime / 1000);
            Dispatcher.Invoke(DispatcherPriority.Background, new Action(async () =>
            {
                while (decryptedStream.Position < 500 * 1024)
                {
                    await Task.Delay(1000);
                }
                _mediaPlayer.Play();
            }));
        }
        private async void TimelineSlider_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!isPlaying) return;
            long targetTime = (long)(timelineSlider.Value * 1000);
            await SeekAndPlay(targetTime);
            isUserDragging = false;
        }
        private void TimelineSlider_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            isUserDragging = true;
            _mediaPlayer.Pause();
        }
        private void TimelineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (!isPlaying || !isUserDragging) return;
            long targetTime = (long)(timelineSlider.Value * 1000);
            // نمایش زمان پیش‌نمایش (اختیاری)
        }
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
        }
        private void Window_Closed(object sender, EventArgs e)
        {
            //_mediaPlayer?.Dispose();
            //_libVLC?.Dispose();
            //decryptedStream?.Dispose();
            //cancellationTokenSource?.Cancel();
            _mediaPlayer?.Stop();
            isPlaying = false;
            cancellationTokenSource?.Cancel();
            sw.WriteLine("End");
            sw.Close();
        }
        private void PlayButton_Click(object sender, RoutedEventArgs e)
        {
            _mediaPlayer?.Play();
        }
        private void PauseButton_Click(object sender, RoutedEventArgs e)
        {
            _mediaPlayer?.Pause();
        }
        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            _mediaPlayer?.Stop();
            isPlaying = false;
            cancellationTokenSource?.Cancel();
        }
        private void FullscreenButton_Click(object sender, RoutedEventArgs e)
        {
            _mediaPlayer?.ToggleFullscreen();
        }
        private async void OpenButton_Click(object sender, RoutedEventArgs e)
        {
            var openFileDialog = new OpenFileDialog
            {
                Filter = "Video Files|*.mp4;*.avi;*.mov;*.mkv;*.wmv|All Files|*.*",
                Title = "Select a Video File"
            };
            if (openFileDialog.ShowDialog() == true)
            {
                _encryptedFile = openFileDialog.FileName;
                await Task.Run(() => PlayDecryptedVideo());
            }
        }
    }
}

Developer technologies | .NET | .NET Runtime
0 comments No comments
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.