<commit_before>﻿using System;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TweetDuck.Video.Controls;
using TweetLib.Communication;
using WMPLib;

namespace TweetDuck.Video{
    sealed partial class FormPlayer : Form{
        protected override bool ShowWithoutActivation => true;

        private readonly IntPtr ownerHandle;
        private readonly string videoUrl;
        private readonly DuplexPipe pipe;
        
        private readonly ControlWMP player;
        private bool wasCursorInside;
        private bool isPaused;
        private bool isDragging;

        private WindowsMediaPlayer Player => player.Ocx;

        public FormPlayer(IntPtr handle, int volume, string url, string token){
            InitializeComponent();

            this.ownerHandle = handle;
            this.videoUrl = url;
            this.pipe = DuplexPipe.CreateClient(token);
            this.pipe.DataIn += pipe_DataIn;
            
            player = new ControlWMP{
                Dock = DockStyle.Fill
            };

            player.BeginInit();
            Controls.Add(player);
            player.EndInit();

            Player.enableContextMenu = false;
            Player.uiMode = "none";
            Player.settings.autoStart = false;
            Player.settings.enableErrorDialogs = false;
            Player.settings.setMode("loop", true);

            Player.PlayStateChange += player_PlayStateChange;
            Player.MediaError += player_MediaError;
            
            trackBarVolume.Value = volume; // changes player volume too if non-default

            labelTooltip.AttachTooltip(progressSeek, true, args => {
                IWMPMedia media = Player.currentMedia;
                int progress = (int)(media.duration*progressSeek.GetProgress(args.X));

                Marshal.ReleaseComObject(media);

                return $"{(progress/60).ToString("00")}:{(progress%60).ToString("00")}";
            });
            
            labelTooltip.AttachTooltip(trackBarVolume, false, args => $"Volume : {trackBarVolume.Value}%");

            labelTooltip.AttachTooltip(imageClose, false, "Close");
            labelTooltip.AttachTooltip(imageDownload, false, "Download");
            labelTooltip.AttachTooltip(imageResize, false, "Fullscreen");

            Application.AddMessageFilter(new MessageFilter(this));
        }

        // Events

        private void FormPlayer_Load(object sender, EventArgs e){
            Player.URL = videoUrl;
        }

        private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
            switch(e.Key){
                case "key":
                    HandleKey((Keys)int.Parse(e.Data, NumberStyles.Integer));
                    break;

                case "die":
                    StopVideo();
                    break;
            }
        }

        private void player_PlayStateChange(int newState){
            WMPPlayState state = (WMPPlayState)newState;

            if (state == WMPPlayState.wmppsReady){
                Player.controls.play();
            }
            else if (state == WMPPlayState.wmppsPlaying){
                Player.PlayStateChange -= player_PlayStateChange;
                
                timerSync.Start();
                NativeMethods.SetWindowOwner(Handle, ownerHandle);
                Cursor.Current = Cursors.Default;
            }
        }

        private void player_MediaError(object pMediaObject){
            Console.Out.WriteLine(((IWMPMedia2)pMediaObject).Error.errorDescription);

            Marshal.ReleaseComObject(pMediaObject);
            Environment.Exit(Program.CODE_MEDIA_ERROR);
        }

        private void timerSync_Tick(object sender, EventArgs e){
            if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
                int width = rect.Right-rect.Left+1;
                int height = rect.Bottom-rect.Top+1;

                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

                bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));

                ClientSize = new Size(Math.Max(MinimumSize.Width, Math.Min(media.imageSourceWidth, width*3/4)), Math.Max(MinimumSize.Height, Math.Min(media.imageSourceHeight, height*3/4)));
                Location = new Point(rect.Left+(width-ClientSize.Width)/2, rect.Top+(height-ClientSize.Height+SystemInformation.CaptionHeight)/2);

                tablePanel.Visible = isCursorInside || isDragging;

                if (tablePanel.Visible){
                    labelTime.Text = $"{controls.currentPositionString} / {media.durationString}";

                    int value = (int)Math.Round(progressSeek.Maximum*controls.currentPosition/media.duration);

                    if (value >= progressSeek.Maximum){
                        progressSeek.Value = progressSeek.Maximum;
                        progressSeek.Value = progressSeek.Maximum-1;
                        progressSeek.Value = progressSeek.Maximum;
                    }
                    else{
                        progressSeek.Value = value+1;
                        progressSeek.Value = value;
                    }
                }

                if (controls.currentPosition > media.duration){ // pausing near the end of the video causes WMP to play beyond the end of the video wtf
                    controls.stop();
                    controls.currentPosition = 0;
                    controls.play();
                }
                
                if (isCursorInside && !wasCursorInside){
                    wasCursorInside = true;
                }
                else if (!isCursorInside && wasCursorInside){
                    wasCursorInside = false;

                    if (!Player.fullScreen && Handle == NativeMethods.GetForegroundWindow()){
                        NativeMethods.SetForegroundWindow(ownerHandle);
                    }
                }

                Marshal.ReleaseComObject(media);
                Marshal.ReleaseComObject(controls);
            }
            else{
                Environment.Exit(Program.CODE_OWNER_GONE);
            }
        }

        private void timerData_Tick(object sender, EventArgs e){
            timerData.Stop();
            pipe.Write("vol", trackBarVolume.Value.ToString());
        }

        private void progressSeek_MouseDown(object sender, MouseEventArgs e){
            if (e.Button == MouseButtons.Left){
                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;
<commit_msg>"Fix" an uncommon video player crash when closing short videos<commit_after>﻿using System;
using System.Drawing;
using System.Globalization;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using TweetDuck.Video.Controls;
using TweetLib.Communication;
using WMPLib;

namespace TweetDuck.Video{
    sealed partial class FormPlayer : Form{
        protected override bool ShowWithoutActivation => true;

        private readonly IntPtr ownerHandle;
        private readonly string videoUrl;
        private readonly DuplexPipe pipe;
        
        private readonly ControlWMP player;
        private bool wasCursorInside;
        private bool isPaused;
        private bool isDragging;

        private WindowsMediaPlayer Player => player.Ocx;

        public FormPlayer(IntPtr handle, int volume, string url, string token){
            InitializeComponent();

            this.ownerHandle = handle;
            this.videoUrl = url;
            this.pipe = DuplexPipe.CreateClient(token);
            this.pipe.DataIn += pipe_DataIn;
            
            player = new ControlWMP{
                Dock = DockStyle.Fill
            };

            player.BeginInit();
            Controls.Add(player);
            player.EndInit();

            Player.enableContextMenu = false;
            Player.uiMode = "none";
            Player.settings.autoStart = false;
            Player.settings.enableErrorDialogs = false;
            Player.settings.setMode("loop", true);

            Player.PlayStateChange += player_PlayStateChange;
            Player.MediaError += player_MediaError;
            
            trackBarVolume.Value = volume; // changes player volume too if non-default

            labelTooltip.AttachTooltip(progressSeek, true, args => {
                IWMPMedia media = Player.currentMedia;
                int progress = (int)(media.duration*progressSeek.GetProgress(args.X));

                Marshal.ReleaseComObject(media);

                return $"{(progress/60).ToString("00")}:{(progress%60).ToString("00")}";
            });
            
            labelTooltip.AttachTooltip(trackBarVolume, false, args => $"Volume : {trackBarVolume.Value}%");

            labelTooltip.AttachTooltip(imageClose, false, "Close");
            labelTooltip.AttachTooltip(imageDownload, false, "Download");
            labelTooltip.AttachTooltip(imageResize, false, "Fullscreen");

            Application.AddMessageFilter(new MessageFilter(this));
        }

        // Events

        private void FormPlayer_Load(object sender, EventArgs e){
            Player.URL = videoUrl;
        }

        private void pipe_DataIn(object sender, DuplexPipe.PipeReadEventArgs e){
            switch(e.Key){
                case "key":
                    HandleKey((Keys)int.Parse(e.Data, NumberStyles.Integer));
                    break;

                case "die":
                    StopVideo();
                    break;
            }
        }

        private void player_PlayStateChange(int newState){
            WMPPlayState state = (WMPPlayState)newState;

            if (state == WMPPlayState.wmppsReady){
                Player.controls.play();
            }
            else if (state == WMPPlayState.wmppsPlaying){
                Player.PlayStateChange -= player_PlayStateChange;
                
                timerSync.Start();
                NativeMethods.SetWindowOwner(Handle, ownerHandle);
                Cursor.Current = Cursors.Default;
            }
        }

        private void player_MediaError(object pMediaObject){
            Console.Out.WriteLine(((IWMPMedia2)pMediaObject).Error.errorDescription);

            Marshal.ReleaseComObject(pMediaObject);
            Environment.Exit(Program.CODE_MEDIA_ERROR);
        }

        [HandleProcessCorruptedStateExceptions]
        private void timerSync_Tick(object sender, EventArgs e){
            if (NativeMethods.GetWindowRect(ownerHandle, out NativeMethods.RECT rect)){
                int width = rect.Right-rect.Left+1;
                int height = rect.Bottom-rect.Top+1;

                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

                bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));

                ClientSize = new Size(Math.Max(MinimumSize.Width, Math.Min(media.imageSourceWidth, width*3/4)), Math.Max(MinimumSize.Height, Math.Min(media.imageSourceHeight, height*3/4)));
                Location = new Point(rect.Left+(width-ClientSize.Width)/2, rect.Top+(height-ClientSize.Height+SystemInformation.CaptionHeight)/2);

                tablePanel.Visible = isCursorInside || isDragging;

                if (tablePanel.Visible){
                    labelTime.Text = $"{controls.currentPositionString} / {media.durationString}";

                    int value = (int)Math.Round(progressSeek.Maximum*controls.currentPosition/media.duration);

                    if (value >= progressSeek.Maximum){
                        progressSeek.Value = progressSeek.Maximum;
                        progressSeek.Value = progressSeek.Maximum-1;
                        progressSeek.Value = progressSeek.Maximum;
                    }
                    else{
                        progressSeek.Value = value+1;
                        progressSeek.Value = value;
                    }
                }

                if (controls.currentPosition > media.duration){ // pausing near the end of the video causes WMP to play beyond the end of the video wtf
                    try{
                        controls.stop();
                        controls.currentPosition = 0;
                        controls.play();
                    }catch(AccessViolationException){
                        // something is super retarded here because shit gets disposed between the start of this method and
                        // the controls.play() call even though it runs on the UI thread
                    }
                }
                
                if (isCursorInside && !wasCursorInside){
                    wasCursorInside = true;
                }
                else if (!isCursorInside && wasCursorInside){
                    wasCursorInside = false;

                    if (!Player.fullScreen && Handle == NativeMethods.GetForegroundWindow()){
                        NativeMethods.SetForegroundWindow(ownerHandle);
                    }
                }

                Marshal.ReleaseComObject(media);
                Marshal.ReleaseComObject(controls);
            }
            else{
                Environment.Exit(Program.CODE_OWNER_GONE);
            }
        }

        private void timerData_Tick(object sender, EventArgs e){
            timerData.Stop();
            pipe.Write("vol", trackBarVolume.Value.ToString());
        }

        private void progressSeek_MouseDown(object sender, MouseEventArgs e){
            if (e.Button == MouseButtons.Left){
                IWMPMedia media = Player.currentMedia;
                IWMPControls controls = Player.controls;

{'fraction_non_alphanumeric': 0.08734003325862194, 'fraction_numerical': 0.0028920540814113224, 'mean_word_length': 1.7808604744672296, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 1, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1959160', 'n_tokens_mistral': 3476, 'n_tokens_neox': 3253, 'n_words': 679}