+++ /dev/null
-using System;\r
-using System.Drawing;\r
-using System.Runtime.InteropServices;\r
-using System.Diagnostics;\r
-using System.Windows.Forms;\r
-using System.Threading;\r
-\r
-using DirectShowLib;\r
-using DirectShowLib.Utils;\r
-using Microsoft.Win32.SafeHandles;\r
-using System.ComponentModel;\r
-using System.Drawing.Imaging;\r
-using System.Drawing.Drawing2D;\r
-using System.Collections.Generic;\r
-using Myriadbits.MXF;\r
-using MaestroShared.Metadata;\r
-using NLog;\r
-using DxPlay.Properties;\r
-\r
-namespace DxPlay {\r
-\r
- internal class DxPlayerx : ISampleGrabberCB, IDisposable {\r
- private static readonly Logger logger = LogManager.GetCurrentClassLogger();\r
-\r
- [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]\r
- private static extern void CopyMemory(IntPtr Destination, IntPtr Source, [MarshalAs(UnmanagedType.U4)] uint Length);\r
-\r
- private const int MEDIATIME_REFERENCE = 10000000;\r
-\r
- public enum GraphState {\r
- Stopped,\r
- Completed,\r
- Paused,\r
- Playing,\r
- Exiting\r
- }\r
-\r
- public Dictionary<GraphState, string> stateHunStringValues = new Dictionary<GraphState, string>();\r
-\r
- public MediaDescription MediaDescription { get; internal set; }\r
- public Timecode CurrentTC { get; internal set; }\r
- public GraphState State { get; internal set; }\r
-\r
- private IFilterGraph2 m_FilterGraph;\r
- private IMediaControl m_mediaCtrl;\r
- private IMediaEvent m_mediaEvent;\r
-\r
- // Event used by Media Event thread\r
- private ManualResetEvent m_mre;\r
- private BackgroundWorker tcWorker;\r
- // Current state of the graph (can change async)\r
-\r
- public event DxPlayEvent PlayEvent;\r
- public delegate void DxPlayEvent();\r
-\r
- private Thread m_eventThread = null;\r
- private IMediaSeeking m_mediaSeek = null;\r
-\r
- private IVideoWindow m_videoWindow = null;\r
- private IBaseFilter m_videoRenderer = null;\r
- private object tcLock = new object();\r
- Bitmap m_Bitmap = null;\r
- public bool IsError { get; set; }\r
-#if DEBUG\r
- // Allow you to "Connect to remote graph" from GraphEdit\r
- DsROTEntry m_DsRot;\r
-#endif\r
- private int m_stride;\r
-\r
- // Release everything.\r
- public void Dispose() {\r
- CloseInterfaces();\r
- }\r
-\r
- ~DxPlayerx() {\r
- CloseInterfaces();\r
- }\r
-\r
- Control playerWindow;\r
- private object ppUnk;\r
-\r
- // Play an avi file into a window. Allow for snapshots.\r
- // (Control to show video in, Avi file to play\r
- public DxPlayerx(Control hWin, ref MediaDescription mediaDesc) {\r
- FillTheHunStringvalues();\r
- State = GraphState.Stopped;\r
- try {\r
- int hr;\r
- IntPtr hEvent;\r
- MediaDescription = mediaDesc;\r
- // Set up the graph\r
- playerWindow = hWin;\r
-\r
- SetupGraph();\r
-\r
- hWin.Hide();\r
- hWin.Show();\r
-\r
- // Get the event handle the graph will use to signal\r
- // when events occur\r
- Debug.WriteLine("GetEventHandle");\r
- hr = m_mediaEvent.GetEventHandle(out hEvent);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- // Wrap the graph event with a ManualResetEvent\r
- m_mre = new ManualResetEvent(false);\r
- m_mre.SafeWaitHandle = new SafeWaitHandle(hEvent, true);\r
-\r
- // Create a new thread to wait for events\r
- Debug.WriteLine("m_eventThread.Start()");\r
- m_eventThread = new Thread(new ThreadStart(EventWait));\r
- m_eventThread.Name = "Media Event Thread";\r
- m_eventThread.Start();\r
-\r
- m_Bitmap = Properties.Resources.lgs;\r
-\r
- tcWorker = new BackgroundWorker();\r
- tcWorker.DoWork += TcWorker_DoWork;\r
- tcWorker.WorkerSupportsCancellation = true;\r
- tcWorker.RunWorkerAsync(tcLock);\r
-\r
- }\r
- catch {\r
- Dispose();\r
- throw;\r
- }\r
- }\r
-\r
- private void FillTheHunStringvalues() {\r
- stateHunStringValues.Add(GraphState.Exiting, Resources.EXITING);\r
- stateHunStringValues.Add(GraphState.Paused, Resources.PAUSED);\r
- stateHunStringValues.Add(GraphState.Playing, Resources.PLAYING);\r
- stateHunStringValues.Add(GraphState.Stopped, Resources.STOPPED);\r
- stateHunStringValues.Add(GraphState.Completed, Resources.COMPLETED);\r
- }\r
-\r
- private void TcWorker_DoWork(object sender, DoWorkEventArgs e) {\r
- while (!e.Cancel) {\r
- UpdateTC();\r
- Thread.Sleep(10);\r
- }\r
- }\r
-\r
-\r
- // start playing\r
- public void Play() {\r
- // If we aren't already playing (or shutting down)\r
- if (State == GraphState.Completed)\r
- Stop();\r
- if (State == GraphState.Stopped || State == GraphState.Paused) {\r
- int hr = m_mediaCtrl.Run();\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- State = GraphState.Playing;\r
- }\r
- }\r
-\r
- // Pause the capture graph.\r
- public void Pause() {\r
- // If we are playing\r
- if (State == GraphState.Playing) {\r
- int hr = m_mediaCtrl.Pause();\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- State = GraphState.Paused;\r
- Seek(CurrentTC.ZeroBasedFrames);\r
- }\r
- }\r
-\r
- // Pause the capture graph.\r
- public void Stop() {\r
- // Can only Stop when playing or paused\r
- if (State == GraphState.Playing || State == GraphState.Paused || State == GraphState.Completed) {\r
- int hr = m_mediaCtrl.Stop();\r
- DsError.ThrowExceptionForHR(hr);\r
- State = GraphState.Stopped;\r
- }\r
- Rewind();\r
- PlayEvent?.Invoke();\r
- }\r
-\r
- // Reset the clip back to the beginning\r
- public void Rewind() {\r
- Seek(0);\r
- }\r
-\r
- public void Seek(int value) {\r
- double frameLength = (double)MEDIATIME_REFERENCE / MediaDescription.FrameRate;\r
- long avgTimePerFrame = (long)Math.Ceiling(MEDIATIME_REFERENCE / MediaDescription.FrameRate);\r
- long requestedPosition = (long)Math.Ceiling(value * frameLength);\r
- int hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- long currentPosition;\r
- hr = m_mediaSeek.GetCurrentPosition(out currentPosition);\r
- DsError.ThrowExceptionForHR(hr);\r
- bool corrected = false;\r
- int reachedFrames = (int)Math.Abs((double)currentPosition / avgTimePerFrame);\r
- if (reachedFrames != value) {\r
- //NTSC-n nem megy a seek a kerekítési hibák miatt, mindíg ua. a frame jön ki \r
- requestedPosition += (int)frameLength / 2;\r
- hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);\r
- DsError.ThrowExceptionForHR(hr);\r
- corrected = true;\r
- }\r
-\r
- Debug.WriteLine("Seeking requested frame {0} got frame {1}, media position {2}, frame length {3}, corrected {4}", value, reachedFrames, requestedPosition, avgTimePerFrame, corrected);\r
- }\r
-\r
- private void UpdateTC() {\r
- if (m_mediaSeek == null)\r
- return;\r
- long currentPosition;\r
- int hr = m_mediaSeek.GetCurrentPosition(out currentPosition);\r
- DsError.ThrowExceptionForHR(hr);\r
- int frames = ReferenceTimeToFrames(currentPosition);\r
- if (CurrentTC.ZeroBasedFrames != frames) {\r
- CurrentTC.Set(frames);\r
- //Debug.WriteLine("Current frame is {0} ({1}), media position is {2}, AVG frame time is {3}", frames, CurrentTC.ToString(), currentPosition, AvgTimePerFrame);\r
- }\r
- PlayEvent?.Invoke();\r
-\r
- }\r
-\r
- private int ReferenceTimeToFrames(long refTime) {\r
- long AvgTimePerFrame = (long)Math.Ceiling(MEDIATIME_REFERENCE / MediaDescription.FrameRate);\r
- return (int)Math.Abs((double)refTime / AvgTimePerFrame);\r
- }\r
-\r
- private void SetupGraph() {\r
- int hr;\r
-\r
- try {\r
- IsError = false;\r
- m_FilterGraph = new FilterGraph() as IFilterGraph2;\r
-\r
- IGraphBuilder graphBuilder = m_FilterGraph as IGraphBuilder;\r
- m_mediaSeek = m_FilterGraph as IMediaSeeking;\r
- m_mediaEvent = m_FilterGraph as IMediaEvent;\r
- m_mediaCtrl = m_FilterGraph as IMediaControl;\r
- m_videoWindow = m_FilterGraph as IVideoWindow;\r
-\r
- logger.Debug("SetTimeCodes");\r
- SetTimeCodes();\r
-\r
-#if DEBUG\r
- m_DsRot = new DsROTEntry(m_FilterGraph);\r
-#endif\r
- logger.Debug("Add SourceFilter to graph");\r
- IBaseFilter sourceFilter = null;\r
- hr = m_FilterGraph.AddSourceFilter(MediaDescription.FileName, MediaDescription.FileName, out sourceFilter);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- //Type typeFromClsid = Type.GetTypeFromCLSID(new Guid("CCE7BD95-3BC4-4cfb-9664-0BF83201BE09"));\r
- //splitter = (IBaseFilter)Activator.CreateInstance(typeFromClsid);\r
- //m_FilterGraph.AddFilter(splitter, "MXF Splitter");\r
- //splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "Sony MXF Splitter");\r
-\r
- logger.Debug("Add LAVSplitter to graph");\r
- IBaseFilter splitter = LoadSplitter(graphBuilder);\r
- if (splitter == null)\r
- throw new Exception("No splitter!");\r
-\r
- logger.Debug("Connect SourceFilter -> LAVSplitter");\r
- FilterGraphTools.ConnectFilters(graphBuilder, sourceFilter, "Output", splitter, "Input", true);\r
-\r
- IAMStreamSelect amStreamSelect = (IAMStreamSelect)splitter;\r
- if (amStreamSelect != null) {\r
- int count = 0;\r
- amStreamSelect.Count(out count);\r
- int audioCount = 0;\r
- for (int i = 0; i < count; i++) {\r
- AMMediaType ppmt;\r
- AMStreamSelectInfoFlags pdwFlags;\r
- int plcid;\r
- int pdwGroup;\r
- string ppszName;\r
- object ppObject;\r
- amStreamSelect.Info(i, out ppmt, out pdwFlags, out plcid, out pdwGroup, out ppszName, out ppObject, out ppUnk);\r
-\r
- if (ppmt.majorType == MediaType.Audio) {\r
- Debug.WriteLine("Found audio channel");\r
- audioCount++;\r
- }\r
-\r
- DsUtils.FreeAMMediaType(ppmt);\r
- //Marshal.FreeCoTaskMem(ppszName);\r
- if (ppObject != null)\r
- DsUtils.ReleaseComObject(ppUnk);\r
-\r
- }\r
- Debug.WriteLine("Audio count: " + audioCount);\r
- }\r
-\r
- logger.Debug("Add LAVVideo to graph");\r
- IBaseFilter videoDecoder = LoadVideoDecoder(graphBuilder);\r
-\r
- if (videoDecoder == null)\r
- throw new Exception("No video decoder!");\r
-\r
- logger.Debug("Connect LAVSplitter -> LAVVideo");\r
- FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Video", videoDecoder, "Input", true);\r
-\r
- logger.Debug("Add SampleGrabber to graph");\r
- IBaseFilter sampGrabber = (IBaseFilter)new SampleGrabber();\r
- ISampleGrabber sampleGrabber = (ISampleGrabber)sampGrabber;\r
- ConfigureSampleGrabber(sampleGrabber);\r
-\r
- hr = m_FilterGraph.AddFilter(sampGrabber, "Sample Grabber");\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- logger.Debug("Connect LAVVideo -> SampleGrabber");\r
- FilterGraphTools.ConnectFilters(graphBuilder, videoDecoder, "Output", sampGrabber, "Input", true);\r
-\r
- AMMediaType media = new AMMediaType();\r
- sampleGrabber.GetConnectedMediaType(media);\r
- logger.Debug("SaveSizeInfo");\r
- SaveSizeInfo(media);\r
- DsUtils.FreeAMMediaType(media);\r
-\r
- logger.Debug("Add VideoMixingRenderer9 to graph");\r
- m_videoRenderer = (IBaseFilter)new VideoMixingRenderer9();\r
- hr = m_FilterGraph.AddFilter(m_videoRenderer, "Video Mixing Renderer 9");\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
-\r
- try {\r
- //IPin pin = DsFindPin.ByName(sampGrabber, "Output");\r
- //m_FilterGraph.RenderEx(pin, AMRenderExFlags.RenderToExistingRenderers, IntPtr.Zero);\r
- //Marshal.ReleaseComObject(pin);\r
- logger.Debug("Connect SampleGrabber -> VideoMixingRenderer9");\r
- FilterGraphTools.ConnectFilters(graphBuilder, sampGrabber, "Output", m_videoRenderer, "VMR Input0", true);\r
- }\r
- catch (Exception e) {\r
- logger.Error(e);\r
- }\r
-\r
- try {\r
- if (DsFindPin.ByName(splitter, "Audio") != null) {\r
- logger.Debug("Add LAVAudio to graph");\r
- IBaseFilter audioDecoder = null;\r
- audioDecoder = LoadAudioDecoder(graphBuilder);\r
- if (audioDecoder == null)\r
- throw new Exception("No audio decoder!");\r
-\r
- logger.Debug("Connect LAVSplitter -> LAVAudio");\r
- FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Audio", audioDecoder, "Input", true);\r
- FilterGraphTools.RenderPin(graphBuilder, audioDecoder, "Output");\r
- } else {\r
- logger.Warn("Audio pin not available");\r
- }\r
- }\r
- catch (Exception ex) {\r
- logger.Warn("Audio pin not available");\r
- }\r
-\r
-\r
- //logger.Debug("SaveSizeInfo");\r
- //SaveSizeInfo(sampGrabber as ISampleGrabber);\r
- logger.Debug("ConfigureVideoWindow");\r
- ConfigureVideoWindow();\r
-\r
- logger.Debug("Enable YADIF deinterlace");\r
- ILAVVideoSettings settings = (ILAVVideoSettings)videoDecoder;\r
- settings.SetSWDeintMode(LAVSWDeintModes.SWDeintMode_YADIF);\r
- settings.SetSWDeintOutput(LAVDeintOutput.DeintOutput_FramePer2Field);\r
- }\r
- catch (Exception e) {\r
- Debug.WriteLine(e.Message);\r
- IsError = true;\r
- }\r
- finally {\r
- }\r
-#if DEBUG\r
- // Double check to make sure we aren't releasing something\r
- // important.\r
- //GC.Collect();\r
- //GC.WaitForPendingFinalizers();\r
-#endif\r
- }\r
-\r
- private static IBaseFilter LoadVideoDecoder(IGraphBuilder graphBuilder) {\r
- IBaseFilter videoDecoder = null;\r
- ILAVVideoSettings lavVideoSettings;\r
- videoDecoder = FilterProvider.GetVideoFilter(out lavVideoSettings);\r
- if (videoDecoder == null)\r
- videoDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Video Decoder");\r
- else\r
- graphBuilder.AddFilter(videoDecoder, "LAV Video Decoder");\r
- return videoDecoder;\r
- }\r
-\r
- private static IBaseFilter LoadAudioDecoder(IGraphBuilder graphBuilder) {\r
- IBaseFilter audioDecoder = null;\r
- ILAVAudioSettings lavAudioSettings;\r
- ILAVAudioStatus lavAudioStatus;\r
- audioDecoder = FilterProvider.GetAudioFilter(out lavAudioSettings, out lavAudioStatus);\r
- if (audioDecoder == null)\r
- audioDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Audio Decoder");\r
- else\r
- graphBuilder.AddFilter(audioDecoder, "LAV Audio Decoder");\r
- return audioDecoder;\r
- }\r
-\r
- private static IBaseFilter LoadSplitter(IGraphBuilder graphBuilder) {\r
- IBaseFilter splitter = null;\r
- ILAVSplitterSettings lavSplitterSettings;\r
- splitter = FilterProvider.GetSplitter(out lavSplitterSettings);\r
- if (splitter == null)\r
- splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Splitter");\r
- else\r
- graphBuilder.AddFilter(splitter, "LAV Splitter");\r
- return splitter;\r
- }\r
-\r
- private void SetTimeCodes() {\r
- int hr;\r
- long duration;\r
- hr = m_mediaSeek.GetDuration(out duration);\r
- DsError.ThrowExceptionForHR(hr);\r
- MediaDescription.duration = new Timecode();\r
- MediaDescription.Duration.Set(ReferenceTimeToFrames(duration));\r
- if (MediaDescription.FirstFrame == null) {\r
- try {\r
- MXFFile mxf = new MXFFile(MediaDescription.FileName);\r
- mxf.Inspect();\r
- //MediaDescription.firstFrame = new Timecode(mxf.FirstSystemItem?.UserDateFullFrameNb, MediaDescription.FrameRate);\r
- MediaDescription.firstFrame = new Timecode((int)mxf.TimecodeComponent.StartTimecode, (float)mxf.TimecodeComponent.RoundedTimecodeBase);\r
- }\r
- catch (Exception ex) {\r
- MediaDescription.firstFrame = new Timecode();\r
- }\r
- }\r
- //MediaDescription.firstFrame = new Timecode();\r
- CurrentTC = new Timecode(MediaDescription.FirstFrame);\r
- }\r
-\r
-\r
- // Configure the video window\r
- private void ConfigureVideoWindow() {\r
- int hr;\r
-\r
- // Set the output window\r
- hr = m_videoWindow.put_Owner(playerWindow.Handle);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- hr = m_videoWindow.put_MessageDrain(playerWindow.Handle);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- // Set the window style\r
- hr = m_videoWindow.put_WindowStyle((WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings));\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- // Make the window visible\r
- hr = m_videoWindow.put_Visible(OABool.True);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- UpdateVideoWindow();\r
- }\r
-\r
- public void UpdateVideoWindow() {\r
- Size resolution = MediaDescription.Resolution;\r
- if (resolution.Width == 0 || resolution.Height == 0)\r
- return;\r
- int hr;\r
- // Position the playing location\r
- Rectangle rc = playerWindow.ClientRectangle;\r
- double x = (double)resolution.Width / resolution.Height;\r
- double y = (double)rc.Right / rc.Bottom;\r
- int playerWidth = 0;\r
- int playerHeight = 0;\r
- if (x - y < 0) {\r
- playerWidth = (int)Math.Ceiling(rc.Bottom * x);\r
- playerHeight = rc.Bottom;\r
- } else {\r
- x = (double)resolution.Height / resolution.Width;\r
- playerWidth = rc.Right;\r
- playerHeight = (int)Math.Ceiling(rc.Right * x); ;\r
- }\r
-\r
- hr = m_videoWindow.SetWindowPosition((rc.Right - playerWidth) / 2, (rc.Bottom - playerHeight) / 2, playerWidth, playerHeight);\r
- DsError.ThrowExceptionForHR(hr);\r
- }\r
-\r
- public void ToggleFullscreen() {\r
- m_videoWindow.put_FullScreenMode(IsFullscreen() ? OABool.False : OABool.True);\r
- }\r
-\r
- public bool IsFullscreen() {\r
- OABool isFullscreen;\r
- int hr = m_videoWindow.get_FullScreenMode(out isFullscreen);\r
- DsError.ThrowExceptionForHR(hr);\r
- return isFullscreen == OABool.True ? true : false;\r
- }\r
-\r
- // Set the options on the sample grabber\r
- private void ConfigureSampleGrabber(ISampleGrabber sampGrabber) {\r
- int hr;\r
- //AMMediaType media;\r
- //media = new AMMediaType();\r
- //media.majorType = MediaType.Video;\r
- //media.subType = MediaSubType.RGB24;\r
- //media.formatType = FormatType.VideoInfo;\r
- //hr = sampGrabber.SetMediaType(media);\r
- //DsError.ThrowExceptionForHR(hr);\r
- //DsUtils.FreeAMMediaType(media);\r
-\r
- hr = sampGrabber.SetCallback(this, 1);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- // Configure the samplegrabber\r
- hr = sampGrabber.SetBufferSamples(true);\r
- DsError.ThrowExceptionForHR(hr);\r
- }\r
-\r
- private void SaveSizeInfo(AMMediaType media) {\r
- //int hr;\r
- //AMMediaType media = new AMMediaType();\r
- //hr = sampGrabber.GetConnectedMediaType(media);\r
- //DsError.ThrowExceptionForHR(hr);\r
-\r
- if ((media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero)) {\r
- throw new NotSupportedException("Unknown Grabber Media Format");\r
- }\r
-\r
- // Grab the size info\r
- VideoInfoHeader videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(VideoInfoHeader));\r
- MediaDescription.resolution = new Size(videoInfoHeader.BmiHeader.Width, videoInfoHeader.BmiHeader.Height);\r
- MediaDescription.frameRate = MEDIATIME_REFERENCE / videoInfoHeader.AvgTimePerFrame;\r
-\r
- m_stride = videoInfoHeader.BmiHeader.Width * (videoInfoHeader.BmiHeader.BitCount / 8);\r
- }\r
-\r
- // Shut down capture\r
- private void CloseInterfaces() {\r
- Debug.WriteLine("CloseInterfaces");\r
- int hr;\r
- GC.SuppressFinalize(this);\r
- if (tcWorker != null)\r
- tcWorker.CancelAsync();\r
- lock (this) {\r
- if (State != GraphState.Exiting) {\r
- State = GraphState.Exiting;\r
-\r
- // Release the thread (if the thread was started)\r
- if (m_mre != null) {\r
- m_mre.Set();\r
- }\r
- }\r
-\r
- if (m_mediaCtrl != null) {\r
- // Stop the graph\r
- hr = m_mediaCtrl.Stop();\r
- FilterGraphTools.DisconnectAllPins((IGraphBuilder)m_mediaCtrl);\r
- FilterGraphTools.RemoveAllFilters((IGraphBuilder)m_mediaCtrl);\r
- m_mediaCtrl = null;\r
-\r
- }\r
-\r
- if (m_videoWindow != null) {\r
- hr = m_videoWindow.put_Visible(OABool.False);\r
- hr = m_videoWindow.put_MessageDrain(IntPtr.Zero);\r
- hr = m_videoWindow.put_Owner(IntPtr.Zero);\r
- m_videoWindow = null;\r
- }\r
-\r
- m_mediaEvent = null;\r
- m_mediaSeek = null;\r
-\r
-#if DEBUG\r
- if (m_DsRot != null) {\r
- m_DsRot.Dispose();\r
- m_DsRot = null;\r
- }\r
-#endif\r
- if (m_FilterGraph != null) {\r
- Marshal.ReleaseComObject(m_FilterGraph);\r
- m_FilterGraph = null;\r
- }\r
- }\r
- GC.Collect();\r
- //if (m_eventThread != null)\r
- // m_eventThread.Join();\r
- }\r
-\r
- public int SampleCB(double SampleTime, IMediaSample pSample) {\r
- Marshal.ReleaseComObject(pSample);\r
- return 0;\r
- }\r
-\r
- public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen) {\r
- return 0;\r
- int frames = (int)Math.Abs(SampleTime * MediaDescription.FrameRate);\r
- //Debug.WriteLine("BufferCB frames {0}, sample time {1}", frames, SampleTime);\r
- Font font = new Font("Tahoma", 30);\r
- string display = frames.ToString();\r
- SizeF size = new SizeF(100, 100);\r
- m_Bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));\r
- GraphicsUnit units = GraphicsUnit.Point;\r
- RectangleF bitmapRectF = m_Bitmap.GetBounds(ref units);\r
-\r
- Graphics g = Graphics.FromImage(m_Bitmap);\r
-\r
- g.SmoothingMode = SmoothingMode.AntiAlias;\r
- g.InterpolationMode = InterpolationMode.HighQualityBicubic;\r
- g.PixelOffsetMode = PixelOffsetMode.HighQuality;\r
- g.FillRectangle(Brushes.Transparent, bitmapRectF);\r
- g.DrawString(display, font, Brushes.White, bitmapRectF);\r
- g.Flush();\r
-\r
- m_Bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);\r
- Rectangle r = new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height);\r
- lock (this) {\r
- BitmapData bmdLogo = m_Bitmap.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);\r
- if (bmdLogo != null) {\r
- IntPtr ipSource = bmdLogo.Scan0;\r
- IntPtr ipDest = pBuffer;\r
-\r
- for (int x = 0; x < bmdLogo.Height; x++) {\r
- CopyMemory(ipDest, ipSource, (uint)bmdLogo.Stride);\r
- ipDest = (IntPtr)(ipDest.ToInt64() + m_stride);\r
- ipSource = (IntPtr)(ipSource.ToInt64() + bmdLogo.Stride);\r
- }\r
- }\r
- m_Bitmap.UnlockBits(bmdLogo);\r
- bmdLogo = null;\r
- }\r
- return 0;\r
- }\r
-\r
- // Wait for events to happen. This approach uses waiting on an event handle.\r
- // The nice thing about doing it this way is that you aren't in the windows message\r
- // loop, and don't have to worry about re-entrency or taking too long. Plus, being\r
- // in a class as we are, we don't have access to the message loop.\r
- // Alternately, you can receive your events as windows messages. See\r
- // IMediaEventEx.SetNotifyWindow.\r
- private void EventWait() {\r
- // Returned when GetEvent is called but there are no events\r
- const int E_ABORT = unchecked((int)0x80004004);\r
-\r
- int hr;\r
- IntPtr p1, p2;\r
- EventCode ec;\r
-\r
- do {\r
- // Wait for an event\r
- m_mre.WaitOne(-1, true);\r
-\r
- // Avoid contention for m_State\r
- lock (this) {\r
- // If we are not shutting down\r
- if (State != GraphState.Exiting) {\r
- // Read the event\r
- for (\r
- hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0);\r
- hr >= 0;\r
- hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0)\r
- ) {\r
- // Write the event name to the debug window\r
- Debug.WriteLine(ec.ToString());\r
-\r
- // If the clip is finished playing\r
- if (ec == EventCode.Complete) {\r
- State = GraphState.Completed;\r
- }\r
-\r
- // Release any resources the message allocated\r
- hr = m_mediaEvent.FreeEventParams(ec, p1, p2);\r
- DsError.ThrowExceptionForHR(hr);\r
-\r
- //lock (tcLock) {\r
- // UpdateTC("");\r
- //}\r
-\r
- }\r
-\r
- // If the error that exited the loop wasn't due to running out of events\r
- if (hr != E_ABORT) {\r
- DsError.ThrowExceptionForHR(hr);\r
- }\r
- } else {\r
- // We are shutting down\r
- Debug.WriteLine("Shutdown");\r
- break;\r
- }\r
- }\r
- } while (true);\r
- }\r
-\r
- }\r
-}\r
using DxPlay.Model;\r
using DxPlay.Properties;\r
using MaestroShared.Commons;\r
-using MaestroShared.Metadata;\r
using NLog;\r
using System;\r
using System.Diagnostics;\r
using System.IO;\r
-using System.Linq;\r
using System.Runtime.InteropServices;\r
using System.Windows.Forms;\r
\r
Pause();\r
return;\r
}\r
+ if (m_play.State == GraphState.Completed) {\r
+ m_play.Seek(0);\r
+ }\r
m_play.Play();\r
- playerControls.Play.Image = Resources.ic_pause_black_24dp_2x;\r
- tooltips.SetToolTip(playerControls.Play, Settings.Resource("PAUSE", Resources.PAUSE));\r
+ UpdatePlayPauseButton();\r
+ }\r
+\r
+ private void UpdatePlayPauseButton() {\r
+ if (m_play.State == GraphState.Playing) {\r
+ playerControls.Play.Image = Resources.ic_pause_black_24dp_2x;\r
+ tooltips.SetToolTip(playerControls.Play, Settings.Resource("PAUSE", Resources.PAUSE));\r
+ } else {\r
+ playerControls.Play.Image = Resources.ic_play_arrow_black_24dp_2x;\r
+ tooltips.SetToolTip(playerControls.Play, Settings.Resource("PLAY", Resources.PLAY));\r
+ }\r
}\r
\r
private void Pause() {\r
m_play.Pause();\r
- playerControls.Play.Image = Resources.ic_play_arrow_black_24dp_2x;\r
- tooltips.SetToolTip(playerControls.Play, Settings.Resource("PLAY", Resources.PLAY));\r
+ UpdatePlayPauseButton();\r
}\r
\r
private void OnPlayClick(object sender, EventArgs e) {\r
if (Disposing || IsDisposed || m_play == null)\r
return;\r
if (m_play.State == GraphState.Completed)\r
- Pause();\r
+ UpdatePlayPauseButton();\r
if (!trackBarAtUser)\r
playerControls.TrackBar.Value = m_play.CurrentTC.ZeroBasedFrames;\r
playerControls.CurrentTC.Text = m_play.CurrentTC.ToString();\r
m_play.Dispose();\r
}\r
\r
- private void OnDefineOneSegmentClick(object sender, EventArgs e) {\r
- if (m_play == null)\r
- return;\r
- MovieSegment segment = null;\r
- if (model.Segments.Count == 0) {\r
- segment = new MovieSegment() {\r
- TCIn = new Timecode(m_mediaDescription.FirstFrame.Frames),\r
- TCOut = new Timecode(m_mediaDescription.FirstFrame, m_mediaDescription.Duration)\r
- };\r
- } else {\r
- MovieSegment lastSegment = model.Segments[model.Segments.Count - 1];\r
- Timecode tcEnd = new Timecode(m_mediaDescription.FirstFrame, m_mediaDescription.Duration);\r
- if (lastSegment.TCOut.Frames == tcEnd.Frames) {\r
- MsgBox.Error(Settings.Resource("ERRORCREATESEGMENT", Resources.ERRORCREATESEGMENT));\r
- return;\r
- }\r
- segment = new MovieSegment() {\r
- TCIn = new Timecode(lastSegment.TCOut.Frames),\r
- TCOut = tcEnd\r
- };\r
- }\r
- model.Segments.Add(segment);\r
- }\r
-\r
- private void OnDeleteSegmentClick(object sender, EventArgs e) {\r
- if (bsSegments.Current != null)\r
- model.Segments.Remove(bsSegments.Current as MovieSegment);\r
- }\r
-\r
- private void SetActualPositionAsIn() {\r
- if (m_play == null)\r
- return;\r
- MovieSegment currentSegment = bsSegments.Current as MovieSegment;\r
- if (currentSegment == null || bsSegments.Count == 0) {\r
- MovieSegment newSegment = new MovieSegment() {\r
- TCIn = new Timecode(m_play.CurrentTC.Frames),\r
- TCOut = new Timecode(m_play.MediaDescription.FirstFrame.Frames + m_play.MediaDescription.Duration.Frames)\r
- };\r
- bsSegments.Position = bsSegments.Add(newSegment);\r
- } else {\r
- if (currentSegment.TCOut.Frames <= m_play.CurrentTC.Frames) {\r
- int pos = bsSegments.IndexOf(currentSegment);\r
- if (pos == bsSegments.Count - 1) {\r
- MovieSegment newSegment = new MovieSegment() {\r
- TCIn = new Timecode(m_play.CurrentTC.Frames),\r
- TCOut = new Timecode(m_play.MediaDescription.FirstFrame.Frames + m_play.MediaDescription.Duration.Frames)\r
- };\r
- bsSegments.Position = bsSegments.Add(newSegment);\r
- return;\r
- }\r
- MsgBox.Error(Settings.Resource("ERRORINVALIDTCIN", Resources.ERRORINVALIDTCIN));\r
- return;\r
- }\r
- //if (MessageBox.Show("Biztos felül akarja írni az belépõt?", "Belépõ felülírása", MessageBoxButtons.YesNo) == DialogResult.No)\r
- // return;\r
- MovieSegment collisionSegment = model.Segments.Where(s => s.TCIn.Frames < m_play.CurrentTC.Frames && m_play.CurrentTC.Frames < s.TCOut.Frames).SingleOrDefault();\r
-\r
- if (collisionSegment != null && !currentSegment.Equals(collisionSegment)) {\r
- MsgBox.Error(Settings.Resource("ERRORSEGMENTCOLLISION", Resources.ERRORSEGMENTCOLLISION));\r
- return;\r
- }\r
-\r
- currentSegment.TCIn = new Timecode(m_play.CurrentTC.Frames);\r
- }\r
- }\r
-\r
-\r
- private void SetActualPositionAsOut() {\r
- if (m_play == null)\r
- return;\r
- MovieSegment currentSegment = bsSegments.Current as MovieSegment;\r
- if (currentSegment == null || bsSegments.Count == 0) {\r
- MovieSegment newSegment = new MovieSegment() {\r
- TCIn = new Timecode(m_play.MediaDescription.FirstFrame.Frames),\r
- TCOut = new Timecode(m_play.CurrentTC.Frames),\r
- };\r
- bsSegments.Position = bsSegments.Add(newSegment);\r
- } else {\r
- if (currentSegment.TCIn.Frames >= m_play.CurrentTC.Frames)\r
- MsgBox.Error(Settings.Resource("ERRORINVALIDTCOUT", Resources.ERRORINVALIDTCOUT));\r
-\r
- MovieSegment collisionSegment = model.Segments.Where(s => s.TCIn.Frames < m_play.CurrentTC.Frames && m_play.CurrentTC.Frames < s.TCOut.Frames).SingleOrDefault();\r
-\r
- if (collisionSegment != null && !currentSegment.Equals(collisionSegment)) {\r
- MsgBox.Error(Settings.Resource("ERRORSEGMENTCOLLISION", Resources.ERRORSEGMENTCOLLISION));\r
- return;\r
- }\r
-\r
- //if (MessageBox.Show("Biztos felül akarja írni az kilépõt?", "Kilépõ felülírása", MessageBoxButtons.YesNo) == DialogResult.No)\r
- // return;\r
- currentSegment.TCOut = new Timecode(m_play.CurrentTC.Frames);\r
- }\r
- }\r
-\r
- private void OnActualPositionToTCInToolStripMenuItem1Click(object sender, EventArgs e) {\r
- SetActualPositionAsIn();\r
- }\r
-\r
- private void OnActualPositionToTCOutToolStripMenuItem1Click(object sender, EventArgs e) {\r
- SetActualPositionAsOut();\r
- }\r
-\r
- private void OnSplitSegmentAtCurrentPositionClick(object sender, EventArgs e) {\r
- MovieSegment currentSegment = model.Segments.Where(s => s.TCIn.Frames < m_play.CurrentTC.Frames && s.TCOut.Frames > m_play.CurrentTC.Frames).SingleOrDefault();\r
- if (currentSegment == null)\r
- return;\r
- int position = model.Segments.IndexOf(currentSegment);\r
- MovieSegment newSegment = new MovieSegment() {\r
- TCIn = new Timecode(currentSegment.TCIn.Frames),\r
- TCOut = new Timecode(m_play.CurrentTC.Frames)\r
- };\r
- currentSegment.TCIn = new Timecode(m_play.CurrentTC.Frames);\r
- model.Segments.Insert(position, newSegment);\r
- }\r
-\r
- private void dgSegments_ColumnAdded(object sender, DataGridViewColumnEventArgs e) {\r
- int index = e.Column.Index;\r
- switch (index) {\r
- case 0:\r
- e.Column.HeaderText = Settings.Resource("TCIN", Resources.TCIN);\r
- e.Column.ReadOnly = true;\r
- break;\r
- case 1:\r
- e.Column.HeaderText = Settings.Resource("TCOUT", Resources.TCOUT);\r
- e.Column.ReadOnly = true;\r
- break;\r
- case 2:\r
- e.Column.HeaderText = Settings.Resource("OPTIONAL", Resources.OPTIONAL);\r
- break;\r
- case 3:\r
- e.Column.HeaderText = Settings.Resource("COMMENT", Resources.COMMENT);\r
- break;\r
- }\r
- }\r
-\r
-\r
- private void dgSegments_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) {\r
- MovieSegment actualSegment = bsSegments.Current as MovieSegment;\r
- if (actualSegment == null || m_play == null)\r
- return;\r
- if (e.ColumnIndex == 0) {\r
- m_play.Pause();\r
- m_play.Seek(actualSegment.TCIn.Frames - m_play.MediaDescription.FirstFrame.Frames);\r
- }\r
- if (e.ColumnIndex == 1) {\r
- m_play.Pause();\r
- int pos = actualSegment.TCOut.Frames - m_play.MediaDescription.FirstFrame.Frames;\r
- //Az utolsó kocka nem jelenik meg egyébként\r
- try {\r
- if (pos == m_play.MediaDescription.Duration.Frames) {\r
- pos--;\r
- m_play.Seek(pos);\r
- m_play.Play();\r
- } else\r
- m_play.Seek(pos);\r
- } catch (Exception ex) {\r
- MsgBox.Error(ex.Message);\r
- }\r
-\r
- }\r
-\r
- }\r
-\r
- private void dgSegments_CellContentClick(object sender, DataGridViewCellEventArgs e) {\r
- if (e.ColumnIndex == 2)\r
- dgSegments.EndEdit();\r
- }\r
\r
private void menuOpenFile_Click(object sender, EventArgs e) {\r
openFileDialogOpened = true;\r
\r
}\r
\r
- private void OnToggleSegmentEditor(object sender, EventArgs e) {\r
\r
- }\r
}\r
}\r
--- /dev/null
+using DxPlay.Properties;\r
+using MaestroShared.Commons;\r
+using MaestroShared.Metadata;\r
+using System;\r
+using System.Linq;\r
+using System.Windows.Forms;\r
+\r
+namespace DxPlay {\r
+ public partial class PlayerForm : Form {\r
+\r
+ private void OnDefineOneSegmentClick(object sender, EventArgs e) {\r
+ if (m_play == null)\r
+ return;\r
+ MovieSegment segment = null;\r
+ if (model.Segments.Count == 0) {\r
+ segment = new MovieSegment() {\r
+ TCIn = new Timecode(m_mediaDescription.FirstFrame.Frames),\r
+ TCOut = new Timecode(m_mediaDescription.FirstFrame, m_mediaDescription.Duration)\r
+ };\r
+ } else {\r
+ MovieSegment lastSegment = model.Segments[model.Segments.Count - 1];\r
+ Timecode tcEnd = new Timecode(m_mediaDescription.FirstFrame, m_mediaDescription.Duration);\r
+ if (lastSegment.TCOut.Frames == tcEnd.Frames) {\r
+ MsgBox.Error(Settings.Resource("ERRORCREATESEGMENT", Resources.ERRORCREATESEGMENT));\r
+ return;\r
+ }\r
+ segment = new MovieSegment() {\r
+ TCIn = new Timecode(lastSegment.TCOut.Frames),\r
+ TCOut = tcEnd\r
+ };\r
+ }\r
+ model.Segments.Add(segment);\r
+ }\r
+\r
+ private void OnDeleteSegmentClick(object sender, EventArgs e) {\r
+ if (bsSegments.Current != null)\r
+ model.Segments.Remove(bsSegments.Current as MovieSegment);\r
+ }\r
+\r
+ private void SetActualPositionAsIn() {\r
+ if (m_play == null)\r
+ return;\r
+ MovieSegment currentSegment = bsSegments.Current as MovieSegment;\r
+ if (currentSegment == null || bsSegments.Count == 0) {\r
+ MovieSegment newSegment = new MovieSegment() {\r
+ TCIn = new Timecode(m_play.CurrentTC.Frames),\r
+ TCOut = new Timecode(m_play.MediaDescription.FirstFrame.Frames + m_play.MediaDescription.Duration.Frames)\r
+ };\r
+ bsSegments.Position = bsSegments.Add(newSegment);\r
+ } else {\r
+ if (currentSegment.TCOut.Frames < m_play.CurrentTC.Frames) {\r
+ int pos = bsSegments.IndexOf(currentSegment);\r
+ if (pos == bsSegments.Count - 1) {\r
+ MovieSegment newSegment = new MovieSegment() {\r
+ TCIn = new Timecode(m_play.CurrentTC.Frames),\r
+ TCOut = new Timecode(m_play.MediaDescription.FirstFrame.Frames + m_play.MediaDescription.Duration.Frames)\r
+ };\r
+ bsSegments.Position = bsSegments.Add(newSegment);\r
+ return;\r
+ }\r
+ MsgBox.Error(Settings.Resource("ERRORINVALIDTCIN", Resources.ERRORINVALIDTCIN));\r
+ return;\r
+ }\r
+ //if (MessageBox.Show("Biztos felül akarja írni az belépőt?", "Belépő felülírása", MessageBoxButtons.YesNo) == DialogResult.No)\r
+ // return;\r
+ MovieSegment collisionSegment = model.Segments.Where(s => s.TCIn.Frames < m_play.CurrentTC.Frames && m_play.CurrentTC.Frames <= s.TCOut.Frames).SingleOrDefault();\r
+\r
+ if (collisionSegment != null && !currentSegment.Equals(collisionSegment)) {\r
+ MsgBox.Error(Settings.Resource("ERRORSEGMENTCOLLISION", Resources.ERRORSEGMENTCOLLISION));\r
+ return;\r
+ }\r
+\r
+ currentSegment.TCIn = new Timecode(m_play.CurrentTC.Frames);\r
+ }\r
+ }\r
+\r
+\r
+ private void SetActualPositionAsOut() {\r
+ if (m_play == null)\r
+ return;\r
+ MovieSegment currentSegment = bsSegments.Current as MovieSegment;\r
+ if (currentSegment == null || bsSegments.Count == 0) {\r
+ MovieSegment newSegment = new MovieSegment() {\r
+ TCIn = new Timecode(m_play.MediaDescription.FirstFrame.Frames),\r
+ TCOut = new Timecode(m_play.CurrentTC.Frames),\r
+ };\r
+ bsSegments.Position = bsSegments.Add(newSegment);\r
+ } else {\r
+ if (currentSegment.TCIn.Frames >= m_play.CurrentTC.Frames)\r
+ MsgBox.Error(Settings.Resource("ERRORINVALIDTCOUT", Resources.ERRORINVALIDTCOUT));\r
+\r
+ MovieSegment collisionSegment = model.Segments.Where(s => s.TCIn.Frames < m_play.CurrentTC.Frames && m_play.CurrentTC.Frames <= s.TCOut.Frames).SingleOrDefault();\r
+\r
+ if (collisionSegment != null && !currentSegment.Equals(collisionSegment)) {\r
+ MsgBox.Error(Settings.Resource("ERRORSEGMENTCOLLISION", Resources.ERRORSEGMENTCOLLISION));\r
+ return;\r
+ }\r
+\r
+ //if (MessageBox.Show("Biztos felül akarja írni az kilépőt?", "Kilépő felülírása", MessageBoxButtons.YesNo) == DialogResult.No)\r
+ // return;\r
+ currentSegment.TCOut = new Timecode(m_play.CurrentTC.Frames);\r
+ }\r
+ }\r
+\r
+ private void OnActualPositionToTCInToolStripMenuItem1Click(object sender, EventArgs e) {\r
+ SetActualPositionAsIn();\r
+ }\r
+\r
+ private void OnActualPositionToTCOutToolStripMenuItem1Click(object sender, EventArgs e) {\r
+ SetActualPositionAsOut();\r
+ }\r
+\r
+ private void OnSplitSegmentAtCurrentPositionClick(object sender, EventArgs e) {\r
+ MovieSegment currentSegment = model.Segments.Where(s => s.TCIn.Frames <= m_play.CurrentTC.Frames && s.TCOut.Frames >= m_play.CurrentTC.Frames).SingleOrDefault();\r
+ if (currentSegment == null)\r
+ return;\r
+ int position = model.Segments.IndexOf(currentSegment);\r
+ MovieSegment newSegment = new MovieSegment() {\r
+ TCIn = new Timecode(currentSegment.TCIn.Frames),\r
+ TCOut = new Timecode(m_play.CurrentTC.Frames - 1)\r
+ };\r
+ currentSegment.TCIn = new Timecode(m_play.CurrentTC.Frames);\r
+ model.Segments.Insert(position, newSegment);\r
+ }\r
+\r
+ private void dgSegments_ColumnAdded(object sender, DataGridViewColumnEventArgs e) {\r
+ int index = e.Column.Index;\r
+ switch (index) {\r
+ case 0:\r
+ e.Column.HeaderText = Settings.Resource("TCIN", Resources.TCIN);\r
+ e.Column.ReadOnly = true;\r
+ break;\r
+ case 1:\r
+ e.Column.HeaderText = Settings.Resource("TCOUT", Resources.TCOUT);\r
+ e.Column.ReadOnly = true;\r
+ break;\r
+ case 2:\r
+ e.Column.HeaderText = Settings.Resource("OPTIONAL", Resources.OPTIONAL);\r
+ break;\r
+ case 3:\r
+ e.Column.HeaderText = Settings.Resource("COMMENT", Resources.COMMENT);\r
+ break;\r
+ }\r
+ }\r
+\r
+\r
+ private void dgSegments_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e) {\r
+ MovieSegment actualSegment = bsSegments.Current as MovieSegment;\r
+ if (actualSegment == null || m_play == null)\r
+ return;\r
+ if (e.ColumnIndex == 0) {\r
+ m_play.Pause();\r
+ m_play.Seek(actualSegment.TCIn.Frames - m_play.MediaDescription.FirstFrame.Frames);\r
+ }\r
+ if (e.ColumnIndex == 1) {\r
+ m_play.Pause();\r
+ int pos = actualSegment.TCOut.Frames - m_play.MediaDescription.FirstFrame.Frames;\r
+ //Az utolsó kocka nem jelenik meg egyébként\r
+ try {\r
+ if (pos == m_play.MediaDescription.Duration.Frames) {\r
+ pos--;\r
+ m_play.Seek(pos);\r
+ m_play.Play();\r
+ } else\r
+ m_play.Seek(pos);\r
+ } catch (Exception ex) {\r
+ MsgBox.Error(ex.Message);\r
+ }\r
+\r
+ }\r
+\r
+ }\r
+\r
+ private void dgSegments_CellContentClick(object sender, DataGridViewCellEventArgs e) {\r
+ if (e.ColumnIndex == 2)\r
+ dgSegments.EndEdit();\r
+ }\r
+\r
+ private void OnSegmentEditorMouseClick(object sender, MouseEventArgs e) {\r
+ var ht = dgSegments.HitTest(e.X, e.Y);\r
+\r
+ if (ht.Type == DataGridViewHitTestType.None) {\r
+ dgSegments.ClearSelection();\r
+ }\r
+ }\r
+\r
+ }\r
+}\r